Temporary commit

This commit is contained in:
Zachary Hall 2024-09-16 15:05:53 -07:00
parent 73686be925
commit ac2120aa2e
26 changed files with 349 additions and 95 deletions

3
.gitignore vendored
View file

@ -8,7 +8,10 @@ compile_commands.json
flatpak-repo flatpak-repo
*.flatpak *.flatpak
!build*.sh !build*.sh
!build*.py
!build.gradle !build.gradle
venv
__pycache__
.cxx .cxx
.gradle .gradle
/sdl-android-project/app/jni /sdl-android-project/app/jni

2
.gitmodules vendored
View file

@ -30,7 +30,7 @@
url = https://github.com/google/oboe.git url = https://github.com/google/oboe.git
[submodule "subprojects/protobuf"] [submodule "subprojects/protobuf"]
path = subprojects/protobuf path = subprojects/protobuf
url = /subprojects/protobuf url = https://github.com/protocolbuffers/protobuf.git
[submodule "subprojects/fmt"] [submodule "subprojects/fmt"]
path = subprojects/fmt path = subprojects/fmt
url = https://github.com/fmtlib/fmt.git url = https://github.com/fmtlib/fmt.git

View file

@ -205,7 +205,6 @@ macro(prefix_all)
list(APPEND ${OUT_VAR} ${PREFIX}${ARG}) list(APPEND ${OUT_VAR} ${PREFIX}${ARG})
endforeach() endforeach()
endmacro() endmacro()
find_package(Protobuf REQUIRED)
find_package(gRPC CONFIG REQUIRED) find_package(gRPC CONFIG REQUIRED)
set(_PROTOBUF_LIBPROTOBUF ${Protobuf_LIBRARY_RELEASE}) set(_PROTOBUF_LIBPROTOBUF ${Protobuf_LIBRARY_RELEASE})

View file

