diff --git a/.gitignore b/.gitignore index 4430d04..a6ea1db 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,10 @@ compile_commands.json flatpak-repo *.flatpak !build*.sh +!build*.py !build.gradle +venv +__pycache__ .cxx .gradle /sdl-android-project/app/jni diff --git a/.gitmodules b/.gitmodules index 50604f4..b92866b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,7 +30,7 @@ url = https://github.com/google/oboe.git [submodule "subprojects/protobuf"] path = subprojects/protobuf - url = /subprojects/protobuf + url = https://github.com/protocolbuffers/protobuf.git [submodule "subprojects/fmt"] path = subprojects/fmt url = https://github.com/fmtlib/fmt.git diff --git a/CMakeLists.txt b/CMakeLists.txt index bd3481d..2613d41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,7 +205,6 @@ macro(prefix_all) list(APPEND ${OUT_VAR} ${PREFIX}${ARG}) endforeach() endmacro() -find_package(Protobuf REQUIRED) find_package(gRPC CONFIG REQUIRED) set(_PROTOBUF_LIBPROTOBUF ${Protobuf_LIBRARY_RELEASE}) diff --git a/backends/playback/sdl_mixer_x/sdl_mixer_x.cpp b/backends/playback/sdl_mixer_x/sdl_mixer_x.cpp index fa96aa2..27348a7 100644 --- a/backends/playback/sdl_mixer_x/sdl_mixer_x.cpp +++ b/backends/playback/sdl_mixer_x/sdl_mixer_x.cpp @@ -2,6 +2,7 @@ #include #include "file_backend.hpp" #include +#include #include std::optional SDLMixerXBackend::get_max_samples() { return 100; @@ -38,7 +39,9 @@ void SDLMixerXBackend::load(const char *filename) { Mix_SetSoundFonts(NULL); } } + DEBUG.writefln("Opening file: %s", filename); file = open_file(filename); + DEBUG.writeln("Loading file..."); Mix_Music *output = Mix_LoadMUS_RW(get_sdl_file(this->file), 0); if (output == nullptr) { throw std::exception(); diff --git a/backends/playback/vgmstream/vgmstream.cpp b/backends/playback/vgmstream/vgmstream.cpp index 987da50..393ffa1 100644 --- a/backends/playback/vgmstream/vgmstream.cpp +++ b/backends/playback/vgmstream/vgmstream.cpp @@ -93,8 +93,6 @@ void VgmStreamBackend::switch_stream(int idx) { free(buf); } open = true; - spec.channels = vf->channels; - spec.freq = vf->sample_rate; } void VgmStreamBackend::cleanup() { streams.clear(); diff --git a/backends/playback/zsm/x16emu/audio.c b/backends/playback/zsm/x16emu/audio.c index 5488848..23b13bd 100644 --- a/backends/playback/zsm/x16emu/audio.c +++ b/backends/playback/zsm/x16emu/audio.c @@ -12,19 +12,7 @@ #include #include -#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 static const int16_t filter[512] = { diff --git a/backends/playback/zsm/x16emu/audio.h b/backends/playback/zsm/x16emu/audio.h index 9af1ddd..a6a2bb2 100644 --- a/backends/playback/zsm/x16emu/audio.h +++ b/backends/playback/zsm/x16emu/audio.h @@ -5,6 +5,18 @@ #pragma once #include +#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) void audio_callback(void *userdata, Uint8 *stream, int len); diff --git a/backends/playback/zsm/zsm_backend.cpp b/backends/playback/zsm/zsm_backend.cpp index 4440292..fdf0863 100644 --- a/backends/playback/zsm/zsm_backend.cpp +++ b/backends/playback/zsm/zsm_backend.cpp @@ -13,25 +13,28 @@ extern "C" { #include void ZsmBackend::load(const char *filename) { spec.format = AUDIO_S16SYS; + spec.samples = SAMPLES_PER_BUFFER; + spec.channels = 2; + spec.size = spec.samples * SAMPLE_BYTES; file = open_file(filename); char magic[2]; - file->read(magic, 2); + file->read(magic, 2, 1); if (magic[0] != 0x7a || magic[1] != 0x6d) { throw std::exception(); } uint8_t version; - file->read(&version, 1); + file->read(&version, 1, 1); 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); - 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); - file->read(&fm_mask, 1); - file->read(loop_point, 2); + file->read(&fm_mask, 1, 1); + file->read(loop_point, 2, 1); 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); - file->read(loop_point, 2); // Reserved. + file->read(loop_point, 2, 1); // Reserved. music_data_start = file->get_pos(); while (true) { ZsmCommand cmd = get_command(); @@ -100,13 +103,21 @@ size_t ZsmBackend::render(void *buf, size_t maxlen) { audio_step((int)(clocks)); } audio_render(); - audio_callback(nullptr, (Uint8*)(buf), sample_type_len * maxlen); - return maxlen * sample_type_len; + 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() { ZsmCommandId cmdid; uint8_t cmd_byte; - file->read(&cmd_byte, 1); + file->read(&cmd_byte, 1, 1); if (cmd_byte == 0x80) { cmdid = ZsmEOF; } else { @@ -128,7 +139,7 @@ ZsmCommand ZsmBackend::get_command() { return output; } else if (cmdid == PsgWrite) { uint8_t value; - file->read(&value, 1); + file->read(&value, 1, 1); output.psg_write.reg = cmd_byte & 0x3F; output.psg_write.val = value; } else if (cmdid == FmWrite) { @@ -139,11 +150,11 @@ ZsmCommand ZsmBackend::get_command() { for (uint8_t i = 0; i < pairs; i++) { output.fm_write.regs[i].reg = value[0]; output.fm_write.regs[i].val = value[1]; - file->read(value, 2); + file->read(value, 2, 1); } } else if (cmdid == ExtCmd) { 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 ch = ext_cmd_byte >> 6; output.extcmd.channel = ch; @@ -155,7 +166,7 @@ ZsmCommand ZsmBackend::get_command() { } for (size_t i = 0; i < bytes; i++) { uint8_t byte; - file->read(&byte, 1); + file->read(&byte, 1, 1); switch (ch) { case 0: { output.extcmd.pcm[i] = byte; diff --git a/backends/playback/zsm/zsm_backend.hpp b/backends/playback/zsm/zsm_backend.hpp index 934ffd2..d8d7bb2 100644 --- a/backends/playback/zsm/zsm_backend.hpp +++ b/backends/playback/zsm/zsm_backend.hpp @@ -46,6 +46,7 @@ struct ZsmCommand { }; class ZsmBackend : public PlaybackBackend { File *file; + std::vector audio_buf; uint32_t loop_point; uint32_t pcm_offset; uint8_t fm_mask; diff --git a/backends/ui/imgui/RendererBackend.h b/backends/ui/imgui/RendererBackend.h index 5a9cb84..98a1048 100644 --- a/backends/ui/imgui/RendererBackend.h +++ b/backends/ui/imgui/RendererBackend.h @@ -58,7 +58,7 @@ class RendererBackend { void SetWindowSize(int w, int h); void GetWindowsize(int *w, int *h); RendererBackend(); - ~RendererBackend(); + virtual ~RendererBackend(); friend void main_loop(); friend void backend_init(void *userdata); -}; \ No newline at end of file +}; diff --git a/build-appimage.py b/build-appimage.py new file mode 100755 index 0000000..cf9d64d --- /dev/null +++ b/build-appimage.py @@ -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() diff --git a/build-appimage.sh b/build-appimage.sh new file mode 100755 index 0000000..87ca27d --- /dev/null +++ b/build-appimage.sh @@ -0,0 +1,5 @@ +#!/bin/bash +pushd "$(dirname "$0")" +. venv/bin/activate +./build-appimage.py "$@" +popd \ No newline at end of file diff --git a/build-docker.sh b/build-docker.sh index e2b00a8..a60cf1e 100755 --- a/build-docker.sh +++ b/build-docker.sh @@ -9,7 +9,13 @@ on_err() { exit $code } 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 $? } trap on_err ERR @@ -31,6 +37,7 @@ build_cmake() { if [ "$first_arg" = "in-docker" ]; then ARGS=( "$@" ) cd /src + git config --global --add safe.directory /src build_cmake subprojects/protobuf "${ARGS[@]}" pushd subprojects/grpc git apply ../../grpc.patch || true diff --git a/build.sh b/build.sh index f897c0f..a680f62 100755 --- a/build.sh +++ b/build.sh @@ -24,8 +24,9 @@ run_command() { trap on_err ERR } trap on_err ERR +cd "$(dirname "$0")" mkdir -p build cd build run_command cmake .. -DDISABLE_GTK_UI=ON -DCMAKE_BUILD_TYPE=Debug run_command cmake --build . "$@" -cd .. +cd "$OLD_DIR" diff --git a/dbus.hpp b/dbus.hpp index 6d70a94..89e21a1 100644 --- a/dbus.hpp +++ b/dbus.hpp @@ -100,12 +100,12 @@ class MprisAPI : public sdbus::AdaptorInterfaces Tracks() override; bool CanEditTracks() override; MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api); - ~MprisAPI(); + virtual ~MprisAPI(); }; #endif class DBusAPI #ifdef DBUS_ENABLED - : public sdbus::AdaptorInterfaces + : public sdbus::AdaptorInterfaces #endif { std::map handles; @@ -184,13 +184,13 @@ class DBusAPI DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string objectPath, bool daemon); #endif DBusAPI(Playback *playback, bool daemon); - ~DBusAPI(); + virtual ~DBusAPI(); static DBusAPI *Create(Playback *playback, bool daemon = false); }; class DBusAPISender : public Playback #ifdef DBUS_ENABLED , public sdbus::ProxyInterfaces -#endif +#endif { // Cache double length, pitch, speed, tempo, volume; @@ -201,7 +201,7 @@ class DBusAPISender : public Playback optional last_error; // Handle for error handling. std::string handle; - + public: // Public API for creating this object, and checking if it is needed. /// @brief Checks if this is the only instance, by attempting creation and immediately deleting the created object. @@ -256,9 +256,9 @@ class DBusAPISender : public Playback protected: DBusAPISender(sdbus::IConnection &connection, std::string busName, std::string objectPath); public: - ~DBusAPISender(); + virtual ~DBusAPISender(); #else public: ~DBusAPISender() = default; #endif -}; \ No newline at end of file +}; diff --git a/file_backend.cpp b/file_backend.cpp index 60400c7..636fd28 100644 --- a/file_backend.cpp +++ b/file_backend.cpp @@ -27,9 +27,9 @@ void CFile::close() { fclose(file); 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; - return fread(ptr, 1, len, file); + return fread(ptr, size, len, file); } void CFile::seek(size_t pos, SeekType seek_type) { 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) { File *file = (File*)rwops->hidden.unknown.data1; - uint8_t *ptr8 = (uint8_t*)ptr; - 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; + return file->read(ptr, size, maxnum); } int rwops_close(SDL_RWops *rwops) { 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) { File *file = sf->file; file->seek(offset, SeekType::SET); - return file->read(dst, length); + return file->read(dst, 1, length); } static size_t sf_size(file_streamfile *sf) { File *file = sf->file; diff --git a/file_backend.hpp b/file_backend.hpp index 33bf696..1ef2da9 100644 --- a/file_backend.hpp +++ b/file_backend.hpp @@ -20,12 +20,17 @@ class File { const char *name; File(const char *fname); inline virtual void open(const char *fname) { name = fname; } - virtual void close() = 0; - virtual size_t read(void *ptr, size_t len) = 0; + inline virtual void close() { } + virtual size_t read(void *ptr, size_t size, size_t len) = 0; virtual void seek(size_t pos, SeekType seek_type) = 0; virtual size_t get_len(); 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 { protected: @@ -34,7 +39,7 @@ class CFile : public File { CFile(const char *fname); void open(const char *fname) 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; size_t get_pos() override; bool is_open() override; @@ -45,7 +50,7 @@ class HttpFile : public File { HttpFile(const char *url); void open(const char *url) 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; size_t get_pos(); bool is_open(); @@ -58,7 +63,7 @@ class AndroidFile : public File { AndroidFile(const char *fname); void open(const char *fname) 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; size_t get_pos() override; bool is_open() override; diff --git a/log.hpp b/log.hpp index 39e2fe8..b1143bc 100644 --- a/log.hpp +++ b/log.hpp @@ -46,6 +46,7 @@ namespace Looper::Log { void writesn(const char *msg, size_t n); void writes(std::string msg); virtual void writec(const char chr); + inline virtual ~LogStream() { } void vwritef(const char *fmt, va_list args); void writef(const char *fmt, ...); void vwritefln(const char *fmt, va_list args); diff --git a/main.cpp b/main.cpp index 14a9505..f5e7ed3 100644 --- a/main.cpp +++ b/main.cpp @@ -140,8 +140,6 @@ extern "C" int looper_run_as_executable(std::vector args) { } DEBUG.writeln("Initializing frontends..."); init_backends(); - DEBUG.writeln("Initializing playback backends..."); - init_playback_backends(); #ifdef DBUS_ENABLED ProxyGlueBackend *proxy_backend = nullptr; if ((disable_gui && !daemonize) || quit) { @@ -230,21 +228,28 @@ extern "C" int looper_run_as_executable(std::vector args) { #endif return output; } +std::string current_process_type; extern int looper_run_playback_process(std::vector args); int main(int argc, char **argv) { CLI::App app{DESCRIPTION}; std::string process_type = "normal"; app.add_option("--process-type", process_type); app.allow_extras(); - app.parse(argc, argv); - executable_path = argv[0]; - if (process_type == "playback") { - return looper_run_playback_process(app.remaining(false)); - } else if (process_type == "daemon") { - std::vector opts = app.remaining(false); - opts.push_back("-d"); - return looper_run_as_executable(opts); - } else { - return looper_run_as_executable(app.remaining(false)); + try { + app.parse(argc, argv); + executable_path = argv[0]; + current_process_type = "host"; + if (process_type == "playback") { + current_process_type = "playback"; + return looper_run_playback_process(app.remaining(false)); + } else if (process_type == "daemon") { + std::vector opts = 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({"--help"})); } -} \ No newline at end of file +} diff --git a/playback.cpp b/playback.cpp index 74818a9..1318f65 100644 --- a/playback.cpp +++ b/playback.cpp @@ -1,5 +1,6 @@ #include "playback.h" #include "SDL_mixer.h" +#include "playback_backend.hpp" #include extern "C" { #include @@ -82,7 +83,7 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) { return; } 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) { if (process == nullptr) { return; @@ -111,6 +112,8 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) { sbuf[i] *= real_volume; } st->putSamples(sbuf, new_bufsize / unit); + } else { + return; } } 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); if (sdl_stream == nullptr) { 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_signal(PlaybackSignalErrorOccurred); } @@ -174,6 +177,7 @@ void PlaybackInstance::Load(const char *file, int idx) { bufsize = 0; } delete backend_spec_proxy; + playback_ready.store(true); } else { ERROR.writeln("Failed to detect valid playback backend for file!"); set_error("Failed to detect valid backend for file."); @@ -233,7 +237,7 @@ void PlaybackInstance::InitLoopFunction() { AUDIO_F32SYS; #endif desired.freq = 48000; - desired.samples = 1024; + desired.samples = 100; desired.channels = 2; desired.callback = PlaybackInstance::SDLCallback; desired.userdata = this; diff --git a/playback_process.cpp b/playback_process.cpp index 2f15ca9..6f81a47 100644 --- a/playback_process.cpp +++ b/playback_process.cpp @@ -29,6 +29,7 @@ #include #endif #include +#include "util.hpp" using namespace google::protobuf; int sndfd; int rcvfd; @@ -80,15 +81,22 @@ void print_ipc_message(const google::protobuf::Message &msg, size_t level) { auto &unknown_fields = reflect->GetUnknownFields(msg); 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) { size_t maxlen = cmd->len(); void *ptr = malloc(maxlen); size_t len = cur_backend->render(ptr, maxlen); - std::string buf = std::string(len, ' ', std::allocator()); - memcpy(buf.data(), ptr, len); - free(ptr); + if (len == 0) { + DEBUG.writeln("Didn't get any audio when rendering"); + } else if (is_zeroes(ptr, len)) { + DEBUG.writeln("Buffer was only zeroes!"); + } auto output = new RenderResponse(); - output->set_data(buf); + output->set_data((const char*)ptr, len); + free(ptr); output->set_len(len); response->set_allocated_output(output); 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) { process->host_channel.value().construct_client(value->value()); - process->done = true; + process->started.notify_all(); return grpc::Status::OK; } 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 idx = cmd->idx(); for (auto &backend : PlaybackBackendHelper()) { + DEBUG.writefln("Trying backend: %s", backend.second->get_name().c_str()); try { backend.second->init(filename.c_str(), idx); } catch (std::exception e) { + DEBUG.writeln("Cleaning up backend."); backend.second->cleanup(); continue; } cur_backend = backend.second; + DEBUG.writefln("Using backend: %s", backend.second->get_name().c_str()); break; } if (cur_backend == nullptr) { @@ -301,6 +312,7 @@ grpc::Status PlaybackProcessServiceImpl::Init(grpc::ServerContext *ctx, const In response->set_allocated_err(maybe_error); process->done = true; 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; @@ -347,7 +359,12 @@ StreamList serialize_stream_list(std::vector streams) { PlaybackProcess::PlaybackProcess(std::vector args) { done = false; is_playback_process = true; + Looper::Log::init_logging(); init_playback_backends(); + DEBUG.writeln("Playback backends: "); + for (auto &backend : PlaybackBackendHelper()) { + DEBUG.writefln(" - %s", backend.second->get_id().c_str()); + } init_audio_data(); std::string address = args[0]; auto mk_service = [this]() -> grpc::Service* { @@ -357,6 +374,7 @@ PlaybackProcess::PlaybackProcess(std::vector args) { }; playback_process_channel = IPCChannel(mk_service); playback_process_channel.value().construct_client(address); + DEBUG.writefln("Host process address: %s", address.c_str()); { StringProperty property; property.set_value(playback_process_channel.value().get_server()->get_address()); @@ -364,7 +382,6 @@ PlaybackProcess::PlaybackProcess(std::vector args) { MaybeError response; playback_process_channel.value().get_stub()->SetAddress(&ctx, property, &response); } - Looper::Log::init_logging(); } PlaybackProcess::PlaybackProcess(std::string filename, int idx) { done = false; @@ -383,9 +400,17 @@ PlaybackProcess::PlaybackProcess(std::string filename, int idx) { new_args.push_back("playback"); new_args.push_back(address); pid = launch(new_args); - while (!done) { - std::this_thread::yield(); + std::thread process_check_thread(std::mem_fn(&PlaybackProcess::threadfunc), this); + 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; InitCommand cmd; cmd.set_filename(filename); @@ -502,9 +527,15 @@ size_t PlaybackProcess::render(void *buf, size_t maxlen) { RenderResponseOrError response; get_stub()->Render(&ctx, rend_cmd, &response); if (response.has_err()) { + ERROR.writefln("Error rendering audio: %s", response.err().id().c_str()); return 0; } 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()); return data.length(); } @@ -519,3 +550,19 @@ PlaybackProcess::~PlaybackProcess() { get_stub()->Quit(&ctx, quit_cmd, &output); 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(); + } +} diff --git a/playback_process.hpp b/playback_process.hpp index 233afec..8f98e4f 100644 --- a/playback_process.hpp +++ b/playback_process.hpp @@ -3,9 +3,11 @@ #include #include #include +#include #include #include #include +#include #include "thirdparty/CRC.hpp" #include "playback_backend.hpp" #include @@ -14,7 +16,6 @@ #include #include #include "rpc.hpp" -#include #include "ipc/common.pb.h" #include "ipc/internal.pb.h" #include "ipc/internal.grpc.pb.h" @@ -51,8 +52,10 @@ class PlaybackProcess { friend class PlaybackProcessServiceImpl; void threadfunc(); int pid; + std::mutex start_mutex; + std::condition_variable started; bool is_playback_process = false; - bool done; + std::atomic_bool done; std::optional> host_channel; std::optional> playback_process_channel; inline std::unique_ptr get_stub() { diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..87ffdbe --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +lddwrap \ No newline at end of file diff --git a/resolve_library.py b/resolve_library.py new file mode 100755 index 0000000..420e656 --- /dev/null +++ b/resolve_library.py @@ -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) \ No newline at end of file diff --git a/rpc.hpp b/rpc.hpp index 97faaf1..a8d0538 100644 --- a/rpc.hpp +++ b/rpc.hpp @@ -2,8 +2,12 @@ #include #include #include +#include #include -#include +#include +#include +#include +#include #include #include #include @@ -28,6 +32,8 @@ #include #include #include +#include "log.hpp" +using namespace std::literals; #define _FORMAT_CPP_TYPE(fdesc, value, prefix, int32, int64, uint32, uint64, float, double, bool, string, enum, message, ...) \ switch (fdesc->cpp_type()) { \ 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__) +extern std::string current_process_type; inline std::string generate_address() { 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._"; for (size_t i = 0; i < 64; i++) { char chr = chars[rand() % strlen(chars)]; @@ -84,7 +91,10 @@ class IPCClient { using Stub = std::unique_ptr; std::shared_ptr channel; IPCClient(std::string address) { + DEBUG.writefln("Connecting to '%s'...", address.c_str()); 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() { @@ -113,6 +123,7 @@ class IPCServer { builder.AddListeningPort(address, grpc::InsecureServerCredentials()); builder.RegisterService(service); server = std::shared_ptr(builder.BuildAndStart().release()); + DEBUG.writefln("Server listening on '%s'...", address.c_str()); } template void init() { @@ -182,6 +193,10 @@ class IPCChannel { return client.has_value(); } Stub get_stub() { + if (!has_client()) { + ERROR.writeln("Attempt to get client stub for nonexistant client!"); + throw std::exception(); + } return get_client()->client_stub(); } ServerPtr get_server() { @@ -201,9 +216,13 @@ class IPCChannel { init_client(client); } void construct_client(std::string client_address) { - if (this->client.has_value()) return; - Client *client = new Client(client_address); - init_client(client); + if (this->client.has_value()) { + ERROR.writefln("Invalid attempt to construct client for address %s", client_address.c_str()); + 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. IPCChannel(ServiceConstructor custom_service_constructor) { diff --git a/util.hpp b/util.hpp index 69d8f8f..fd1f518 100644 --- a/util.hpp +++ b/util.hpp @@ -58,7 +58,7 @@ inline std::string to_string_with_decimals(double value, unsigned decimals) { num_text = num_text.substr(0, found); return num_text; } - int chars_after_decimal = num_text.size() - found - 1; + int chars_after_decimal = num_text.size() - found - 1; if (chars_after_decimal > decimals) { num_text = num_text.substr(0, found + decimals + 1); } else { @@ -78,6 +78,15 @@ inline size_t combine_hashes(std::initializer_list hashes) { } return std::hash()(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 args); #ifdef MIN #undef MIN