Temporary commit
This commit is contained in:
parent
73686be925
commit
ac2120aa2e
26 changed files with 349 additions and 95 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -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
|
||||
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <filesystem>
|
||||
#include "file_backend.hpp"
|
||||
#include <stddef.h>
|
||||
#include <log.hpp>
|
||||
#include <string.h>
|
||||
std::optional<uint64_t> 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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -12,19 +12,7 @@
|
|||
#include <string.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
|
||||
static const int16_t filter[512] = {
|
||||
|
|
|
@ -5,6 +5,18 @@
|
|||
#pragma once
|
||||
|
||||
#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)
|
||||
void audio_callback(void *userdata, Uint8 *stream, int len);
|
||||
|
|
|
@ -13,25 +13,28 @@ extern "C" {
|
|||
#include <file_backend.hpp>
|
||||
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;
|
||||
|
|
|
@ -46,6 +46,7 @@ struct ZsmCommand {
|
|||
};
|
||||
class ZsmBackend : public PlaybackBackend {
|
||||
File *file;
|
||||
std::vector<uint8_t> audio_buf;
|
||||
uint32_t loop_point;
|
||||
uint32_t pcm_offset;
|
||||
uint8_t fm_mask;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
};
|
||||
|
|
122
build-appimage.py
Executable file
122
build-appimage.py
Executable 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
5
build-appimage.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
pushd "$(dirname "$0")"
|
||||
. venv/bin/activate
|
||||
./build-appimage.py "$@"
|
||||
popd
|
|
@ -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
|
||||
|
|
3
build.sh
3
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"
|
||||
|
|
14
dbus.hpp
14
dbus.hpp
|
@ -100,12 +100,12 @@ class MprisAPI : public sdbus::AdaptorInterfaces<org::mpris::MediaPlayer2_adapto
|
|||
std::vector<track_id_t> 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<com::complecwaft::looper_adaptor, com::complecwaft::looper::Errors_adaptor, org::freedesktop::Application_adaptor>
|
||||
: public sdbus::AdaptorInterfaces<com::complecwaft::looper_adaptor, com::complecwaft::looper::Errors_adaptor, org::freedesktop::Application_adaptor>
|
||||
#endif
|
||||
{
|
||||
std::map<std::string, void*> 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<com::complecwaft::looper_proxy, com::complecwaft::looper::Errors_proxy, org::freedesktop::Application_proxy, sdbus::Peer_proxy>
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
// Cache
|
||||
double length, pitch, speed, tempo, volume;
|
||||
|
@ -201,7 +201,7 @@ class DBusAPISender : public Playback
|
|||
optional<std::string> 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
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
1
log.hpp
1
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);
|
||||
|
|
31
main.cpp
31
main.cpp
|
@ -140,8 +140,6 @@ extern "C" int looper_run_as_executable(std::vector<std::string> 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<std::string> args) {
|
|||
#endif
|
||||
return output;
|
||||
}
|
||||
std::string current_process_type;
|
||||
extern int looper_run_playback_process(std::vector<std::string> args);
|
||||
int main(int argc, char **argv) {
|
||||
CLI::App app{DESCRIPTION};
|
||||
std::string process_type = "normal";
|
||||
app.add_option<std::string, std::string>("--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<std::string> 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<std::string> 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<std::string>({"--help"}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
playback.cpp
10
playback.cpp
|
@ -1,5 +1,6 @@
|
|||
#include "playback.h"
|
||||
#include "SDL_mixer.h"
|
||||
#include "playback_backend.hpp"
|
||||
#include <SDL_audio.h>
|
||||
extern "C" {
|
||||
#include <vgmstream.h>
|
||||
|
@ -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;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <windows.h>
|
||||
#endif
|
||||
#include <google/protobuf/message.h>
|
||||
#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<char>());
|
||||
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<PlaybackStream> streams) {
|
|||
PlaybackProcess::PlaybackProcess(std::vector<std::string> 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<std::string> args) {
|
|||
};
|
||||
playback_process_channel = IPCChannel<PlaybackProcessService, HostProcess>(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<std::string> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@
|
|||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <stdint.h>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "thirdparty/CRC.hpp"
|
||||
#include "playback_backend.hpp"
|
||||
#include <grpc/grpc.h>
|
||||
|
@ -14,7 +16,6 @@
|
|||
#include <grpcpp/server_builder.h>
|
||||
#include <grpcpp/server_context.h>
|
||||
#include "rpc.hpp"
|
||||
#include <google/protobuf/stubs/status.h>
|
||||
#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<IPCChannel<HostProcess, PlaybackProcessService>> host_channel;
|
||||
std::optional<IPCChannel<PlaybackProcessService, HostProcess>> playback_process_channel;
|
||||
inline std::unique_ptr<PlaybackProcessService::Stub> get_stub() {
|
||||
|
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
@ -0,0 +1 @@
|
|||
lddwrap
|
20
resolve_library.py
Executable file
20
resolve_library.py
Executable 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
29
rpc.hpp
|
@ -2,8 +2,12 @@
|
|||
#include <algorithm>
|
||||
#include <any>
|
||||
#include <grpcpp/security/server_credentials.h>
|
||||
#include <grpcpp/server_builder.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/generated_message_reflection.h>
|
||||
#include <google/protobuf/message.h>
|
||||
|
@ -28,6 +32,8 @@
|
|||
#include <grpc++/server.h>
|
||||
#include <variant>
|
||||
#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, ...) \
|
||||
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<typename C::Stub>;
|
||||
std::shared_ptr<grpc::ChannelInterface> 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<grpc::Server>(builder.BuildAndStart().release());
|
||||
DEBUG.writefln("Server listening on '%s'...", address.c_str());
|
||||
}
|
||||
template<class SI>
|
||||
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) {
|
||||
|
|
11
util.hpp
11
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<size_t> hashes) {
|
|||
}
|
||||
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);
|
||||
#ifdef MIN
|
||||
#undef MIN
|
||||
|
|
Loading…
Reference in a new issue