@ -2,6 +2,7 @@
#include <filesystem> #include <filesystem>
#include "file_backend.hpp" #include "file_backend.hpp"
#include <stddef.h> #include <stddef.h>
#include <log.hpp>
#include <string.h> #include <string.h>
std::optional<uint64_t> SDLMixerXBackend::get_max_samples() { std::optional<uint64_t> SDLMixerXBackend::get_max_samples() {
return 100; return 100;
@ -38,7 +39,9 @@ void SDLMixerXBackend::load(const char *filename) {
Mix_SetSoundFonts(NULL); Mix_SetSoundFonts(NULL);
} }
} }
DEBUG.writefln("Opening file: %s", filename);
file = open_file(filename); file = open_file(filename);
DEBUG.writeln("Loading file...");
Mix_Music *output = Mix_LoadMUS_RW(get_sdl_file(this->file), 0); Mix_Music *output = Mix_LoadMUS_RW(get_sdl_file(this->file), 0);
if (output == nullptr) { if (output == nullptr) {
throw std::exception(); throw std::exception();

View file

@ -93,8 +93,6 @@ void VgmStreamBackend::switch_stream(int idx) {
free(buf); free(buf);
} }
open = true; open = true;
spec.channels = vf->channels;
spec.freq = vf->sample_rate;
} }
void VgmStreamBackend::cleanup() { void VgmStreamBackend::cleanup() {
streams.clear(); streams.clear();

View file

@ -12,19 +12,7 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#ifdef __EMSCRIPTEN__
#define SAMPLES_PER_BUFFER (1024)
#define SAMP_POS_FRAC_BITS (22)
#else
#define SAMPLES_PER_BUFFER (256)
#define SAMP_POS_FRAC_BITS (24)
#endif
#define VERA_SAMP_CLKS_PER_CPU_CLK ((25000000ULL << SAMP_POS_FRAC_BITS) / 512 / MHZ / 1000000)
#define YM_SAMP_CLKS_PER_CPU_CLK ((3579545ULL << SAMP_POS_FRAC_BITS) / 64 / MHZ / 1000000)
#define SAMPLE_BYTES (2 * sizeof(int16_t))
#define SAMP_POS_MASK (SAMPLES_PER_BUFFER - 1)
#define SAMP_POS_MASK_FRAC (((uint32_t)SAMPLES_PER_BUFFER << SAMP_POS_FRAC_BITS) - 1)
// windowed sinc // windowed sinc
static const int16_t filter[512] = { static const int16_t filter[512] = {

View file

@ -5,6 +5,18 @@
#pragma once #pragma once
#include <SDL.h> #include <SDL.h>
#ifdef __EMSCRIPTEN__
#define SAMPLES_PER_BUFFER (1024)
#define SAMP_POS_FRAC_BITS (22)
#else
#define SAMPLES_PER_BUFFER (256)
#define SAMP_POS_FRAC_BITS (24)
#endif
#define VERA_SAMP_CLKS_PER_CPU_CLK ((25000000ULL << SAMP_POS_FRAC_BITS) / 512 / MHZ / 1000000)
#define YM_SAMP_CLKS_PER_CPU_CLK ((3579545ULL << SAMP_POS_FRAC_BITS) / 64 / MHZ / 1000000)
#define SAMPLE_BYTES (2 * sizeof(int16_t))
#define SAMP_POS_MASK (SAMPLES_PER_BUFFER - 1)
#define SAMP_POS_MASK_FRAC (((uint32_t)SAMPLES_PER_BUFFER << SAMP_POS_FRAC_BITS) - 1)
#define AUDIO_SAMPLERATE (25000000 / 512) #define AUDIO_SAMPLERATE (25000000 / 512)
void audio_callback(void *userdata, Uint8 *stream, int len); void audio_callback(void *userdata, Uint8 *stream, int len);

View file

@ -13,25 +13,28 @@ extern "C" {
#include <file_backend.hpp> #include <file_backend.hpp>
void ZsmBackend::load(const char *filename) { void ZsmBackend::load(const char *filename) {
spec.format = AUDIO_S16SYS; spec.format = AUDIO_S16SYS;
spec.samples = SAMPLES_PER_BUFFER;
spec.channels = 2;
spec.size = spec.samples * SAMPLE_BYTES;
file = open_file(filename); file = open_file(filename);
char magic[2]; char magic[2];
file->read(magic, 2); file->read(magic, 2, 1);
if (magic[0] != 0x7a || magic[1] != 0x6d) { if (magic[0] != 0x7a || magic[1] != 0x6d) {
throw std::exception(); throw std::exception();
} }
uint8_t version; uint8_t version;
file->read(&version, 1); file->read(&version, 1, 1);
uint8_t loop_point[3]; uint8_t loop_point[3];
file->read(loop_point, 3); file->read(loop_point, 3, 1);
this->loop_point = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16); this->loop_point = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16);
file->read(loop_point, 3); file->read(loop_point, 3, 1);
this->pcm_offset = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16); this->pcm_offset = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16);
file->read(&fm_mask, 1); file->read(&fm_mask, 1, 1);
file->read(loop_point, 2); file->read(loop_point, 2, 1);
this->psg_channel_mask = loop_point[0] | ((uint16_t)(loop_point[1]) << 8); this->psg_channel_mask = loop_point[0] | ((uint16_t)(loop_point[1]) << 8);
file->read(loop_point, 2); file->read(loop_point, 2, 1);
this->tick_rate = loop_point[0] | ((uint16_t)(loop_point[1]) << 8); this->tick_rate = loop_point[0] | ((uint16_t)(loop_point[1]) << 8);
file->read(loop_point, 2); // Reserved. file->read(loop_point, 2, 1); // Reserved.
music_data_start = file->get_pos(); music_data_start = file->get_pos();
while (true) { while (true) {
ZsmCommand cmd = get_command(); ZsmCommand cmd = get_command();
@ -100,13 +103,21 @@ size_t ZsmBackend::render(void *buf, size_t maxlen) {
audio_step((int)(clocks)); audio_step((int)(clocks));
} }
audio_render(); audio_render();
audio_callback(nullptr, (Uint8*)(buf), sample_type_len * maxlen); maxlen *= sample_type_len;
return maxlen * sample_type_len; if (audio_buf.size() < maxlen) {
size_t oldlen = audio_buf.size();
audio_buf.resize(audio_buf.size() + spec.size);
audio_callback(nullptr, (Uint8*)(audio_buf.data() + oldlen), spec.size);
}
memcpy(audio_buf.data(), buf, maxlen);
memmove(audio_buf.data(), audio_buf.data() + maxlen, audio_buf.size() - maxlen);
audio_buf.resize(audio_buf.size() - maxlen);
return maxlen;
} }
ZsmCommand ZsmBackend::get_command() { ZsmCommand ZsmBackend::get_command() {
ZsmCommandId cmdid; ZsmCommandId cmdid;
uint8_t cmd_byte; uint8_t cmd_byte;
file->read(&cmd_byte, 1); file->read(&cmd_byte, 1, 1);
if (cmd_byte == 0x80) { if (cmd_byte == 0x80) {
cmdid = ZsmEOF; cmdid = ZsmEOF;
} else { } else {
@ -128,7 +139,7 @@ ZsmCommand ZsmBackend::get_command() {
return output; return output;
} else if (cmdid == PsgWrite) { } else if (cmdid == PsgWrite) {
uint8_t value; uint8_t value;
file->read(&value, 1); file->read(&value, 1, 1);
output.psg_write.reg = cmd_byte & 0x3F; output.psg_write.reg = cmd_byte & 0x3F;
output.psg_write.val = value; output.psg_write.val = value;
} else if (cmdid == FmWrite) { } else if (cmdid == FmWrite) {
@ -139,11 +150,11 @@ ZsmCommand ZsmBackend::get_command() {
for (uint8_t i = 0; i < pairs; i++) { for (uint8_t i = 0; i < pairs; i++) {
output.fm_write.regs[i].reg = value[0]; output.fm_write.regs[i].reg = value[0];
output.fm_write.regs[i].val = value[1]; output.fm_write.regs[i].val = value[1];
file->read(value, 2); file->read(value, 2, 1);
} }
} else if (cmdid == ExtCmd) { } else if (cmdid == ExtCmd) {
uint8_t ext_cmd_byte; uint8_t ext_cmd_byte;
file->read(&ext_cmd_byte, 1); file->read(&ext_cmd_byte, 1, 1);
uint8_t bytes = ext_cmd_byte & 0x3F; uint8_t bytes = ext_cmd_byte & 0x3F;
uint8_t ch = ext_cmd_byte >> 6; uint8_t ch = ext_cmd_byte >> 6;
output.extcmd.channel = ch; output.extcmd.channel = ch;
@ -155,7 +166,7 @@ ZsmCommand ZsmBackend::get_command() {
} }
for (size_t i = 0; i < bytes; i++) { for (size_t i = 0; i < bytes; i++) {
uint8_t byte; uint8_t byte;
file->read(&byte, 1); file->read(&byte, 1, 1);
switch (ch) { switch (ch) {
case 0: { case 0: {
output.extcmd.pcm[i] = byte; output.extcmd.pcm[i] = byte;

View file

@ -46,6 +46,7 @@ struct ZsmCommand {
}; };
class ZsmBackend : public PlaybackBackend { class ZsmBackend : public PlaybackBackend {
File *file; File *file;
std::vector<uint8_t> audio_buf;
uint32_t loop_point; uint32_t loop_point;
uint32_t pcm_offset; uint32_t pcm_offset;
uint8_t fm_mask; uint8_t fm_mask;

View file

@ -58,7 +58,7 @@ class RendererBackend {
void SetWindowSize(int w, int h); void SetWindowSize(int w, int h);
void GetWindowsize(int *w, int *h); void GetWindowsize(int *w, int *h);
RendererBackend(); RendererBackend();
~RendererBackend(); virtual ~RendererBackend();
friend void main_loop(); friend void main_loop();
friend void backend_init(void *userdata); friend void backend_init(void *userdata);
}; };

122
build-appimage.py Executable file
View file

@ -0,0 +1,122 @@
#!/usr/bin/env python3
import os
import sys
from os import path
from sys import argv
import urllib
from subprocess import call, check_output
import shutil
import pathlib
from urllib import request
from urllib.request import urlretrieve
import lddwrap
import argparse
from resolve_library import resolve as resolve_library
basedir = path.realpath(path.dirname(__file__))
def download_always(url: str, filename: str) -> None:
urlretrieve(url, filename)
def download_if_not_found(url: str, filename: str) -> None:
if path.exists(filename):
return
download_always(url, filename)
def download_appimagetool():
url = "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
filename = "appimagetool"
download_if_not_found(url, filename)
def build(*args: list[str]):
call(args=["./build.sh", *args])
def remove_recursive(inpath: str):
print("remove_recursive(%s)" % inpath)
if path.isdir(inpath) and not path.islink(inpath):
for inner in os.listdir(inpath):
remove_recursive(path.join(inpath, inner))
print("os.removedirs(%s)" % inpath)
os.removedirs(inpath)
elif path.islink(inpath):
print("os.unlink(%s)" % inpath)
os.unlink(inpath)
else:
print("os.remove(%s)" % inpath)
os.remove(inpath)
def force_link(src: str, dst: str):
if path.exists(dst):
remove_recursive(dst)
os.symlink(src, dst)
def force_copy(src: str, dst: str):
print("force_copy(%s, %s)" % (src, dst))
if path.exists(dst) and path.isdir(dst):
dst = path.join(dst, path.basename(src))
if path.exists(dst):
remove_recursive(dst)
shutil.copy(src, dst)
def copy_libraries(libpath: str, previous: list[str] = []):
print("Getting libraries for '%s'..." % libpath)
for lib in lddwrap.list_dependencies(pathlib.Path(libpath)):
if str(lib.path) in previous:
continue
print(" - Library %s" % lib.soname, end="")
if lib.path == None:
print("")
continue
print(" (%s)" % lib.path)
inpaths = resolve_library(str(lib.path))
for inpath in inpaths:
outpath = path.join("AppDir/lib", path.basename(inpath))
force_copy(inpath, outpath)
previous.append(str(lib.path))
copy_libraries(str(lib.path), previous)
def overwrite_file(dst: str, contents: str):
if path.exists(dst):
remove_recursive(dst)
with open(dst, "wt+") as f:
f.write(contents)
def main() -> None:
P = argparse.ArgumentParser()
P.add_argument("-j", "--parallel", action=argparse._StoreAction, default=0, type=int, help="Specifies the number of jobs to use. Set to 1 to disable parallelism", dest="parallel")
P.add_argument("-D", "--define", default=[], action=argparse._AppendAction, help="Defines a CMake variable", dest="cmake_vars")
p = P.parse_args(argv[1:])
old_dir = os.curdir
os.chdir(basedir)
os.chdir("build")
args=["cmake", ".."]
for definition in p.cmake_vars:
args.append("-D%s" % definition)
ret = call(args);
if ret != 0:
exit(ret)
parallel = p.parallel
if parallel == 0:
parallel = os.cpu_count()
args=["cmake", "--build", ".", "-j%d" % parallel]
ret = call(args)
if ret != 0:
exit(ret)
download_appimagetool()
os.makedirs("AppDir", exist_ok=True)
for dir in ["bin", "lib", "share"]:
os.makedirs(path.join("AppDir", dir), exist_ok=True)
force_link(".", "AppDir/usr")
force_link(".", "AppDir/local")
force_link("lib", "AppDir/lib64")
force_copy("looper", "AppDir/bin")
copy_libraries("looper")
force_copy(path.join(basedir, "assets/com.complecwaft.Looper.desktop"), "AppDir")
force_copy(path.join(basedir, "assets/icon.svg"), "AppDir/looper.svg")
force_copy(path.join(basedir, "assets/icon.png"), "AppDir/looper.png")
overwrite_file("AppDir/AppRun", """#!/bin/bash
export LD_LIBRARY_PATH="$APPDIR/lib"
if [ "$1" = "--gdb" ]; then
shift
exec gdb --args "$APPDIR/usr/bin/looper" "$@"
else
exec "$APPDIR/usr/bin/looper" "$@"
fi""")
os.chmod("AppDir/AppRun", 0o777)
arch = check_output(["uname", "-m"])
print("Architecture: %s" % arch.decode())
os.environ["ARCH"] = arch.decode().removesuffix("\n")
call(args=["./appimagetool", "AppDir", "Looper.AppImage"])
os.chdir(old_dir)
if __name__ == "__main__":
main()

5
build-appimage.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/bash
pushd "$(dirname "$0")"
. venv/bin/activate
./build-appimage.py "$@"
popd

View file

@ -9,7 +9,13 @@ on_err() {
exit $code exit $code
} }
have_image() { have_image() {
docker images --format json | jq '.[].Names' --raw-output | grep -v null | jq '.[]' --raw-output | sed 's/:.*$//' | sed 's@^.*/@@' | grep -Fx "$1" >/dev/null 2>/dev/null IMAGES="$(docker images --format json)"
if echo "$IMAGES" | jq '.Repository' | grep '^null$'; then
IMAGES="$(echo "$IMAGES" | jq '.[].Names' | grep -v null | jq '.[]' --raw-output)"
else
IMAGES="$(echo "$IMAGES" | jq '.Repository' --raw-output)"
fi
echo "$IMAGES" | sed 's/:.*$//' | sed 's@^.*/@@' | grep -Fx "$1" >/dev/null 2>/dev/null
return $? return $?
} }
trap on_err ERR trap on_err ERR
@ -31,6 +37,7 @@ build_cmake() {
if [ "$first_arg" = "in-docker" ]; then if [ "$first_arg" = "in-docker" ]; then
ARGS=( "$@" ) ARGS=( "$@" )
cd /src cd /src
git config --global --add safe.directory /src
build_cmake subprojects/protobuf "${ARGS[@]}" build_cmake subprojects/protobuf "${ARGS[@]}"
pushd subprojects/grpc pushd subprojects/grpc
git apply ../../grpc.patch || true git apply ../../grpc.patch || true

View file

@ -24,8 +24,9 @@ run_command() {
trap on_err ERR trap on_err ERR
} }
trap on_err ERR trap on_err ERR
cd "$(dirname "$0")"
mkdir -p build mkdir -p build
cd build cd build
run_command cmake .. -DDISABLE_GTK_UI=ON -DCMAKE_BUILD_TYPE=Debug run_command cmake .. -DDISABLE_GTK_UI=ON -DCMAKE_BUILD_TYPE=Debug
run_command cmake --build . "$@" run_command cmake --build . "$@"
cd .. cd "$OLD_DIR"

View file

@ -100,7 +100,7 @@ class MprisAPI : public sdbus::AdaptorInterfaces<org::mpris::MediaPlayer2_adapto
std::vector<track_id_t> Tracks() override; std::vector<track_id_t> Tracks() override;
bool CanEditTracks() override; bool CanEditTracks() override;
MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api); MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api);
~MprisAPI(); virtual ~MprisAPI();
}; };
#endif #endif
class DBusAPI class DBusAPI
@ -184,7 +184,7 @@ class DBusAPI
DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string objectPath, bool daemon); DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string objectPath, bool daemon);
#endif #endif
DBusAPI(Playback *playback, bool daemon); DBusAPI(Playback *playback, bool daemon);
~DBusAPI(); virtual ~DBusAPI();
static DBusAPI *Create(Playback *playback, bool daemon = false); static DBusAPI *Create(Playback *playback, bool daemon = false);
}; };
class DBusAPISender : public Playback class DBusAPISender : public Playback
@ -256,7 +256,7 @@ class DBusAPISender : public Playback
protected: protected:
DBusAPISender(sdbus::IConnection &connection, std::string busName, std::string objectPath); DBusAPISender(sdbus::IConnection &connection, std::string busName, std::string objectPath);
public: public:
~DBusAPISender(); virtual ~DBusAPISender();
#else #else
public: public:
~DBusAPISender() = default; ~DBusAPISender() = default;

View file

@ -27,9 +27,9 @@ void CFile::close() {
fclose(file); fclose(file);
file = NULL; file = NULL;
} }
size_t CFile::read(void *ptr, size_t len) { size_t CFile::read(void *ptr, size_t size, size_t len) {
if (file == NULL) return 0; if (file == NULL) return 0;
return fread(ptr, 1, len, file); return fread(ptr, size, len, file);
} }
void CFile::seek(size_t pos, SeekType seek_type) { void CFile::seek(size_t pos, SeekType seek_type) {
int whence; int whence;
@ -56,17 +56,7 @@ bool CFile::is_open() {
} }
size_t rwops_read(SDL_RWops *rwops, void *ptr, size_t size, size_t maxnum) { size_t rwops_read(SDL_RWops *rwops, void *ptr, size_t size, size_t maxnum) {
File *file = (File*)rwops->hidden.unknown.data1; File *file = (File*)rwops->hidden.unknown.data1;
uint8_t *ptr8 = (uint8_t*)ptr; return file->read(ptr, size, maxnum);
size_t out = 0;
size_t tmp;
for (size_t i = 0; i < maxnum; i++) {
tmp = file->read(ptr8 + (i * size), size);
out++;
if (tmp == 0) {
break;
}
}
return out;
} }
int rwops_close(SDL_RWops *rwops) { int rwops_close(SDL_RWops *rwops) {
File *file = (File*)rwops->hidden.unknown.data1; File *file = (File*)rwops->hidden.unknown.data1;
@ -116,7 +106,7 @@ static file_streamfile *sf_open(file_streamfile *sf, const char *const filename,
static size_t sf_read(file_streamfile *sf, uint8_t *dst, offv_t offset, size_t length) { static size_t sf_read(file_streamfile *sf, uint8_t *dst, offv_t offset, size_t length) {
File *file = sf->file; File *file = sf->file;
file->seek(offset, SeekType::SET); file->seek(offset, SeekType::SET);
return file->read(dst, length); return file->read(dst, 1, length);
} }
static size_t sf_size(file_streamfile *sf) { static size_t sf_size(file_streamfile *sf) {
File *file = sf->file; File *file = sf->file;

View file

@ -20,12 +20,17 @@ class File {
const char *name; const char *name;
File(const char *fname); File(const char *fname);
inline virtual void open(const char *fname) { name = fname; } inline virtual void open(const char *fname) { name = fname; }
virtual void close() = 0; inline virtual void close() { }
virtual size_t read(void *ptr, size_t len) = 0; virtual size_t read(void *ptr, size_t size, size_t len) = 0;
virtual void seek(size_t pos, SeekType seek_type) = 0; virtual void seek(size_t pos, SeekType seek_type) = 0;
virtual size_t get_len(); virtual size_t get_len();
virtual size_t get_pos() = 0; virtual size_t get_pos() = 0;
virtual bool is_open() = 0; inline virtual bool is_open() {
return false;
}
inline virtual ~File() {
if (is_open()) close();
}
}; };
class CFile : public File { class CFile : public File {
protected: protected:
@ -34,7 +39,7 @@ class CFile : public File {
CFile(const char *fname); CFile(const char *fname);
void open(const char *fname) override; void open(const char *fname) override;
void close() override; void close() override;
size_t read(void *ptr, size_t len) override; size_t read(void *ptr, size_t size, size_t len) override;
void seek(size_t pos, SeekType seek_type) override; void seek(size_t pos, SeekType seek_type) override;
size_t get_pos() override; size_t get_pos() override;
bool is_open() override; bool is_open() override;
@ -45,7 +50,7 @@ class HttpFile : public File {
HttpFile(const char *url); HttpFile(const char *url);
void open(const char *url) override; void open(const char *url) override;
void close() override; void close() override;
size_t read(void *ptr, size_t len) override; size_t read(void *ptr, size_t size, size_t len) override;
void seek(size_t pos, SeekType type) override; void seek(size_t pos, SeekType type) override;
size_t get_pos(); size_t get_pos();
bool is_open(); bool is_open();
@ -58,7 +63,7 @@ class AndroidFile : public File {
AndroidFile(const char *fname); AndroidFile(const char *fname);
void open(const char *fname) override; void open(const char *fname) override;
void close() override; void close() override;
size_t read(void *ptr, size_t len) override; size_t read(void *ptr, size_t size, size_t len) override;
void seek(size_t pos, SeekType seek_type) override; void seek(size_t pos, SeekType seek_type) override;
size_t get_pos() override; size_t get_pos() override;
bool is_open() override; bool is_open() override;

View file

@ -46,6 +46,7 @@ namespace Looper::Log {
void writesn(const char *msg, size_t n); void writesn(const char *msg, size_t n);
void writes(std::string msg); void writes(std::string msg);
virtual void writec(const char chr); virtual void writec(const char chr);
inline virtual ~LogStream() { }
void vwritef(const char *fmt, va_list args); void vwritef(const char *fmt, va_list args);
void writef(const char *fmt, ...); void writef(const char *fmt, ...);
void vwritefln(const char *fmt, va_list args); void vwritefln(const char *fmt, va_list args);

View file

@ -140,8 +140,6 @@ extern "C" int looper_run_as_executable(std::vector<std::string> args) {
} }
DEBUG.writeln("Initializing frontends..."); DEBUG.writeln("Initializing frontends...");
init_backends(); init_backends();
DEBUG.writeln("Initializing playback backends...");
init_playback_backends();
#ifdef DBUS_ENABLED #ifdef DBUS_ENABLED
ProxyGlueBackend *proxy_backend = nullptr; ProxyGlueBackend *proxy_backend = nullptr;
if ((disable_gui && !daemonize) || quit) { if ((disable_gui && !daemonize) || quit) {
@ -230,21 +228,28 @@ extern "C" int looper_run_as_executable(std::vector<std::string> args) {
#endif #endif
return output; return output;
} }
std::string current_process_type;
extern int looper_run_playback_process(std::vector<std::string> args); extern int looper_run_playback_process(std::vector<std::string> args);
int main(int argc, char **argv) { int main(int argc, char **argv) {
CLI::App app{DESCRIPTION}; CLI::App app{DESCRIPTION};
std::string process_type = "normal"; std::string process_type = "normal";
app.add_option<std::string, std::string>("--process-type", process_type); app.add_option<std::string, std::string>("--process-type", process_type);
app.allow_extras(); app.allow_extras();
app.parse(argc, argv); try {
executable_path = argv[0]; app.parse(argc, argv);
if (process_type == "playback") { executable_path = argv[0];
return looper_run_playback_process(app.remaining(false)); current_process_type = "host";
} else if (process_type == "daemon") { if (process_type == "playback") {
std::vector<std::string> opts = app.remaining(false); current_process_type = "playback";
opts.push_back("-d"); return looper_run_playback_process(app.remaining(false));
return looper_run_as_executable(opts); } else if (process_type == "daemon") {
} else { std::vector<std::string> opts = app.remaining(false);
return looper_run_as_executable(app.remaining(false)); opts.push_back("-d");
return looper_run_as_executable(opts);
} else {
return looper_run_as_executable(app.remaining(false));
}
} catch (CLI::CallForHelp) {
looper_run_as_executable(std::vector<std::string>({"--help"}));
} }
} }

View file

@ -1,5 +1,6 @@
#include "playback.h" #include "playback.h"
#include "SDL_mixer.h" #include "SDL_mixer.h"
#include "playback_backend.hpp"
#include <SDL_audio.h> #include <SDL_audio.h>
extern "C" { extern "C" {
#include <vgmstream.h> #include <vgmstream.h>
@ -82,7 +83,7 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) {
return; return;
} }
size_t unit = sizeof(SAMPLETYPE) * spec.channels; size_t unit = sizeof(SAMPLETYPE) * spec.channels;
size_t bytes_per_iter = ((bufsize / unit)) * unit; size_t bytes_per_iter = (bufsize / unit) * unit;
while (st->numSamples() < (size_t)len) { while (st->numSamples() < (size_t)len) {
if (process == nullptr) { if (process == nullptr) {
return; return;
@ -111,6 +112,8 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) {
sbuf[i] *= real_volume; sbuf[i] *= real_volume;
} }
st->putSamples(sbuf, new_bufsize / unit); st->putSamples(sbuf, new_bufsize / unit);
} else {
return;
} }
} }
st->receiveSamples((SAMPLETYPE*)stream, len / unit); st->receiveSamples((SAMPLETYPE*)stream, len / unit);
@ -156,7 +159,7 @@ void PlaybackInstance::Load(const char *file, int idx) {
sdl_stream = SDL_NewAudioStream(backend_spec.format, backend_spec.channels, backend_spec.freq, spec.format, spec.channels, spec.freq); sdl_stream = SDL_NewAudioStream(backend_spec.format, backend_spec.channels, backend_spec.freq, spec.format, spec.channels, spec.freq);
if (sdl_stream == nullptr) { if (sdl_stream == nullptr) {
ERROR.writefln("SDL_NewAudioStream: %s", SDL_GetError()); ERROR.writefln("SDL_NewAudioStream: %s", SDL_GetError());
DEBUG.writefln("format: AUDIO_%s%d%s", sample_fmt.is_float ? "F" : sample_fmt.is_signed ? "S" : "U", sample_fmt.size, sample_fmt.endian ? "MSB" : "LSB"); DEBUG.writefln("format: AUDIO_%s%d%s", sample_fmt.is_float ? "F" : sample_fmt.is_signed ? "S" : "U", sample_fmt.size * 8, sample_fmt.endian ? "MSB" : "LSB");
set_error("Failed to create SDL audio stream"); set_error("Failed to create SDL audio stream");
set_signal(PlaybackSignalErrorOccurred); set_signal(PlaybackSignalErrorOccurred);
} }
@ -174,6 +177,7 @@ void PlaybackInstance::Load(const char *file, int idx) {
bufsize = 0; bufsize = 0;
} }
delete backend_spec_proxy; delete backend_spec_proxy;
playback_ready.store(true);
} else { } else {
ERROR.writeln("Failed to detect valid playback backend for file!"); ERROR.writeln("Failed to detect valid playback backend for file!");
set_error("Failed to detect valid backend for file."); set_error("Failed to detect valid backend for file.");
@ -233,7 +237,7 @@ void PlaybackInstance::InitLoopFunction() {
AUDIO_F32SYS; AUDIO_F32SYS;
#endif #endif
desired.freq = 48000; desired.freq = 48000;
desired.samples = 1024; desired.samples = 100;
desired.channels = 2; desired.channels = 2;
desired.callback = PlaybackInstance::SDLCallback; desired.callback = PlaybackInstance::SDLCallback;
desired.userdata = this; desired.userdata = this;

View file

@ -29,6 +29,7 @@
#include <windows.h> #include <windows.h>
#endif #endif
#include <google/protobuf/message.h> #include <google/protobuf/message.h>
#include "util.hpp"
using namespace google::protobuf; using namespace google::protobuf;
int sndfd; int sndfd;
int rcvfd; int rcvfd;
@ -80,15 +81,22 @@ void print_ipc_message(const google::protobuf::Message &msg, size_t level) {
auto &unknown_fields = reflect->GetUnknownFields(msg); auto &unknown_fields = reflect->GetUnknownFields(msg);
DEBUG.writef_level(level, "Unknown fields: %d", unknown_fields.field_count()); DEBUG.writef_level(level, "Unknown fields: %d", unknown_fields.field_count());
} }
void show_command(const std::string &cmdid, const google::protobuf::Message &msg) {
DEBUG.writefln("Command %s:", cmdid.c_str());
print_ipc_message(msg);
}
grpc::Status PlaybackProcessServiceImpl::Render(grpc::ServerContext *ctx, const RenderCommand *cmd, RenderResponseOrError *response) { grpc::Status PlaybackProcessServiceImpl::Render(grpc::ServerContext *ctx, const RenderCommand *cmd, RenderResponseOrError *response) {
size_t maxlen = cmd->len(); size_t maxlen = cmd->len();
void *ptr = malloc(maxlen); void *ptr = malloc(maxlen);
size_t len = cur_backend->render(ptr, maxlen); size_t len = cur_backend->render(ptr, maxlen);
std::string buf = std::string(len, ' ', std::allocator<char>()); if (len == 0) {
memcpy(buf.data(), ptr, len); DEBUG.writeln("Didn't get any audio when rendering");
free(ptr); } else if (is_zeroes(ptr, len)) {
DEBUG.writeln("Buffer was only zeroes!");
}
auto output = new RenderResponse(); auto output = new RenderResponse();
output->set_data(buf); output->set_data((const char*)ptr, len);
free(ptr);
output->set_len(len); output->set_len(len);
response->set_allocated_output(output); response->set_allocated_output(output);
return grpc::Status::OK; return grpc::Status::OK;
@ -214,7 +222,7 @@ grpc::Status HostProcessImpl::WriteLog(grpc::ServerContext *ctx, const LogMessag
} }
grpc::Status HostProcessImpl::SetAddress(grpc::ServerContext *ctx, const StringProperty *value, MaybeError *response) { grpc::Status HostProcessImpl::SetAddress(grpc::ServerContext *ctx, const StringProperty *value, MaybeError *response) {
process->host_channel.value().construct_client(value->value()); process->host_channel.value().construct_client(value->value());
process->done = true; process->started.notify_all();
return grpc::Status::OK; return grpc::Status::OK;
} }
grpc::Status PlaybackProcessServiceImpl::Set(grpc::ServerContext *ctx, const SetProperty *request, MaybeError *response) { grpc::Status PlaybackProcessServiceImpl::Set(grpc::ServerContext *ctx, const SetProperty *request, MaybeError *response) {
@ -284,13 +292,16 @@ grpc::Status PlaybackProcessServiceImpl::Init(grpc::ServerContext *ctx, const In
auto filename = cmd->filename(); auto filename = cmd->filename();
auto idx = cmd->idx(); auto idx = cmd->idx();
for (auto &backend : PlaybackBackendHelper()) { for (auto &backend : PlaybackBackendHelper()) {
DEBUG.writefln("Trying backend: %s", backend.second->get_name().c_str());
try { try {
backend.second->init(filename.c_str(), idx); backend.second->init(filename.c_str(), idx);
} catch (std::exception e) { } catch (std::exception e) {
DEBUG.writeln("Cleaning up backend.");
backend.second->cleanup(); backend.second->cleanup();
continue; continue;
} }
cur_backend = backend.second; cur_backend = backend.second;
DEBUG.writefln("Using backend: %s", backend.second->get_name().c_str());
break; break;
} }
if (cur_backend == nullptr) { if (cur_backend == nullptr) {
@ -301,6 +312,7 @@ grpc::Status PlaybackProcessServiceImpl::Init(grpc::ServerContext *ctx, const In
response->set_allocated_err(maybe_error); response->set_allocated_err(maybe_error);
process->done = true; process->done = true;
process->playback_process_channel.value().get_server()->get_server()->Shutdown(); process->playback_process_channel.value().get_server()->get_server()->Shutdown();
DEBUG.writefln("Couldn't find any backend.");
return grpc::Status::OK; return grpc::Status::OK;
} }
return grpc::Status::OK; return grpc::Status::OK;
@ -347,7 +359,12 @@ StreamList serialize_stream_list(std::vector<PlaybackStream> streams) {
PlaybackProcess::PlaybackProcess(std::vector<std::string> args) { PlaybackProcess::PlaybackProcess(std::vector<std::string> args) {
done = false; done = false;
is_playback_process = true; is_playback_process = true;
Looper::Log::init_logging();
init_playback_backends(); init_playback_backends();
DEBUG.writeln("Playback backends: ");
for (auto &backend : PlaybackBackendHelper()) {
DEBUG.writefln(" - %s", backend.second->get_id().c_str());
}
init_audio_data(); init_audio_data();
std::string address = args[0]; std::string address = args[0];
auto mk_service = [this]() -> grpc::Service* { auto mk_service = [this]() -> grpc::Service* {
@ -357,6 +374,7 @@ PlaybackProcess::PlaybackProcess(std::vector<std::string> args) {
}; };
playback_process_channel = IPCChannel<PlaybackProcessService, HostProcess>(mk_service); playback_process_channel = IPCChannel<PlaybackProcessService, HostProcess>(mk_service);
playback_process_channel.value().construct_client(address); playback_process_channel.value().construct_client(address);
DEBUG.writefln("Host process address: %s", address.c_str());
{ {
StringProperty property; StringProperty property;
property.set_value(playback_process_channel.value().get_server()->get_address()); property.set_value(playback_process_channel.value().get_server()->get_address());
@ -364,7 +382,6 @@ PlaybackProcess::PlaybackProcess(std::vector<std::string> args) {
MaybeError response; MaybeError response;
playback_process_channel.value().get_stub()->SetAddress(&ctx, property, &response); playback_process_channel.value().get_stub()->SetAddress(&ctx, property, &response);
} }
Looper::Log::init_logging();
} }
PlaybackProcess::PlaybackProcess(std::string filename, int idx) { PlaybackProcess::PlaybackProcess(std::string filename, int idx) {
done = false; done = false;
@ -383,9 +400,17 @@ PlaybackProcess::PlaybackProcess(std::string filename, int idx) {
new_args.push_back("playback"); new_args.push_back("playback");
new_args.push_back(address); new_args.push_back(address);
pid = launch(new_args); pid = launch(new_args);
while (!done) { std::thread process_check_thread(std::mem_fn(&PlaybackProcess::threadfunc), this);
std::this_thread::yield(); std::unique_lock lk(start_mutex);
started.wait(lk);
if (done) {
ERROR.writeln("Playback process exited too early!");
throw std::exception();
} }
lk.unlock();
done = true;
process_check_thread.join();
DEBUG.writeln("Playback process started.");
ClientContext ctx; ClientContext ctx;
InitCommand cmd; InitCommand cmd;
cmd.set_filename(filename); cmd.set_filename(filename);
@ -502,9 +527,15 @@ size_t PlaybackProcess::render(void *buf, size_t maxlen) {
RenderResponseOrError response; RenderResponseOrError response;
get_stub()->Render(&ctx, rend_cmd, &response); get_stub()->Render(&ctx, rend_cmd, &response);
if (response.has_err()) { if (response.has_err()) {
ERROR.writefln("Error rendering audio: %s", response.err().id().c_str());
return 0; return 0;
} else { } else {
std::string data = response.output().data(); std::string data = response.output().data();
if (data.length() == 0) {
WARNING.writeln("Rendering audio didn't produce anything!");
} else if (is_zeroes(data.data(), data.length())) {
DEBUG.writeln("RECV'd buffer was only zeroes!");
}
memcpy(buf, data.data(), data.length()); memcpy(buf, data.data(), data.length());
return data.length(); return data.length();
} }
@ -519,3 +550,19 @@ PlaybackProcess::~PlaybackProcess() {
get_stub()->Quit(&ctx, quit_cmd, &output); get_stub()->Quit(&ctx, quit_cmd, &output);
done = true; done = true;
} }
void PlaybackProcess::threadfunc() {
while (!process_running()) {
std::this_thread::yield();
if (done) return;
}
while (true) {
if (!process_running()) {
done = true;
started.notify_all();
}
if (done) {
return;
}
std::this_thread::yield();
}
}

View file

@ -3,9 +3,11 @@
#include <mutex> #include <mutex>
#include <atomic> #include <atomic>
#include <stdint.h> #include <stdint.h>
#include <future>
#include <thread> #include <thread>
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
#include "thirdparty/CRC.hpp" #include "thirdparty/CRC.hpp"
#include "playback_backend.hpp" #include "playback_backend.hpp"
#include <grpc/grpc.h> #include <grpc/grpc.h>
@ -14,7 +16,6 @@
#include <grpcpp/server_builder.h> #include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h> #include <grpcpp/server_context.h>
#include "rpc.hpp" #include "rpc.hpp"
#include <google/protobuf/stubs/status.h>
#include "ipc/common.pb.h" #include "ipc/common.pb.h"
#include "ipc/internal.pb.h" #include "ipc/internal.pb.h"
#include "ipc/internal.grpc.pb.h" #include "ipc/internal.grpc.pb.h"
@ -51,8 +52,10 @@ class PlaybackProcess {
friend class PlaybackProcessServiceImpl; friend class PlaybackProcessServiceImpl;
void threadfunc(); void threadfunc();
int pid; int pid;
std::mutex start_mutex;
std::condition_variable started;
bool is_playback_process = false; bool is_playback_process = false;
bool done; std::atomic_bool done;
std::optional<IPCChannel<HostProcess, PlaybackProcessService>> host_channel; std::optional<IPCChannel<HostProcess, PlaybackProcessService>> host_channel;
std::optional<IPCChannel<PlaybackProcessService, HostProcess>> playback_process_channel; std::optional<IPCChannel<PlaybackProcessService, HostProcess>> playback_process_channel;
inline std::unique_ptr<PlaybackProcessService::Stub> get_stub() { inline std::unique_ptr<PlaybackProcessService::Stub> get_stub() {

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
lddwrap

20
resolve_library.py Executable file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env python3
from sys import argv
from os import readlink
from os import path
def resolve(inpath: str) -> list[str]:
output = []
inpath = path.abspath(inpath)
dir=path.dirname(inpath)
while path.islink(inpath):
output.append(inpath)
inpath=readlink(inpath)
if not path.isabs(inpath):
inpath=path.join(dir, inpath)
dir=path.dirname(inpath)
return output
if __name__ == "__main__":
for i in argv[1:]:
paths=resolve(i)
for path in paths:
print(path)

29
rpc.hpp
View file

@ -2,8 +2,12 @@
#include <algorithm> #include <algorithm>
#include <any> #include <any>
#include <grpcpp/security/server_credentials.h> #include <grpcpp/security/server_credentials.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/support/byte_buffer.h> #include <grpcpp/support/byte_buffer.h>
#include <jsoncpp/json/json.h> #include <grpc/grpc.h>
#include <grpc/support/port_platform.h>
#include <json/json.h>
#include <chrono>
#include <google/protobuf/descriptor.h> #include <google/protobuf/descriptor.h>
#include <google/protobuf/generated_message_reflection.h> #include <google/protobuf/generated_message_reflection.h>
#include <google/protobuf/message.h> #include <google/protobuf/message.h>
@ -28,6 +32,8 @@
#include <grpc++/server.h> #include <grpc++/server.h>
#include <variant> #include <variant>
#include <filesystem> #include <filesystem>
#include "log.hpp"
using namespace std::literals;
#define _FORMAT_CPP_TYPE(fdesc, value, prefix, int32, int64, uint32, uint64, float, double, bool, string, enum, message, ...) \ #define _FORMAT_CPP_TYPE(fdesc, value, prefix, int32, int64, uint32, uint64, float, double, bool, string, enum, message, ...) \
switch (fdesc->cpp_type()) { \ switch (fdesc->cpp_type()) { \
case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: \ case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: \
@ -67,9 +73,10 @@ switch (fdesc->cpp_type()) { \
#define FORMAT_CPP_TYPE_TITLECASE(fdesc, value, prefix, ...) _FORMAT_CPP_TYPE(fdesc, value, prefix, Int32, Int64, UInt32, UInt64, Float, Double, Bool, String, Enum, Message __VA_OPT__(,) __VA_ARGS__) #define FORMAT_CPP_TYPE_TITLECASE(fdesc, value, prefix, ...) _FORMAT_CPP_TYPE(fdesc, value, prefix, Int32, Int64, UInt32, UInt64, Float, Double, Bool, String, Enum, Message __VA_OPT__(,) __VA_ARGS__)
extern std::string current_process_type;
inline std::string generate_address() { inline std::string generate_address() {
std::filesystem::path tmpdir = std::filesystem::temp_directory_path(); std::filesystem::path tmpdir = std::filesystem::temp_directory_path();
std::filesystem::path sockpath = tmpdir / "looper_playback."; std::filesystem::path sockpath = tmpdir / std::filesystem::path("looper_" + current_process_type + ".");
const char *chars = "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._"; const char *chars = "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._";
for (size_t i = 0; i < 64; i++) { for (size_t i = 0; i < 64; i++) {
char chr = chars[rand() % strlen(chars)]; char chr = chars[rand() % strlen(chars)];
@ -84,7 +91,10 @@ class IPCClient {
using Stub = std::unique_ptr<typename C::Stub>; using Stub = std::unique_ptr<typename C::Stub>;
std::shared_ptr<grpc::ChannelInterface> channel; std::shared_ptr<grpc::ChannelInterface> channel;
IPCClient(std::string address) { IPCClient(std::string address) {
DEBUG.writefln("Connecting to '%s'...", address.c_str());
channel = grpc::CreateChannel(address, grpc::InsecureChannelCredentials()); channel = grpc::CreateChannel(address, grpc::InsecureChannelCredentials());
channel->WaitForConnected(gpr_time_from_seconds(30, gpr_clock_type::GPR_CLOCK_MONOTONIC));
DEBUG.writefln("Connection successful!");
} }
Stub client_stub() { Stub client_stub() {
@ -113,6 +123,7 @@ class IPCServer {
builder.AddListeningPort(address, grpc::InsecureServerCredentials()); builder.AddListeningPort(address, grpc::InsecureServerCredentials());
builder.RegisterService(service); builder.RegisterService(service);
server = std::shared_ptr<grpc::Server>(builder.BuildAndStart().release()); server = std::shared_ptr<grpc::Server>(builder.BuildAndStart().release());
DEBUG.writefln("Server listening on '%s'...", address.c_str());
} }
template<class SI> template<class SI>
void init() { void init() {
@ -182,6 +193,10 @@ class IPCChannel {
return client.has_value(); return client.has_value();
} }
Stub get_stub() { Stub get_stub() {
if (!has_client()) {
ERROR.writeln("Attempt to get client stub for nonexistant client!");
throw std::exception();
}
return get_client()->client_stub(); return get_client()->client_stub();
} }
ServerPtr get_server() { ServerPtr get_server() {
@ -201,9 +216,13 @@ class IPCChannel {
init_client(client); init_client(client);
} }
void construct_client(std::string client_address) { void construct_client(std::string client_address) {
if (this->client.has_value()) return; if (this->client.has_value()) {
Client *client = new Client(client_address); ERROR.writefln("Invalid attempt to construct client for address %s", client_address.c_str());
init_client(client); return;
} else {
DEBUG.writefln("Constructing client for address %s...", client_address.c_str());
}
init_client(client_address);
} }
/// @brief Constructs an IP channel with a newly-created server with a generated address. /// @brief Constructs an IP channel with a newly-created server with a generated address.
IPCChannel(ServiceConstructor custom_service_constructor) { IPCChannel(ServiceConstructor custom_service_constructor) {

View file

@ -78,6 +78,15 @@ inline size_t combine_hashes(std::initializer_list<size_t> hashes) {
} }
return std::hash<std::string>()(values); return std::hash<std::string>()(values);
} }
inline bool is_zeroes(void *ptr, size_t len) {
uint8_t *ptr8 = (uint8_t*)ptr;
for (size_t i = 0; i < len; i++) {
if (ptr8[i] != 0) {
return false;
}
}
return true;
}
int launch(std::vector<std::string> args); int launch(std::vector<std::string> args);
#ifdef MIN #ifdef MIN
#undef MIN #undef MIN