From 80ff7bdcf32e3a4ea4605fd79a75062606d33258 Mon Sep 17 00:00:00 2001 From: Zachary Hall Date: Mon, 14 Oct 2024 21:27:16 -0700 Subject: [PATCH] Mostly fix ZSM support --- .codelite/find-in-files.json | 13 + .codelite/neko-player-someone.tags | Bin 0 -> 98304 bytes .codelite/neko-player.session | 165 +++++++++ .codelite/neko-player.workspace.someone | 4 + .codelite/valgrind.memcheck.supp | 0 .gitignore | 3 + .gitmodules | 3 + CMakeLists.txt | 42 ++- assets/test.fur | Bin 0 -> 747 bytes assets/test.zsm | Bin 0 -> 672 bytes backends/playback/sdl_mixer_x/sdl_mixer_x.cpp | 9 - backends/playback/zsm/CMakeLists.txt | 4 +- backends/playback/zsm/x16emu/audio.c | 312 ------------------ backends/playback/zsm/x16emu/audio.h | 11 +- backends/playback/zsm/x16emu/ymglue.cpp | 4 +- backends/playback/zsm/x16emu/ymglue.h | 2 +- backends/playback/zsm/zsm_backend.cpp | 279 +++++++++++----- backends/playback/zsm/zsm_backend.hpp | 106 +++++- backends/ui/imgui/main.cpp | 9 +- backends/ui/imgui/main.h | 1 + config.cmake.h.in | 2 +- include/playback_backend.h | 11 + log.cpp | 2 +- log.hpp | 17 + main.cpp | 16 +- neko-player.project | 232 +++++++++++++ neko-player.sh | 5 + neko-player.workspace | 14 + playback.cpp | 53 +-- playback_backend.hpp | 8 + playback_process.cpp | 152 ++++++--- playback_process.hpp | 5 + protobuf_c_optional-1.patch | 75 +++++ subprojects/protobuf-c | 1 + util.cpp | 84 +++++ util.hpp | 139 ++++++++ 36 files changed, 1252 insertions(+), 531 deletions(-) create mode 100644 .codelite/find-in-files.json create mode 100644 .codelite/neko-player-someone.tags create mode 100644 .codelite/neko-player.session create mode 100644 .codelite/neko-player.workspace.someone create mode 100644 .codelite/valgrind.memcheck.supp create mode 100644 assets/test.fur create mode 100644 assets/test.zsm delete mode 100644 backends/playback/zsm/x16emu/audio.c create mode 100644 include/playback_backend.h create mode 100644 neko-player.project create mode 100644 neko-player.sh create mode 100644 neko-player.workspace create mode 100644 protobuf_c_optional-1.patch create mode 160000 subprojects/protobuf-c diff --git a/.codelite/find-in-files.json b/.codelite/find-in-files.json new file mode 100644 index 0000000..cf87826 --- /dev/null +++ b/.codelite/find-in-files.json @@ -0,0 +1,13 @@ +{ + "find_what_array": [], + "find_what": "", + "replace_with_array": [], + "replace_with": "", + "files_array": ["*.cpp;*.cc;*.c;*.hpp;*.h;CMakeLists.txt;*.plist;*.rc", "*.c;*.cpp;*.cxx;*.cc;*.h;*.hpp;*.inc;*.mm;*.m;*.xrc"], + "files": "*.cpp;*.cc;*.c;*.hpp;*.h;CMakeLists.txt;*.plist;*.rc", + "where_array": [""], + "where": "", + "encoding": "ISO-8859-1", + "flags": 4099, + "files_scanner_flags": 1 +} \ No newline at end of file diff --git a/.codelite/neko-player-someone.tags b/.codelite/neko-player-someone.tags new file mode 100644 index 0000000000000000000000000000000000000000..5254ea5370d4c0bab2f14637bcd303c95d507b94 GIT binary patch literal 98304 zcmeI)Z*S8^9Ki9IKS^6C|AVQfX*X4^AcG$~0s=q40})(sYI(mF1q zNZS-N@rw3f5-($KfQOs5XM3`TyE{9MZMtdV8P+$V#pm<={JYPc&p9Dce^_=rTWodR zot7tVWX@y^BXduPOeXV2{#=qjgO4c>jtvU(ZzFR2Z-;L(v#<8Xv-_Ee<1aF&-kx&C ze;apC{&MohiQi9HW8UcBqi;ul7`;C7>xegUGxt~S=Ug{8m3^1}{`kA>9od8r0tg_G zGJ$6kIrG|Oqd)3&Htm;QtKDn9u-%^1?KJm0&R+DyrTLmwY*=EgQhK;%iBe_3dMu(B z=oX^eiJhDd!WF(Tv*zW?#(~#*x@||BMUO8A_h=OFm#tVFF$g4jp6hhlV%K$cTJC{( zZXXnGUCNpdE*lx$iQe9}e33L;`(9U-27p#2NoSZP8=vxG_E5#MdKfDkN8mU&% z{Ni+{wPP0+FG$9`(b8cW#ie?)vA$~6tVPwY^RXz=+QG0)dk4F=Yi||i&&zc3K|1=% z)nd)6G*p;#?}za#!zjBg*Y0?QyXUgzXL)07n9ORiv8+OzJuF0HITFBoR=9UIYkr=xua&?ZVwN$pjRs3UK=U0IDi%uQ8vT&YIUIjqh{Pj&+v1}@H+SMxSmhX zb?{)qs7KR3JQ+lcJ*<(dRLJPGx_vThewsJxI+~jD&C|)P#*y?uA!lIW>p+EBe-YCaR>>y+;r0M0iCQM&&RgRInMb+wz{xh@R8# zw7h*+o(|@_Jg|3mw_9>y*tEBtj^oKCuF$WH=02IskG$^6HKpCQU3J%{)9J}0br{^g zQ7bJiSv7w>@@qwLu_4ctDs`*Y5Y>vV6N}ZFu!{4`qE>w*9xYoHQL!G)>ZvO3im5yP zkbgBL?psTxicp^*LaEZIMyHUNc4TkVK+DYi*(AO$me;Jhn69jq%QHH%U$5>VXWMP7 zve5s0#P3i(+M!KbuDE*5J*-21ZP1|wt8B?N{?HB$lMb$0tW{THNy{$Rtmv6pKX=*r zYI#8>sd8M-$*0~l+Tm2IH`SA=*V&Q7MqJnEYt)eIlE0+(k7RS^)RfVm*DsaWq8lsZ z2L0Brl~|RSE-lDGXjp21S8Jt}Vr^YKu+~L!tx+viWWyD?D$WQs$%0ppKQ2L`(7$y& zXHHBQuS+@!|Dp1KXJfbZ&wbGSww;bGoQ`M9DH^-`jBt9*t^Ll1n$FE_=efRYM!2?| z>l^ltT%^OK!-ry!xpypQnx@fT(ur$V{p)xvZGGy)vTpUfrmN;%+uj_cU-uklqIfix zN1{@{k7s1d!t47+&dleHpV~U6ykAfK9Er77XFe$X*vyO11hwXc-E3{h{Ci=ik)>YAbp~LI42-5I_I{1Q0*~0R#|0AR)m1 zKLJ7!0R#|0009ILKmY**5I_Kd6bi8aPvM}_B?J&a009ILKmY**5I_I{1QG)5{}Uh- z5kLR|1Q0*~0R#|0009ILNTC4x{}c`?T|xi>1Q0*~0R#|0009ILKp-K&{yzai5dj1c zKmY**5I_I{1Q0*~ffNd`|4-qd(j^2CKmY**5I_I{1Q0*~0R$2P?Ee!W6cIoG0R#|0 z009ILKmY**5J;f_`~MUUDqTVV0R#|0009ILKmY**5I`Uy!2Ul0LJVitpwcA-5I_I{1Q0*~0R#|0009IN0_^`2AQTZm009ILKmY**5I_I{ z1Q1A}0Q>(G4k}$j009ILKmY**5I_I{1Q0+VAuuk>#_2yZ@(&*b5I_I{1Q0*~0R#|0 z009ILNP)n)?vbnW-A%jfc(!;k_vP&M*_-O$|BcgsWaJ+{2q1s}0tg_000IagfB*sr tAn@@ETsV5wNN)eT?EgRhfu#osAb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.codelite/neko-player.workspace.someone b/.codelite/neko-player.workspace.someone new file mode 100644 index 0000000..5f6074a --- /dev/null +++ b/.codelite/neko-player.workspace.someone @@ -0,0 +1,4 @@ + + + + diff --git a/.codelite/valgrind.memcheck.supp b/.codelite/valgrind.memcheck.supp new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 32aa8ee..6657670 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ __pycache__ .gradle /sdl-android-project/app/jni wasm-rt/wasmer +cmake-build-*/ +.ctagsd +*.rej diff --git a/.gitmodules b/.gitmodules index a9c211b..dd19c3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -58,3 +58,6 @@ [submodule "subprojects/googletest"] path = subprojects/googletest url = https://github.com/google/googletest.git +[submodule "subprojects/protobuf-c"] + path = subprojects/protobuf-c + url = https://github.com/protobuf-c/protobuf-c.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cf4a15..3abeae2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,27 +220,44 @@ macro(prefix_all) endforeach() endmacro() +include(FetchContent) +execute_process(COMMAND patch -u -Np1 -i ${CMAKE_SOURCE_DIR}/protobuf_c_optional-1.patch WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/subprojects/protobuf-c/) +set(BUILD_PROTOC ON CACHE BOOL "" FORCE) +set(BUILD_TESTS OFF CACHE BOOL "" FORCE) +add_subdirectory(${CMAKE_SOURCE_DIR}/subprojects/protobuf-c/build-cmake) if(BUILD_PROTOBUF) set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf) else() set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf) endif() find_program(_PROTOBUF_PROTOC protoc) +macro(run_protoc) + cmake_parse_arguments(PROTOC "" "OUTDIR;SOURCE;OUTVAR" "INCPATH" ${ARGV}) + get_filename_component(_run_protoc_src_stem "${PROTOC_SOURCE}" NAME_WE) + make_directory(${PROTOC_OUTDIR}) + set(_run_protoc_dst_base ${PROTOC_OUTDIR}/${_run_protoc_src_stem}.pb) + set(_run_protoc_incpath_expanded) + foreach (INCPATH_IDX ${PROTOC_INCPATH}) + list(APPEND _run_protoc_incpath_expanded -I${INCPATH_IDX}) + endforeach() + set(_run_protoc_output ${_run_protoc_dst_base}.cc ${_run_protoc_dst_base}.h)# ${_run_protoc_dst_base}-c.c ${_run_protoc_dst_base}-c.h) + unset(_run_protoc_dst_base) + add_custom_command(OUTPUT ${_run_protoc_output} COMMAND ${_PROTOBUF_PROTOC} --cpp_out ${PROTOC_OUTDIR} ${_run_protoc_incpath_expanded} ${PROTOC_SOURCE})# --plugin=${CMAKE_BINARY_DIR}/subprojects/protobuf-c/build-cmake/protoc-gen-c --c_out ${PROTOC_OUTDIR}) + unset(_run_protoc_incpath_expanded) + unset(_run_protoc_src_stem) + set(${PROTOC_OUTVAR} ${_run_protoc_output}) + unset(_run_protoc_output) +endmacro() + function(grpc_proto) cmake_parse_arguments(GRPC_PROTO "" "TARGET" "SOURCES" ${ARGV}) foreach (GRPC_PROTO_SOURCE ${GRPC_PROTO_SOURCES}) - set(GRPC_PROTO_SRCS) - set(GRPC_PROTO_HDRS) get_filename_component(src_stem "${GRPC_PROTO_SOURCE}" NAME_WE) get_filename_component(src_path "${GRPC_PROTO_SOURCE}" DIRECTORY) cmake_path(RELATIVE_PATH src_path BASE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE src_rel_path) set(src_out ${CMAKE_CURRENT_BINARY_DIR}/${src_rel_path}) - message("Output directory: ${src_out}") - list(APPEND GRPC_PROTO_SRCS ${src_out}/${src_stem}.pb.cc) - list(APPEND GRPC_PROTO_HDRS ${src_out}/${src_stem}.pb.h) - make_directory(${src_out}) - execute_process(COMMAND ${_PROTOBUF_PROTOC} --cpp_out "${src_out}" -I "${src_path}" "${GRPC_PROTO_SOURCE}") + run_protoc(OUTDIR ${src_out} SOURCE ${GRPC_PROTO_SOURCE} INCPATH ${src_path} OUTVAR GRPC_PROTO_SRCS) target_sources(${GRPC_PROTO_TARGET} PRIVATE ${GRPC_PROTO_SRCS}) target_include_directories(${GRPC_PROTO_TARGET} PRIVATE ${src_out}) endforeach() @@ -264,11 +281,14 @@ prefix_all(LIBRARY_SOURCES playback_process.cpp base85.cpp ) +run_protoc(OUTDIR ${CMAKE_BINARY_DIR}/google/protobuf SOURCE google/protobuf/any.proto OUTVAR _src) add_library(liblooper STATIC ${LIBRARY_SOURCES}) + if(FOR_WASMER) target_compile_definitions(liblooper PUBLIC FOR_WASMER) endif() -grpc_proto(TARGET liblooper SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ipc/internal.proto ${CMAKE_CURRENT_SOURCE_DIR}/ipc/common.proto) +grpc_proto(TARGET liblooper SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ipc/common.proto) +grpc_proto(TARGET liblooper SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ipc/internal.proto) target_include_directories(liblooper PUBLIC ${INC}) target_include_directories(liblooper PUBLIC) set(JSONCPP_TARGET PkgConfig::jsoncpp) @@ -415,9 +435,9 @@ endmacro() set(ENABLED_UIS ) set(ENABLED_PLAYBACK_BACKENDS ) ui_backend_subdir(NAME "IMGUI" READABLE_NAME "Dear ImGui" SUBDIR backends/ui/imgui) -if (NOT (DEFINED EMSCRIPTEN OR DEFINED ANDROID_NDK)) - ui_backend_subdir(NAME "GTK" READABLE_NAME "GTK4" SUBDIR backends/ui/gtk) -endif() +#if (NOT (DEFINED EMSCRIPTEN OR DEFINED ANDROID_NDK)) + #ui_backend_subdir(NAME "GTK" READABLE_NAME "GTK4" SUBDIR backends/ui/gtk) +#:endif() playback_backend_subdir(NAME "VGMSTREAM" READABLE_NAME "VgmStream" SUBDIR backends/playback/vgmstream) playback_backend_subdir(NAME "SDL_MIXER_X" READABLE_NAME "SDL Mixer X" SUBDIR backends/playback/sdl_mixer_x) playback_backend_subdir(NAME "ZSM" READABLE_NAME "ZSM" SUBDIR backends/playback/zsm) diff --git a/assets/test.fur b/assets/test.fur new file mode 100644 index 0000000000000000000000000000000000000000..eb2a054e23649dabbb6c63bf8f630518848e9980 GIT binary patch literal 747 zcmVY#&zE}A%KFb)p7I5^0ni9yXx92|6UaL}c`U!kcl2^bRw4}D(ly*%&f zy{D)2jMnDMQ>TlW(rlqNQ|wt4k&LuPE%Yy^QF(Q=%39-fFSdG6O0uL|AmSgZQ zW9#<+F+L25Wf?s(rg_bn3RdMQcn6|hkzp_nCc#Z`4?G4hz#H%pd<8#&-zSm;yFf1( z0At_;m6MZEk?gVjp5~p~ zGh&wQvPMN?PNS+((^%A4!Ywb?TQ&2qHJ_|MdFxEqqBW)N4tr8dr!)#0MU68W(;DYA zW_U_NQ(8i2hR(>e$+XF|$@GxvA=5*qmrO62UNU`T`pEQ==_k`qrk~6JnE^5bWCqC$ zk{Kj3L}rN05Sd{z!(@iZjF1^2GeTyR%qW>rGGk=M$c&L0Co@iFoJ@yIhfIge1euAZ z+1Sc&D+UrC$pW*4l+B)>>x8qW|GV# dnJF?;WTwdMB(syuPBPPErpZjJ`5SA8ZW>$^V4?s3 literal 0 HcmV?d00001 diff --git a/assets/test.zsm b/assets/test.zsm new file mode 100644 index 0000000000000000000000000000000000000000..a0f9026538db8eaf41e692dc8609c2094871437d GIT binary patch literal 672 zcmb=*Wt3(B0Y+v98zAK@!5~>L`QM?5k%3_!BNNkeW`#zk|NTs#8kwFnGF@wAI?>2< zpoc+(k(uc^Hi=T~!Yi>0%P_KFmzaxPSdWnvyF@Kmcs{x_!9HM#XJur2&JOYi8^|YY zAiuDIe8UFv4;#owY%o8ubg_Z_g;ycak1V?gD+BtOuD5+679xLTL{u?FFTMp|l@I K#Rf>~YXAVF!38=1 literal 0 HcmV?d00001 diff --git a/backends/playback/sdl_mixer_x/sdl_mixer_x.cpp b/backends/playback/sdl_mixer_x/sdl_mixer_x.cpp index fdb333e..192f874 100644 --- a/backends/playback/sdl_mixer_x/sdl_mixer_x.cpp +++ b/backends/playback/sdl_mixer_x/sdl_mixer_x.cpp @@ -91,9 +91,6 @@ size_t SDLMixerXBackend::render(void *buf, size_t maxlen) { open = false; return 0; } - if (initial) { - seek(0.0); - } size_t i = 0; size_t bytes_per_iter = maxlen; if (bytes_per_iter > spec.size) { @@ -112,12 +109,6 @@ size_t SDLMixerXBackend::render(void *buf, size_t maxlen) { mixer(NULL, (Uint8*)(buf) + i, bytes_per_iter); i += bytes_per_iter; } - if (get_position() > (((double)maxlen) / ((double)spec.freq))) { - if (initial) { - seek(0.0); - } - initial = false; - } return i; } double SDLMixerXBackend::get_position() { diff --git a/backends/playback/zsm/CMakeLists.txt b/backends/playback/zsm/CMakeLists.txt index 7d88576..f6f5cd5 100644 --- a/backends/playback/zsm/CMakeLists.txt +++ b/backends/playback/zsm/CMakeLists.txt @@ -1,6 +1,8 @@ set(X16_DIR ${CMAKE_CURRENT_SOURCE_DIR}/x16emu) set(YMFM_DIR ${X16_DIR}/ymfm/src) -set(BACKEND_ZSM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/zsm_backend.cpp ${X16_DIR}/audio.c ${X16_DIR}/vera_pcm.c ${X16_DIR}/vera_psg.c ${X16_DIR}/ymglue.cpp ${YMFM_DIR}/ymfm_adpcm.cpp ${YMFM_DIR}/ymfm_opl.cpp ${YMFM_DIR}/ymfm_opm.cpp ${YMFM_DIR}/ymfm_pcm.cpp) +set(BACKEND_ZSM_SRC ${CMAKE_CURRENT_SOURCE_DIR}/zsm_backend.cpp ${X16_DIR}/vera_pcm.c ${X16_DIR}/vera_psg.c ${X16_DIR}/ymglue.cpp ${YMFM_DIR}/ymfm_adpcm.cpp ${YMFM_DIR}/ymfm_opl.cpp ${YMFM_DIR}/ymfm_opm.cpp ${YMFM_DIR}/ymfm_pcm.cpp) set(BACKEND_ZSM_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${YMFM_DIR} ${X16_DIR}) add_playback_backend(zsm_backend ${BACKEND_ZSM_SRC}) target_include_directories(zsm_backend PRIVATE ${BACKEND_ZSM_INC}) +find_package(OpenMP) +target_link_libraries(zsm_backend PUBLIC OpenMP::OpenMP_CXX) diff --git a/backends/playback/zsm/x16emu/audio.c b/backends/playback/zsm/x16emu/audio.c deleted file mode 100644 index 28577df..0000000 --- a/backends/playback/zsm/x16emu/audio.c +++ /dev/null @@ -1,312 +0,0 @@ -// Commander X16 Emulator -// Copyright (c) 2020 Frank van den Hoef -// All rights reserved. License: 2-clause BSD -#define IN_AUDIO -#include "audio.h" -#include "glue.h" -#include "vera_psg.h" -#include "vera_pcm.h" -#include "ymglue.h" -#include -#include -#include -#include - - - -// windowed sinc -static const int16_t filter[512] = { - 32767,32765,32761,32755,32746,32736,32723,32707,32690,32670,32649,32625,32598,32570,32539,32507, - 32472,32435,32395,32354,32310,32265,32217,32167,32115,32061,32004,31946,31885,31823,31758,31691, - 31623,31552,31479,31404,31327,31248,31168,31085,31000,30913,30825,30734,30642,30547,30451,30353, - 30253,30151,30048,29943,29835,29726,29616,29503,29389,29273,29156,29037,28916,28793,28669,28544, - 28416,28288,28157,28025,27892,27757,27621,27483,27344,27204,27062,26918,26774,26628,26481,26332, - 26182,26031,25879,25726,25571,25416,25259,25101,24942,24782,24621,24459,24296,24132,23967,23801, - 23634,23466,23298,23129,22959,22788,22616,22444,22271,22097,21923,21748,21572,21396,21219,21042, - 20864,20686,20507,20328,20148,19968,19788,19607,19426,19245,19063,18881,18699,18517,18334,18152, - 17969,17786,17603,17420,17237,17054,16871,16688,16505,16322,16139,15957,15774,15592,15409,15227, - 15046,14864,14683,14502,14321,14141,13961,13781,13602,13423,13245,13067,12890,12713,12536,12360, - 12185,12010,11836,11663,11490,11317,11146,10975,10804,10635,10466,10298,10131, 9964, 9799, 9634, - 9470, 9306, 9144, 8983, 8822, 8662, 8504, 8346, 8189, 8033, 7878, 7724, 7571, 7419, 7268, 7118, - 6969, 6822, 6675, 6529, 6385, 6241, 6099, 5958, 5818, 5679, 5541, 5405, 5269, 5135, 5002, 4870, - 4739, 4610, 4482, 4355, 4229, 4104, 3981, 3859, 3738, 3619, 3500, 3383, 3268, 3153, 3040, 2928, - 2817, 2708, 2600, 2493, 2388, 2284, 2181, 2079, 1979, 1880, 1783, 1686, 1591, 1498, 1405, 1314, - 1225, 1136, 1049, 963, 879, 795, 714, 633, 554, 476, 399, 323, 249, 176, 105, 34, - -34, -102, -168, -234, -298, -361, -422, -482, -542, -599, -656, -712, -766, -819, -871, -922, - -971,-1020,-1067,-1113,-1158,-1202,-1244,-1286,-1326,-1366,-1404,-1441,-1477,-1512,-1546,-1579, - -1611,-1642,-1671,-1700,-1728,-1755,-1781,-1806,-1830,-1852,-1874,-1896,-1916,-1935,-1953,-1971, - -1987,-2003,-2018,-2032,-2045,-2058,-2069,-2080,-2090,-2099,-2108,-2116,-2123,-2129,-2134,-2139, - -2143,-2147,-2150,-2152,-2153,-2154,-2154,-2154,-2153,-2151,-2149,-2146,-2143,-2139,-2135,-2130, - -2124,-2118,-2112,-2105,-2098,-2090,-2082,-2073,-2064,-2054,-2045,-2034,-2024,-2012,-2001,-1989, - -1977,-1965,-1952,-1939,-1926,-1912,-1898,-1884,-1870,-1855,-1840,-1825,-1810,-1794,-1778,-1762, - -1746,-1730,-1714,-1697,-1680,-1663,-1646,-1629,-1612,-1595,-1577,-1560,-1542,-1525,-1507,-1489, - -1471,-1453,-1435,-1418,-1400,-1382,-1364,-1346,-1328,-1310,-1292,-1274,-1256,-1238,-1220,-1203, - -1185,-1167,-1150,-1132,-1115,-1097,-1080,-1063,-1046,-1029,-1012, -995, -978, -962, -945, -929, - -912, -896, -880, -864, -849, -833, -817, -802, -787, -772, -757, -742, -727, -713, -699, -684, - -670, -656, -643, -629, -616, -603, -589, -577, -564, -551, -539, -526, -514, -502, -491, -479, - -468, -456, -445, -434, -423, -413, -402, -392, -381, -371, -361, -352, -342, -333, -323, -314, - -305, -296, -288, -279, -270, -262, -254, -246, -238, -230, -222, -215, -207, -200, -193, -186, - -179, -172, -165, -158, -152, -145, -139, -133, -127, -120, -114, -108, -103, -97, -91, -85, - -80, -74, -69, -63, -58, -53, -47, -42, -37, -32, -27, -22, -17, -12, -7, -2 -}; - -static int16_t * buffer; -uint32_t buffer_size = 0; -static uint32_t rdidx = 0; -static uint32_t wridx = 0; -static uint32_t buffer_written = 0; -static uint32_t vera_samp_pos_rd = 0; -static uint32_t vera_samp_pos_wr = 0; -static uint32_t vera_samp_pos_hd = 0; -static uint32_t ym_samp_pos_rd = 0; -static uint32_t ym_samp_pos_wr = 0; -static uint32_t ym_samp_pos_hd = 0; -static uint32_t vera_samps_per_host_samps = 0; -static uint32_t ym_samps_per_host_samps = 0; -static uint32_t limiter_amp = 0; - -static int16_t psg_buf[2 * SAMPLES_PER_BUFFER]; -static int16_t pcm_buf[2 * SAMPLES_PER_BUFFER]; -static int16_t ym_buf[2 * SAMPLES_PER_BUFFER]; - -uint32_t host_sample_rate = 0; - -void -audio_callback(void *userdata, Uint8 *stream, int len) -{ - int expected = SAMPLES_PER_BUFFER * SAMPLE_BYTES; - if (len != expected) { - printf("Audio buffer size mismatch! (expected: %d, got: %d)\n", expected, len); - return; - } - - uint32_t spos = 0; - if (rdidx > wridx) { - uint32_t actual_len = SDL_min(len / SAMPLE_BYTES, (buffer_size - rdidx) / 2); - if (actual_len > 0) { - memcpy(&stream[spos], &buffer[rdidx], actual_len * SAMPLE_BYTES); - spos += actual_len * SAMPLE_BYTES; - len -= actual_len * SAMPLE_BYTES; - rdidx = (rdidx + actual_len * 2) % buffer_size; - buffer_written -= actual_len * 2; - } - } - uint32_t actual_len = SDL_min(len / SAMPLE_BYTES, (wridx - rdidx) / 2); - if (actual_len > 0) { - memcpy(&stream[spos], &buffer[rdidx], actual_len * SAMPLE_BYTES); - spos += actual_len * SAMPLE_BYTES; - len -= actual_len * SAMPLE_BYTES; - rdidx = (rdidx + actual_len * 2) % buffer_size; - buffer_written -= actual_len * 2; - } - if (len > 0) memset(&stream[spos], 0, len); -} - -SDL_AudioSpec obtained; -void -audio_init(int num_audio_buffers) -{ - // Set number of buffers - int num_bufs = num_audio_buffers; - if (num_bufs < 3) { - num_bufs = 3; - } - if (num_bufs > 1024) { - num_bufs = 1024; - } - buffer_size = SAMPLES_PER_BUFFER * num_bufs * 2; - - // Allocate audio buffer - buffer = malloc(buffer_size * sizeof(int16_t)); - rdidx = 0; - wridx = 0; - buffer_written = 0; - - SDL_AudioSpec desired; - - // Setup SDL audio - memset(&desired, 0, sizeof(desired)); - desired.freq = AUDIO_SAMPLERATE; - desired.format = AUDIO_S16SYS; - desired.samples = SAMPLES_PER_BUFFER; - desired.channels = 2; - - obtained = desired; - if (obtained.freq <= 0 || (AUDIO_SAMPLERATE / obtained.freq) > SAMPLES_PER_BUFFER) { - fprintf(stderr, "Obtained sample rate is too low"); - audio_close(); - return; - } - - // Init YM2151 emulation. 3.579545 MHz clock - YM_Create(3579545); - YM_init(3579545/64, 60); - - host_sample_rate = obtained.freq; - vera_samps_per_host_samps = ((25000000ULL << SAMP_POS_FRAC_BITS) / 512 / host_sample_rate); - ym_samps_per_host_samps = ((3579545ULL << SAMP_POS_FRAC_BITS) / 64 / host_sample_rate); - vera_samp_pos_rd = 0; - vera_samp_pos_wr = 0; - vera_samp_pos_hd = 0; - ym_samp_pos_rd = 0; - ym_samp_pos_wr = 0; - ym_samp_pos_hd = 0; - limiter_amp = (1 << 16); - - psg_buf[0] = psg_buf[1] = 0; - pcm_buf[0] = pcm_buf[1] = 0; - ym_buf[0] = ym_buf[1] = 0; - -} - -void -audio_close(void) -{ - - - // Free audio buffers - if (buffer != NULL) { - free(buffer); - buffer = NULL; - } -} - -void -audio_step(int cpu_clocks) -{ - - while (cpu_clocks > 0) { - // Only the source with the higest sample rate (YM2151) is needed for calculation - uint32_t max_cpu_clks_ym = ((ym_samp_pos_rd - ym_samp_pos_hd - (1 << SAMP_POS_FRAC_BITS)) & SAMP_POS_MASK_FRAC) / YM_SAMP_CLKS_PER_CPU_CLK; - uint32_t max_cpu_clks = SDL_min(cpu_clocks, max_cpu_clks_ym); - vera_samp_pos_hd = (vera_samp_pos_hd + max_cpu_clks * VERA_SAMP_CLKS_PER_CPU_CLK) & SAMP_POS_MASK_FRAC; - ym_samp_pos_hd = (ym_samp_pos_hd + max_cpu_clks * YM_SAMP_CLKS_PER_CPU_CLK) & SAMP_POS_MASK_FRAC; - cpu_clocks -= max_cpu_clks; - if (cpu_clocks > 0) audio_render(); - } -} - -void -audio_render() -{ - // Render all audio sources until read and write positions catch up - // This happens when there's a change to sound registers or one of the - // sources' sample buffer head position is too far - - uint32_t pos, len; - - pos = (vera_samp_pos_wr + 1) & SAMP_POS_MASK; - len = ((vera_samp_pos_hd >> SAMP_POS_FRAC_BITS) - vera_samp_pos_wr) & SAMP_POS_MASK; - vera_samp_pos_wr = vera_samp_pos_hd >> SAMP_POS_FRAC_BITS; - if (pos + len > SAMPLES_PER_BUFFER) { - psg_render(&psg_buf[pos * 2], SAMPLES_PER_BUFFER - pos); - pcm_render(&pcm_buf[pos * 2], SAMPLES_PER_BUFFER - pos); - len -= SAMPLES_PER_BUFFER - pos; - pos = 0; - } - if (len > 0) { - psg_render(&psg_buf[pos * 2], len); - pcm_render(&pcm_buf[pos * 2], len); - } - - pos = (ym_samp_pos_wr + 1) & SAMP_POS_MASK; - len = ((ym_samp_pos_hd >> SAMP_POS_FRAC_BITS) - ym_samp_pos_wr) & SAMP_POS_MASK; - ym_samp_pos_wr = ym_samp_pos_hd >> SAMP_POS_FRAC_BITS; - if ((pos + len) > SAMPLES_PER_BUFFER) { - YM_stream_update((uint16_t *)&ym_buf[pos * 2], SAMPLES_PER_BUFFER - pos); - len -= SAMPLES_PER_BUFFER - pos; - pos = 0; - } - if (len > 0) { - YM_stream_update((uint16_t *)&ym_buf[pos * 2], len); - } - - uint32_t wridx_old = wridx; - uint32_t len_vera = (vera_samp_pos_hd - vera_samp_pos_rd) & SAMP_POS_MASK_FRAC; - uint32_t len_ym = (ym_samp_pos_hd - ym_samp_pos_rd) & SAMP_POS_MASK_FRAC; - if (len_vera < (4 << SAMP_POS_FRAC_BITS) || len_ym < (4 << SAMP_POS_FRAC_BITS)) { - // not enough samples yet, at least 4 are needed for the filter - return; - } - len_vera = (len_vera - (4 << SAMP_POS_FRAC_BITS)) / vera_samps_per_host_samps; - len_ym = (len_ym - (4 << SAMP_POS_FRAC_BITS)) / ym_samps_per_host_samps; - len = SDL_min(len_vera, len_ym); - for (int i = 0; i < len; i++) { - int32_t samp[8]; - int32_t filter_idx = 0; - int32_t vera_out_l = 0; - int32_t vera_out_r = 0; - int32_t ym_out_l = 0; - int32_t ym_out_r = 0; - // Don't resample VERA outputs if the host sample rate is as desired - if (host_sample_rate == AUDIO_SAMPLERATE) { - pos = (vera_samp_pos_rd >> SAMP_POS_FRAC_BITS) * 2; - vera_out_l = ((int32_t)psg_buf[pos] + pcm_buf[pos]) << 14; - vera_out_r = ((int32_t)psg_buf[pos + 1] + pcm_buf[pos + 1]) << 14; - } else { - filter_idx = (vera_samp_pos_rd >> (SAMP_POS_FRAC_BITS - 8)) & 0xff; - pos = (vera_samp_pos_rd >> SAMP_POS_FRAC_BITS) * 2; - for (int j = 0; j < 8; j += 2) { - samp[j] = (int32_t)psg_buf[pos] + pcm_buf[pos]; - samp[j + 1] = (int32_t)psg_buf[pos + 1] + pcm_buf[pos + 1]; - pos = (pos + 2) & (SAMP_POS_MASK * 2); - } - vera_out_l += samp[0] * filter[256 + filter_idx]; - vera_out_r += samp[1] * filter[256 + filter_idx]; - vera_out_l += samp[2] * filter[ 0 + filter_idx]; - vera_out_r += samp[3] * filter[ 0 + filter_idx]; - vera_out_l += samp[4] * filter[255 - filter_idx]; - vera_out_r += samp[5] * filter[255 - filter_idx]; - vera_out_l += samp[6] * filter[511 - filter_idx]; - vera_out_r += samp[7] * filter[511 - filter_idx]; - } - filter_idx = (ym_samp_pos_rd >> (SAMP_POS_FRAC_BITS - 8)) & 0xff; - pos = (ym_samp_pos_rd >> SAMP_POS_FRAC_BITS) * 2; - for (int j = 0; j < 8; j += 2) { - samp[j] = ym_buf[pos]; - samp[j + 1] = ym_buf[pos + 1]; - pos = (pos + 2) & (SAMP_POS_MASK * 2); - } - ym_out_l += samp[0] * filter[256 + filter_idx]; - ym_out_r += samp[1] * filter[256 + filter_idx]; - ym_out_l += samp[2] * filter[ 0 + filter_idx]; - ym_out_r += samp[3] * filter[ 0 + filter_idx]; - ym_out_l += samp[4] * filter[255 - filter_idx]; - ym_out_r += samp[5] * filter[255 - filter_idx]; - ym_out_l += samp[6] * filter[511 - filter_idx]; - ym_out_r += samp[7] * filter[511 - filter_idx]; - // Mixing is according to the Developer Board - // Loudest single PSG channel is 1/8 times the max output - // mix = (psg + pcm) * 2 + ym - int32_t mix_l = (vera_out_l >> 13) + (ym_out_l >> 15); - int32_t mix_r = (vera_out_r >> 13) + (ym_out_r >> 15); - uint32_t amp = SDL_max(SDL_abs(mix_l), SDL_abs(mix_r)); - if (amp > 32767) { - uint32_t limiter_amp_new = (32767 << 16) / amp; - limiter_amp = SDL_min(limiter_amp_new, limiter_amp); - } - buffer[wridx++] = (int16_t)((mix_l * limiter_amp) >> 16); - buffer[wridx++] = (int16_t)((mix_r * limiter_amp) >> 16); - wridx %= buffer_size; - if (limiter_amp < (1 << 16)) limiter_amp++; - vera_samp_pos_rd = (vera_samp_pos_rd + vera_samps_per_host_samps) & SAMP_POS_MASK_FRAC; - ym_samp_pos_rd = (ym_samp_pos_rd + ym_samps_per_host_samps) & SAMP_POS_MASK_FRAC; - } - buffer_written += len * 2; - if (buffer_written > buffer_size) { - // Prevent the buffer from overflowing by skipping the read pointer ahead. - uint32_t buffer_skip_amount = (buffer_written / buffer_size) * SAMPLES_PER_BUFFER * 2; - rdidx = (rdidx + buffer_skip_amount) % buffer_size; - buffer_written -= buffer_skip_amount; - } - - // catch up all buffers if they are too far behind - uint32_t skip = len_vera - len; - if (skip > 1) { - vera_samp_pos_rd = (vera_samp_pos_rd + vera_samps_per_host_samps) & SAMP_POS_MASK_FRAC; - } - skip = len_ym - len; - if (skip > 1) { - ym_samp_pos_rd = (ym_samp_pos_rd + ym_samps_per_host_samps) & SAMP_POS_MASK_FRAC; - } -} diff --git a/backends/playback/zsm/x16emu/audio.h b/backends/playback/zsm/x16emu/audio.h index f76d43a..ca30bbd 100644 --- a/backends/playback/zsm/x16emu/audio.h +++ b/backends/playback/zsm/x16emu/audio.h @@ -5,6 +5,7 @@ #pragma once #include +#include "glue.h" #ifdef __EMSCRIPTEN__ #define SAMPLES_PER_BUFFER (1024) #define SAMP_POS_FRAC_BITS (22) @@ -19,13 +20,3 @@ #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); -void audio_init(int num_audio_buffers); -void audio_close(void); -void audio_step(int cpu_clocks); -void audio_render(); - -void audio_usage(void); -#ifndef IN_AUDIO_C -extern uint32_t buffer_size; -#endif diff --git a/backends/playback/zsm/x16emu/ymglue.cpp b/backends/playback/zsm/x16emu/ymglue.cpp index a754609..7858356 100644 --- a/backends/playback/zsm/x16emu/ymglue.cpp +++ b/backends/playback/zsm/x16emu/ymglue.cpp @@ -107,8 +107,8 @@ extern "C" { initialized = true; } - void YM_stream_update(uint16_t* output, uint32_t numsamples) { - if (initialized) opm_iface.generate((int16_t*)output, numsamples); + void YM_stream_update(int16_t* output, uint32_t numsamples) { + if (initialized) opm_iface.generate(output, numsamples); } void YM_write_reg(uint8_t reg, uint8_t val) { diff --git a/backends/playback/zsm/x16emu/ymglue.h b/backends/playback/zsm/x16emu/ymglue.h index bd5d605..f3bd3b7 100644 --- a/backends/playback/zsm/x16emu/ymglue.h +++ b/backends/playback/zsm/x16emu/ymglue.h @@ -9,7 +9,7 @@ extern "C" { uint8_t YM_read_status(void); void YM_Create(int clock); void YM_init(int sample_rate, int frame_rate); - void YM_stream_update(uint16_t* output, uint32_t numsamples); + void YM_stream_update(int16_t* output, uint32_t numsamples); void YM_write_reg(uint8_t reg, uint8_t val); bool YM_irq(void); diff --git a/backends/playback/zsm/zsm_backend.cpp b/backends/playback/zsm/zsm_backend.cpp index 459c153..a2f76e1 100644 --- a/backends/playback/zsm/zsm_backend.cpp +++ b/backends/playback/zsm/zsm_backend.cpp @@ -1,6 +1,6 @@ #include "zsm_backend.hpp" extern "C" { -#include "x16emu/audio.h" +#include "x16emu/glue.h" #include "x16emu/vera_pcm.h" #include "x16emu/vera_psg.h" #include "x16emu/ymglue.h" @@ -11,13 +11,15 @@ extern "C" { #include #include #include +#define HZ (AUDIO_SAMPLERATE) +#define BUFFERS 32 void ZsmBackend::load(const char *filename) { memset(&spec, 0, sizeof(spec)); spec.format = AUDIO_S16SYS; - spec.samples = SAMPLES_PER_BUFFER; + spec.samples = 100; spec.channels = 2; - spec.freq = AUDIO_SAMPLERATE; - spec.size = SAMPLES_PER_BUFFER * SAMPLE_BYTES; + spec.freq = PSG_FREQ; + spec.size = 100 * 2 * sizeof(int16_t); file = open_file(filename); char magic[2]; file->read(magic, 2, 1); @@ -31,6 +33,7 @@ void ZsmBackend::load(const char *filename) { this->loop_point = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16); file->read(loop_point, 3, 1); this->pcm_offset = loop_point[0] | ((uint32_t)(loop_point[1]) << 8) | ((uint32_t)(loop_point[2]) << 16); + pcm_offset += 3; 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); @@ -38,86 +41,195 @@ void ZsmBackend::load(const char *filename) { this->tick_rate = loop_point[0] | ((uint16_t)(loop_point[1]) << 8); file->read(loop_point, 2, 1); // Reserved. music_data_start = file->get_pos(); + this->loop_point += music_data_start; + file->seek(pcm_offset, SeekType::SET); + file->read(loop_point, 1, 1); + pcm_offset++; + pcm_data_offs = (loop_point[0] * 16) + pcm_offset; + file->seek(music_data_start, SeekType::SET); + double time = 0.0; + double tmpDelayTicks = 0.0; while (true) { - ZsmCommand cmd = get_command(); - if (cmd.id == ZsmEOF) { - break; + tmpDelayTicks -= get_delay_per_frame(); + if (tmpDelayTicks < 0.0) { + ZsmCommand cmd = get_command(); + if (cmd.id == ZsmEOF) { + break; + } else if (cmd.id == Delay) { + time += ((double)cmd.delay) / ((double)(tick_rate)); + tmpDelayTicks += cmd.delay; + } } } + length = time; music_data_len = file->get_pos(); switch_stream(0); + loop_end = length; + loop_start = ((double)this->loop_point) / ((double)tick_rate); + fm_stream = SDL_NewAudioStream(AUDIO_S16SYS, 2, YM_FREQ, AUDIO_S16SYS, 2, PSG_FREQ); + DEBUG.writefln("fm_stream: %ld -> %ld", YM_FREQ, PSG_FREQ); } extern SDL_AudioSpec obtained; void ZsmBackend::switch_stream(int idx) { + YM_Create(YM_FREQ); + YM_init(YM_FREQ/64, 60); psg_reset(); - audio_close(); - audio_init(16); - spec = obtained; - spec.size = SAMPLES_PER_BUFFER * SAMPLE_BYTES; + for (uint8_t i = 0; i < 16; i++) { + psg_writereg(i * 4 + 2, 0); + } + this->cpuClocks = 0.0; + this->delayTicks = 0.0; + this->ticks = 0.0; } void ZsmBackend::cleanup() { - audio_close(); delete file; file = nullptr; + audio_buf.clear(); + SDL_FreeAudioStream(fm_stream); + fm_stream = nullptr; +} +void ZsmBackend::tick(bool step) { + delayTicks -= 1; + const double ClocksPerTick = ((double)HZ) / ((double)tick_rate); + ssize_t ticks_remaining = ClocksPerTick; + while (delayTicks <= 0) { + ZsmCommand cmd = get_command(); + switch (cmd.id) { + case ZsmEOF: { + if (step) { + file->seek(this->loop_point, SeekType::SET); + this->position = this->loop_pos; + } else { + throw std::exception(); + } + } break; + case PsgWrite: { + psg_writereg(cmd.psg_write.reg, cmd.psg_write.val); + } break; + case FmWrite: { + for (uint8_t i = 0; i < cmd.fm_write.len; i++) { + YM_write_reg(cmd.fm_write.regs[i].reg, cmd.fm_write.regs[i].val); + while (YM_read_status()) { + size_t clocksToAddForYm = 64; + ticks_remaining -= clocksToAddForYm; + if (ticks_remaining < 0) { + delayTicks -= 1; + cpuClocks += ClocksPerTick; + ticks_remaining += ClocksPerTick; + } + audio_step(clocksToAddForYm); + } + } + } break; + case Delay: { + delayTicks += cmd.delay; + position += ((double)cmd.delay) / ((double)(tick_rate)); + } break; + case ExtCmd: { + //cmd.extcmd.channel + switch (cmd.extcmd.channel) { + case 0: { + for (size_t i = 0; i < cmd.extcmd.bytes; i += 2) { + switch (cmd.extcmd.pcm[i]) { + case 0: { // ctrl + pcm_write_ctrl(cmd.extcmd.pcm[i + 1]); + } break; + case 1: { // rate + pcm_write_rate(cmd.extcmd.pcm[i + 1]); + } break; + default: { // trigger + size_t file_pos = file->get_pos(); + uint8_t ctrl = pcm_read_ctrl(); + pcm_write_ctrl(ctrl | 0x80); + uint16_t pcm_idx = cmd.extcmd.pcm[i + 1]; + uint16_t instdef = pcm_idx * 16; + file->seek(pcm_offset + instdef, SeekType::SET); + uint8_t geom; + file->read(&geom, 1, 1); + ctrl = pcm_read_ctrl() & 0x0F; + ctrl |= geom & 0x30; + pcm_write_ctrl(ctrl); + uint8_t bytes[10]; + file->read(bytes, 10, 1); + loop_rem = bytes[9]; + loop_rem <<= 8; + loop_rem |= bytes[8]; + loop_rem <<= 8; + loop_rem |= bytes[7]; + loop = loop_rem & 0xFFFF; + islooped = bytes[6]; + remain = bytes[5]; + remain <<= 8; + remain |= bytes[4]; + remain <<= 8; + remain |= bytes[3]; + cur = bytes[2]; + cur <<= 8; + cur |= bytes[1]; + cur <<= 8; + cur |= bytes[0]; + pcm_loop_point = cur + loop; + rem_point = remain - loop_rem; + file->seek(file_pos, SeekType::SET); + } break; + } + //cmd.extcmd.pcm + audio_step(0); + } break; + } + // Nothing handled yet. + } + } break; + } + } + audio_step(ticks_remaining); + cpuClocks += ClocksPerTick; +} +void ZsmBackend::add_clocks(double amount, bool step) { + const double ClocksPerTick = ((double)HZ) / ((double)tick_rate); + cpuClocks = std::fmod(cpuClocks, ClocksPerTick); + double prevCpuClocks = cpuClocks; + size_t prevIntCpuClocks = prevCpuClocks; + double tickDelta = amount / ClocksPerTick; + double prevTicks = ticks; + ticks += tickDelta; + size_t prevIntTicks = prevTicks; + size_t intTicks = ticks; + size_t intTicksDelta = intTicks - prevIntTicks; + cpuClocks += intTicks * ClocksPerTick; + double remainder = amount - (intTicksDelta * ClocksPerTick); + size_t intCpuClocks = cpuClocks; + size_t intCpuClockDelta = intCpuClocks - prevIntCpuClocks; + double initialTicks = prevCpuClocks / ClocksPerTick; + for (size_t i = 0; i < intTicksDelta; i++) { + double preTickCpuClocks = cpuClocks; + delayTicks--; + tick(step); + double neededCpuClocks = preTickCpuClocks + ClocksPerTick; + if (cpuClocks < neededCpuClocks) { + cpuClocks = neededCpuClocks; + } + } + if (remainder >= 0) { + cpuClocks += remainder; + audio_step(remainder); + } } size_t ZsmBackend::render(void *buf, size_t maxlen) { - size_t sample_type_len = size_of_sample_type(spec.format); + size_t sample_type_len = 2; maxlen /= sample_type_len; - double prevTicks = ticks; - double delta = (double)(maxlen) / (double)(spec.freq); - double deltaTicks = delta / (double)(tick_rate); - ticks += deltaTicks; - double clocks = delta * (double)(8000000); - for (size_t i = 0; i < (size_t)(ticks - prevTicks); i++) { - double deltaClocks = (double)(tick_rate) * (double)(8000000); - clocks -= deltaClocks; - if (clocks < 0) { - break; - } - while (true) { - ZsmCommand cmd = get_command(); - bool tick_end = false; - if (delayTicks > 0.0) { - delayTicks -= 1.0 / double(tick_rate); - break; - } - switch (cmd.id) { - case ZsmEOF: { - seek_internal((double)(loop_point) / (double)(tick_rate)); - } break; - case PsgWrite: { - psg_writereg(cmd.psg_write.reg, cmd.psg_write.val); - } break; - case FmWrite: { - for (uint8_t i = 0; i < cmd.fm_write.len; i++) { - YM_write_reg(cmd.fm_write.regs[i].reg, cmd.fm_write.regs[i].val); - } - } break; - case Delay: { - delayTicks = cmd.delay; - tick_end = true; - } break; - case ExtCmd: { - // Nothing handled yet. - } break; - } - if (tick_end) break; - } - audio_step((int)(clocks)); + while (audio_buf.size() <= maxlen) { + tick(true); } + size_t copied = copy_out(buf, maxlen) * sample_type_len; maxlen *= sample_type_len; - if (audio_buf.size() < maxlen || audio_buf.size() == 0) { - size_t oldlen = audio_buf.size(); - uint32_t diff = SAMPLES_PER_BUFFER * SAMPLE_BYTES; - audio_buf.resize(audio_buf.size() + diff); - audio_callback(nullptr, (Uint8*)(audio_buf.data() + oldlen), diff); - } - memcpy(buf, audio_buf.data(), maxlen); - if (audio_buf.size() - maxlen != 0) { - memmove(audio_buf.data(), audio_buf.data() + maxlen, audio_buf.size() - maxlen); - } - audio_buf.resize(audio_buf.size() - maxlen); - return maxlen; + return copied; +} +uint64_t ZsmBackend::get_min_samples() { + return spec.size; +} +std::optional ZsmBackend::get_max_samples() { + return get_min_samples(); } ZsmCommand ZsmBackend::get_command() { ZsmCommandId cmdid; @@ -195,6 +307,8 @@ ZsmCommand ZsmBackend::get_command() { } break; } } + } else if (cmdid == Delay) { + output.delay = cmd_byte & 0x7F; } return output; } @@ -214,22 +328,35 @@ ZsmCommand::~ZsmCommand() { } } } -void ZsmBackend::seek_internal(double position) { - double ticks = 0; +void ZsmBackend::seek_internal(double position, bool loop) { + switch_stream(0); file->seek(music_data_start, SeekType::SET); - while (ticks < position) { - ZsmCommand cmd = get_command(); - if (cmd.id == ZsmEOF) { + this->cpuClocks = 0.0; + this->delayTicks = 0; + this->ticks = 0.0; + this->position = 0.0; + while (this->position < position) { + audio_buf.clear(); + try { + tick(false); + } catch (std::exception) { + switch_stream(0); file->seek(music_data_start, SeekType::SET); - break; - } else if (cmd.id == Delay) { - ticks += (double)(cmd.delay) / (double)(tick_rate); + this->cpuClocks = 0.0; + this->delayTicks = 0; + this->ticks = 0.0; + this->position = 0.0; + return; } } - this->position = position; + size_t samples = (this->position - position) * PSG_FREQ; + while (samples--) { + audio_buf.pop(); + } + this->position = std::floor(position * PSG_FREQ) / PSG_FREQ; } void ZsmBackend::seek(double position) { - seek_internal(position); + seek_internal(position, false); } double ZsmBackend::get_position() { return position; diff --git a/backends/playback/zsm/zsm_backend.hpp b/backends/playback/zsm/zsm_backend.hpp index d8d7bb2..438078b 100644 --- a/backends/playback/zsm/zsm_backend.hpp +++ b/backends/playback/zsm/zsm_backend.hpp @@ -1,9 +1,25 @@ #pragma once #include "playback_backend.hpp" +#include +#include "x16emu/ymglue.h" #include #include #include +#include +#include +#include +#include +#include +#include +extern "C" { +#include "x16emu/audio.h" +#include "x16emu/vera_pcm.h" +#include "x16emu/vera_psg.h" +#include "x16emu/ymglue.h" +} #include "file_backend.hpp" +#define YM_FREQ (3579545/64) +#define PSG_FREQ (AUDIO_SAMPLERATE) enum ZsmCommandId { PsgWrite, ExtCmd, @@ -46,8 +62,81 @@ struct ZsmCommand { }; class ZsmBackend : public PlaybackBackend { File *file; - std::vector audio_buf; + Fifo audio_buf; + DynPtr psg_buf; + DynPtr pcm_buf; + DynPtr out_buf; + DynPtr ym_buf; + DynPtr ym_resample_buf; + uint32_t loop_rem; + uint32_t pcm_data_offs; + uint32_t loop; + bool islooped; + uint32_t remain; + uint32_t cur; + uint32_t pcm_loop_point; + uint32_t rem_point; + SDL_AudioStream *fm_stream; + int16_t combine_audio(int16_t a, int16_t b) { + return (int16_t)((((int32_t)a) + ((int32_t)b)) >> 1); + } + void audio_step(size_t samples) { + while (((pcm_read_ctrl() & 0x80) == 0) && remain > 0) { + remain--; + size_t oldpos = file->get_pos(); + file->seek((cur++) + pcm_data_offs, SeekType::SET); + uint8_t sample; + file->read(&sample, 1, 1); + pcm_write_fifo(sample); + if (remain == 0) { + if (islooped) { + cur = loop; + remain = loop_rem; + } + } + file->seek(oldpos, SeekType::SET); + } + if (samples == 0) return; + samples *= 2; + int16_t *psg_ptr = psg_buf.get_item_sized(samples); + int16_t *pcm_ptr = pcm_buf.get_item_sized(samples); + psg_render(psg_ptr, samples / 2); + pcm_render(pcm_ptr, samples / 2); + int16_t *out_ptr = out_buf.get_item_sized(samples); + // The exact amount of samples needed for the stream. + double ratio = ((double)YM_FREQ) / ((double)PSG_FREQ); + size_t needed_samples = ((size_t)std::floor(samples * ratio)) / 2; + int16_t *ym_ptr = ym_buf.get_item_sized(needed_samples * 2); + YM_stream_update(ym_ptr, needed_samples); + assert(SDL_AudioStreamPut(fm_stream, ym_ptr, needed_samples * 2 * sizeof(int16_t)) == 0); + while (SDL_AudioStreamAvailable(fm_stream) < ((samples + 2) * sizeof(int16_t))) { + YM_stream_update(ym_ptr, 1); + assert(SDL_AudioStreamPut(fm_stream, ym_ptr, 2 * sizeof(int16_t)) == 0); + } + int16_t *ym_resample_ptr = ym_resample_buf.get_item_sized(samples); + ssize_t ym_resample_len = SDL_AudioStreamGet(fm_stream, ym_resample_ptr, (samples + 2) * sizeof(int16_t)); + assert(ym_resample_len >= 0); + ym_resample_len /= sizeof(int16_t); + for (size_t i = 0; i < samples / 2; i++) { + size_t j = i * 2; + int32_t psg[2] = {psg_ptr[j], psg_ptr[j + 1]}; + int32_t pcm[2] = {pcm_ptr[j], pcm_ptr[j + 1]}; + int32_t vera[2] = {psg[0] + pcm[0], psg[1] + pcm[1]}; + int32_t fm[2] = {ym_resample_ptr[j], ym_resample_ptr[j + 1]}; + int16_t mix[2] = {(vera[0] + fm[0]) / 3, (vera[1] + fm[1]) / 3}; + out_ptr[j++] = mix[0]; + out_ptr[j++] = mix[1]; + } + audio_buf.push(out_ptr, samples); + } + inline void *reserve(size_t len) { + return (void*)audio_buf.reserve(len); + } + inline size_t copy_out(void *buf, size_t len) { + return audio_buf.pop((int16_t*)buf, len); + } uint32_t loop_point; + double loop_pos = 0.0; uint32_t pcm_offset; uint8_t fm_mask; uint16_t psg_channel_mask; @@ -55,11 +144,19 @@ class ZsmBackend : public PlaybackBackend { size_t music_data_start; size_t music_data_len; double ticks; - double delayTicks = 0.0; + ssize_t delayTicks = 0; double position; - void seek_internal(double position); + double cpuClocks = 0; + inline double get_delay_per_frame() { + return 1.0; + } + void tick(bool step = true); + void add_clocks(double amount, bool step = true); + void seek_internal(double position, bool loop = true); ZsmCommand get_command(); public: + uint64_t get_min_samples() override; + std::optional get_max_samples() override; inline std::string get_id() override { return "zsm"; } @@ -73,5 +170,8 @@ class ZsmBackend : public PlaybackBackend { int get_stream_idx() override; size_t render(void *buf, size_t maxlen) override; double get_position() override; + inline double get_length() override { + return length; + } inline ~ZsmBackend() override { } }; diff --git a/backends/ui/imgui/main.cpp b/backends/ui/imgui/main.cpp index b1a3c87..8890615 100644 --- a/backends/ui/imgui/main.cpp +++ b/backends/ui/imgui/main.cpp @@ -99,6 +99,7 @@ void MainLoop::Init() { accent_color.y = (float)get_option("ui.imgui.accent_color.s", accent_color.y); accent_color.z = (float)get_option("ui.imgui.accent_color.v", accent_color.z); accent_color.w = (float)get_option("ui.imgui.accent_color.a", accent_color.w); + debug_mode = get_option("ui.imgui.debug_mode", false); } Theme::updateAvailableThemes(); if (Theme::availableThemes.empty()) { @@ -186,7 +187,9 @@ void MainLoop::GuiFunction() { } ImGui::EndMenu(); } - #ifdef DEBUG_MODE + #ifndef DEBUG_MODE + if (debug_mode) + #endif if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_COG, "Main menu (in debug builds)", "Debug"))) { if (ImGui::MenuItem(_TR_CTX("Main menu | Debug", "Show ImGui Demo Window"), nullptr, show_demo_window)) { show_demo_window = !show_demo_window; @@ -194,7 +197,6 @@ void MainLoop::GuiFunction() { } ImGui::EndMenu(); } - #endif if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_INFO_CIRCLE, "Main menu", "Help"))) { if (ImGui::MenuItem(_TRI_CTX(ICON_FK_INFO, "Main menu | Help", "About"), nullptr, about_window)) { about_window = !about_window; @@ -328,6 +330,9 @@ void MainLoop::GuiFunction() { if (ImGui::SliderInt("##Framerate", &framerate, 10, 480, _TR_CTX("Preferences | Framerate slider", "Max framerate without VSync: %d"))) { set_option("ui.imgui.framerate", framerate); } + if (ImGui::Checkbox(_TR_CTX("Preference | Debug menu enable", "Enable debug menu in release builds"), &debug_mode)) { + set_option("ui.imgui.debug_mode", debug_mode); + } if (ImGui::Button(_TRI_CTX(ICON_FK_MAGIC, "Preference | Related non-preference button", "Theme Editor"), ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), 0))) { theme_editor = true; } diff --git a/backends/ui/imgui/main.h b/backends/ui/imgui/main.h index 2c8cbec..5779249 100644 --- a/backends/ui/imgui/main.h +++ b/backends/ui/imgui/main.h @@ -45,6 +45,7 @@ class MainLoop : public RendererBackend { bool prefs_window = false; bool theme_editor = false; bool about_window = false; + bool debug_mode = false; bool restart_needed = false; bool stopped = true; std::vector backends; diff --git a/config.cmake.h.in b/config.cmake.h.in index e1a1df1..2da5dc2 100644 --- a/config.cmake.h.in +++ b/config.cmake.h.in @@ -5,4 +5,4 @@ #if DEBUG_MODE_VALUE==1 #define DEBUG_MODE #endif -#undef DEBUG_MODE_VALUE +#undef DEBUG_MODE_VALUE \ No newline at end of file diff --git a/include/playback_backend.h b/include/playback_backend.h new file mode 100644 index 0000000..0b8d9a7 --- /dev/null +++ b/include/playback_backend.h @@ -0,0 +1,11 @@ +#pragma once +#include #endif namespace Looper::Log { - + const std::locale LogStream::c_locale = std::locale("C"); std::set LogStream::global_outputs; int LogStream::log_level = #ifdef DEBUG_MODE diff --git a/log.hpp b/log.hpp index 86607b1..77f09ad 100644 --- a/log.hpp +++ b/log.hpp @@ -13,6 +13,8 @@ #include #endif #include +#include +#include namespace Looper::Log { struct LogStream { std::set outputs; @@ -52,6 +54,21 @@ namespace Looper::Log { void writef(const char *fmt, ...); void vwritefln(const char *fmt, va_list args); void writefln(const char *fmt, ...); + static const std::locale c_locale; + inline void vwritef2(fmt::string_view fmt, fmt::format_args args) { + return writes(fmt::vformat(c_locale, fmt, args)); + } + template + inline void writef2(fmt::format_string fmt, T&&... args) { + return vwritef2(fmt::string_view(fmt), fmt::make_format_args(args...)); + } + inline void vwritefln2(fmt::string_view fmt, fmt::format_args args) { + return writeln(fmt::vformat(c_locale, fmt, args)); + } + template + inline void writefln2(fmt::format_string fmt, T&&... args) { + return vwritefln2(fmt::string_view(fmt), fmt::make_format_args(args...)); + } LogStream(std::initializer_list names, std::initializer_list streams, int log_level = 0); #ifdef __ANDROID__ diff --git a/main.cpp b/main.cpp index 70ab58b..2a23403 100644 --- a/main.cpp +++ b/main.cpp @@ -60,7 +60,6 @@ extern "C" int looper_run_as_executable(std::vector args) { CLI::App app{DESCRIPTION}; std::string filename = ""; app.allow_extras(); - int log_level; std::string ui_backend_option = ""; bool full_help = false; bool open_window = false; @@ -92,7 +91,6 @@ extern "C" int looper_run_as_executable(std::vector args) { args = app.remaining(false); int new_argc = args.size(); char **new_argv = (char**)malloc(new_argc * sizeof(char*)); - init_logging(); #ifdef DBUS_ENABLED if (quit) { @@ -233,10 +231,23 @@ extern "C" int looper_run_as_executable(std::vector args) { std::string current_process_type; extern int looper_run_playback_process(std::vector args); int main(int argc, char **argv) { + if (argc == 0) { + throw std::exception(); + } +#ifdef _WIN32 + size_t size = std::max(10240, PATH_MAX) + 1; + executable_path = malloc(size); + size = GetModuleFileNameA(NULL, executable_path, size); + realloc(executable_path, size); +#else + executable_path = strdup(fs::canonical("/proc/self/exe").c_str()); +#endif CLI::App app{DESCRIPTION}; std::string process_type = "normal"; app.add_option("--process-type", process_type); + app.add_option("-l, --log-level", LogStream::log_level, "Sets the minimum log level to display in the logs."); app.allow_extras(); + init_logging(); try { app.parse(argc, argv); executable_path = argv[0]; @@ -254,4 +265,5 @@ int main(int argc, char **argv) { } catch (CLI::CallForHelp) { looper_run_as_executable(std::vector({"--help"})); } + free(executable_path); } diff --git a/neko-player.project b/neko-player.project new file mode 100644 index 0000000..da10083 --- /dev/null +++ b/neko-player.project @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subprojects/ + cmake-build-Debug/ + build-wasm/ + build/ + build-codelite/ + + + + + + /mnt/dev/catmeow/neko-player + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -DDISABLE_GTK_UI=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SOUNDTOUCH=ON + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/neko-player.sh b/neko-player.sh new file mode 100644 index 0000000..88aaaa0 --- /dev/null +++ b/neko-player.sh @@ -0,0 +1,5 @@ + +echo Executing Pre Build commands ... +mkdir -p ./build-codelite +echo Done +cd build-codelite; ninja diff --git a/neko-player.workspace b/neko-player.workspace new file mode 100644 index 0000000..ed0402c --- /dev/null +++ b/neko-player.workspace @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/playback.cpp b/playback.cpp index 20aa17c..7abc29f 100644 --- a/playback.cpp +++ b/playback.cpp @@ -24,54 +24,6 @@ extern "C" { #include "file_backend.hpp" using namespace std::chrono; -int NotSDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, - const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) -{ - if (dst_data) { - *dst_data = NULL; - } - - if (dst_len) { - *dst_len = 0; - } - - if (!src_data) { - return 0; - } else if (src_len < 0) { - return 0; - } else if (!dst_data) { - return 0; - } else if (!dst_len) { - return 0; - } - - int retval = -1; - Uint8 *dst = NULL; - int dstlen = 0; - - SDL_AudioStream *stream = SDL_NewAudioStream(src_spec->format, src_spec->channels, src_spec->freq, dst_spec->format, dst_spec->channels, dst_spec->freq); - if (stream) { - if ((SDL_AudioStreamPut(stream, src_data, src_len) == 0) && (SDL_AudioStreamFlush(stream) == 0)) { - dstlen = SDL_AudioStreamAvailable(stream); - if (dstlen >= 0) { - dst = (Uint8 *)SDL_malloc(dstlen); - if (dst) { - retval = (SDL_AudioStreamGet(stream, dst, dstlen) >= 0) ? 0 : -1; - } - } - } - } - - if (retval == -1) { - SDL_free(dst); - } else { - *dst_data = dst; - *dst_len = dstlen; - } - - SDL_FreeAudioStream(stream); - return retval; -} size_t CalculateBufSize(SDL_AudioSpec *obtained, double seconds, double max_seconds, size_t samples_override = 0) { return ((((samples_override == 0) ? obtained->samples : samples_override) * std::min(seconds, max_seconds)) + 1) * sizeof(SAMPLETYPE) * obtained->channels; } @@ -103,7 +55,7 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) { return; } do { - new_bufsize = SDL_AudioStreamGet(sdl_stream, buf, (bufsize / unit) * unit); + new_bufsize = SDL_AudioStreamGet(sdl_stream, buf, std::min((size_t)backend_spec.samples, bufsize / unit) * unit); if (new_bufsize < 0) { ERROR.writefln("SDL_AudioStreamGet: %s", SDL_GetError()); DEBUG.writefln("Current audio backend: %s", process->get_backend_id().c_str()); @@ -189,7 +141,6 @@ void PlaybackInstance::Load(const char *file, int idx) { } delete backend_spec_proxy; playback_ready.store(true); - this->process->set_position(0.0); } else { set_error("Failed to create playback backend."); set_signal(PlaybackSignalErrorOccurred); @@ -257,7 +208,7 @@ void PlaybackInstance::InitLoopFunction() { SDL_AudioSpec desired; desired.format = SDL_SAMPLE_FMT; desired.freq = 48000; - desired.samples = 200; + desired.samples = 400; desired.channels = 2; desired.callback = PlaybackInstance::SDLCallback; desired.userdata = this; diff --git a/playback_backend.hpp b/playback_backend.hpp index 0e2e518..857e7d4 100644 --- a/playback_backend.hpp +++ b/playback_backend.hpp @@ -34,6 +34,8 @@ class PlaybackBackend { size_t max_sample_requirement = std::numeric_limits::max(); size_t min_sample_requirement = std::numeric_limits::min(); double rate; + double loop_start = 0.0; + double loop_end = -1.0; void setMinSamples(size_t samples) { this->minSamples = samples; adjustSampleEstimates(); @@ -98,6 +100,12 @@ class PlaybackBackend { inline virtual double get_length() { return open ? length : 0.0; } + inline virtual double get_loop_start() { + return open ? loop_start : 0.0; + } + inline virtual double get_loop_end() { + return open ? loop_end < 0.0 ? get_length() : loop_end : 0.0; + } inline virtual std::optional get_current_file() { return open ? current_file : std::optional(); } diff --git a/playback_process.cpp b/playback_process.cpp index 387a1a4..d4cfa3f 100644 --- a/playback_process.cpp +++ b/playback_process.cpp @@ -394,13 +394,35 @@ PlaybackProcess::PlaybackProcess(std::vector args) { DEBUG.writefln(" - %s", backend.second->get_id().c_str()); } init_audio_data(); + send_fd = std::stoi(args[0]); + recv_fd = std::stoi(args[1]); + this->impl.process = this; + done = true; } PlaybackProcess::PlaybackProcess(std::string filename, int idx) { - //bool multi_process = Looper::Options::get_option("playback.multi_process", false); +// multi_process = Looper::Options::get_option("playback.multi_process", true); + multi_process = false; done = false; this->done = false; - this->impl.process = this; - other_process = new PlaybackProcess(this); + if (multi_process) { + int fd1[2]; + int fd2[2]; + if (pipe(fd1) < 0) { + throw CustomException("Pipe creation failed!"); + } + if (pipe(fd2) < 0) { + throw CustomException("Pipe creation failed!"); + } + std::vector args; + args.push_back(fmt::format("{}", fd1[1])); + args.push_back(fmt::format("{}", fd2[0])); + send_fd = fd2[1]; + recv_fd = fd1[0]; + pid = launch_self("playback", args); + } else { + this->impl.process = this; + other_process = new PlaybackProcess(this); + } done = true; DEBUG.writeln("Playback process started."); InitCommand cmd; @@ -422,8 +444,70 @@ bool PlaybackProcess::process_running() { if (other_process != nullptr) return true; return kill(pid, 0) == 0; } +RPCResponse PlaybackProcess::handle_command(RPCCall &call) { + SimpleAckResponse *ack = new SimpleAckResponse(); + RPCResponse resp; + if (call.has_get()) { + PropertyDataOrError output = impl.Get(&call.get()); + if (output.has_err()) { + resp.mutable_err()->CopyFrom(output.err()); + } else { + resp.mutable_data()->CopyFrom(output.output()); + } + } else if (call.has_init()) { + MaybeError output = impl.Init(&call.init()); + if (output.has_err()) { + resp.mutable_err()->CopyFrom(output.err()); + } else { + resp.set_allocated_ack(ack); + } + } else if (call.has_quit()) { + MaybeError output = impl.Quit(&call.quit()); + if (output.has_err()) { + resp.mutable_err()->CopyFrom(output.err()); + } else { + resp.set_allocated_ack(ack); + } + } else if (call.has_set()) { + MaybeError output = impl.Set(&call.set()); + if (output.has_err()) { + resp.mutable_err()->CopyFrom(output.err()); + } else { + resp.set_allocated_ack(ack); + } + } else if (call.has_reset()) { + ResetResponse output = impl.Reset(&call.reset()); + resp.mutable_reset()->CopyFrom(output); + } else if (call.has_render()) { + RenderResponseOrError output = impl.Render(&call.render()); + if (output.has_err()) { + resp.mutable_err()->CopyFrom(output.err()); + } else { + resp.mutable_render()->CopyFrom(output.output()); + } + } + if (!resp.has_ack()) { + delete ack; + } + return resp; +} void PlaybackProcess::run_playback_process() { - throw std::exception(); + while (true) { + uint64_t len; + blocking_read(recv_fd, &len, sizeof(len)); + std::string in_bytes = std::string(' ', len, std::allocator()); + blocking_read(recv_fd, in_bytes.data(), len); + RPCCall call; + if (!call.ParseFromString(in_bytes)) { + throw std::exception(); + } + RPCResponse resp = handle_command(call); + std::string bytes = resp.SerializeAsString(); + len = bytes.length(); + ssize_t ret = -1; + blocking_write(send_fd, &len, sizeof(len)); + blocking_write(send_fd, bytes.data(), bytes.length()); + } } int looper_run_playback_process(std::vector args) { auto proc = PlaybackProcess(args); @@ -500,51 +584,21 @@ std::string PlaybackProcess::get_file_name() { return get_property_string(PropertyId::FilenameProperty); } RPCResponse PlaybackProcess::SendCommand(RPCCall *call) { - SimpleAckResponse *ack = new SimpleAckResponse(); - RPCResponse resp; - if (call->has_get()) { - PropertyDataOrError output = impl.Get(&call->get()); - if (output.has_err()) { - resp.mutable_err()->CopyFrom(output.err()); - } else { - resp.mutable_data()->CopyFrom(output.output()); - } - } else if (call->has_init()) { - MaybeError output = impl.Init(&call->init()); - if (output.has_err()) { - resp.mutable_err()->CopyFrom(output.err()); - } else { - resp.set_allocated_ack(ack); - } - } else if (call->has_quit()) { - MaybeError output = impl.Quit(&call->quit()); - if (output.has_err()) { - resp.mutable_err()->CopyFrom(output.err()); - } else { - resp.set_allocated_ack(ack); - } - } else if (call->has_set()) { - MaybeError output = impl.Set(&call->set()); - if (output.has_err()) { - resp.mutable_err()->CopyFrom(output.err()); - } else { - resp.set_allocated_ack(ack); - } - } else if (call->has_reset()) { - ResetResponse output = impl.Reset(&call->reset()); - resp.mutable_reset()->CopyFrom(output); - } else if (call->has_render()) { - RenderResponseOrError output = impl.Render(&call->render()); - if (output.has_err()) { - resp.mutable_err()->CopyFrom(output.err()); - } else { - resp.mutable_render()->CopyFrom(output.output()); - } - } - if (!resp.has_ack()) { - delete ack; - } - return resp; + if (multi_process) { + uint64_t len; + std::string str = call->SerializeAsString(); + len = str.length(); + blocking_write(send_fd, &len, sizeof(len)); + blocking_write(send_fd, str.data(), str.length()); + blocking_read(recv_fd, &len, sizeof(len)); + str.resize(len); + blocking_read(recv_fd, str.data(), len); + RPCResponse resp; + resp.ParseFromString(str); + return resp; + } else { + return handle_command(*call); + } } PlaybackStream PlaybackProcess::get_playback_stream(size_t idx) { auto *stream = get_property_value(PropertyId::StreamsProperty, idx); diff --git a/playback_process.hpp b/playback_process.hpp index 4d3aeaf..42b796e 100644 --- a/playback_process.hpp +++ b/playback_process.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include @@ -42,6 +43,9 @@ class PlaybackProcess { friend class HostProcessImpl; friend class PlaybackProcessServiceImpl; void threadfunc(); + int send_fd; + int recv_fd; + bool multi_process = false; PlaybackProcess *other_process = nullptr; PlaybackProcessServiceImpl impl; int pid; @@ -50,6 +54,7 @@ class PlaybackProcess { std::condition_variable started; bool is_playback_process = false; std::atomic_bool done; + RPCResponse handle_command(RPCCall &msg); RPCResponse SendCommand(RPCCall *msg); std::string get_version_code(); PropertyData get_property(PropertyId id, std::optional idx = {}); diff --git a/protobuf_c_optional-1.patch b/protobuf_c_optional-1.patch new file mode 100644 index 0000000..92ab7ec --- /dev/null +++ b/protobuf_c_optional-1.patch @@ -0,0 +1,75 @@ +diff --git a/protoc-c/c_field.cc b/protoc-c/c_field.cc +index d49c001..fc4943d 100644 +--- a/protoc-c/c_field.cc ++++ b/protoc-c/c_field.cc +@@ -107,7 +107,7 @@ void FieldGenerator::GenerateDescriptorInitializerGeneric(io::Printer* printer, + const std::string &descriptor_addr) const + { + std::map variables; +- const OneofDescriptor *oneof = descriptor_->containing_oneof(); ++ const OneofDescriptor *oneof = descriptor_->real_containing_oneof(); + const ProtobufCFileOptions opt = descriptor_->file()->options().GetExtension(pb_c_file); + variables["TYPE"] = type_macro; + variables["classname"] = FullNameToC(FieldScope(descriptor_)->full_name(), FieldScope(descriptor_)->file()); +diff --git a/protoc-c/c_generator.h b/protoc-c/c_generator.h +index b8b44aa..ceb53b2 100644 +--- a/protoc-c/c_generator.h ++++ b/protoc-c/c_generator.h +@@ -93,6 +93,11 @@ class PROTOC_C_EXPORT CGenerator : public CodeGenerator { + const std::string& parameter, + OutputDirectory* output_directory, + std::string* error) const; ++ ++ uint64_t GetSupportedFeatures() const override { ++ // Indicate that this code generator supports proto3 optional fields. ++ return FEATURE_PROTO3_OPTIONAL; ++ } + }; + + } // namespace c +diff --git a/protoc-c/c_message.cc b/protoc-c/c_message.cc +index af2974c..34744f4 100755 +--- a/protoc-c/c_message.cc ++++ b/protoc-c/c_message.cc +@@ -149,7 +149,7 @@ GenerateStructDefinition(io::Printer* printer) { + } + + // Generate the case enums for unions +- for (int i = 0; i < descriptor_->oneof_decl_count(); i++) { ++ for (int i = 0; i < descriptor_->real_oneof_decl_count(); i++) { + const OneofDescriptor *oneof = descriptor_->oneof_decl(i); + vars["opt_comma"] = ","; + +@@ -191,7 +191,7 @@ GenerateStructDefinition(io::Printer* printer) { + printer->Indent(); + for (int i = 0; i < descriptor_->field_count(); i++) { + const FieldDescriptor *field = descriptor_->field(i); +- if (field->containing_oneof() == NULL) { ++ if (field->real_containing_oneof() == NULL) { + SourceLocation fieldSourceLoc; + field->GetSourceLocation(&fieldSourceLoc); + +@@ -202,7 +202,7 @@ GenerateStructDefinition(io::Printer* printer) { + } + + // Generate unions from oneofs. +- for (int i = 0; i < descriptor_->oneof_decl_count(); i++) { ++ for (int i = 0; i < descriptor_->real_oneof_decl_count(); i++) { + const OneofDescriptor *oneof = descriptor_->oneof_decl(i); + vars["oneofname"] = CamelToLower(oneof->name()); + vars["foneofname"] = FullNameToC(oneof->full_name(), oneof->file()); +@@ -238,12 +238,12 @@ GenerateStructDefinition(io::Printer* printer) { + " { PROTOBUF_C_MESSAGE_INIT (&$lcclassname$__descriptor) \\\n "); + for (int i = 0; i < descriptor_->field_count(); i++) { + const FieldDescriptor *field = descriptor_->field(i); +- if (field->containing_oneof() == NULL) { ++ if (field->real_containing_oneof() == NULL) { + printer->Print(", "); + field_generators_.get(field).GenerateStaticInit(printer); + } + } +- for (int i = 0; i < descriptor_->oneof_decl_count(); i++) { ++ for (int i = 0; i < descriptor_->real_oneof_decl_count(); i++) { + const OneofDescriptor *oneof = descriptor_->oneof_decl(i); + vars["foneofname"] = FullNameToUpper(oneof->full_name(), oneof->file()); + // Initialize the case enum diff --git a/subprojects/protobuf-c b/subprojects/protobuf-c new file mode 160000 index 0000000..8c201f6 --- /dev/null +++ b/subprojects/protobuf-c @@ -0,0 +1 @@ +Subproject commit 8c201f6e47a53feaab773922a743091eb6c8972a diff --git a/util.cpp b/util.cpp index 429b159..2500ed6 100644 --- a/util.cpp +++ b/util.cpp @@ -5,6 +5,9 @@ #include #include #include +#include "log.hpp" +#include +#include std::string PadZeros(std::string input, size_t required_length) { return std::string(required_length - std::min(required_length, input.length()), '0') + input; } @@ -96,3 +99,84 @@ int launch(std::vector args) { } return pid; } +extern char *executable_path; + +int launch_self(std::string process_type, std::vector extra_args) { + extra_args.push_back(fmt::format("--process-type={0}", process_type)); + extra_args.insert(extra_args.cbegin(), executable_path); + extra_args.push_back(fmt::format("--log-level={0}", Looper::Log::LogStream::log_level)); + return launch(extra_args); +} +fs::path get_base_dir() { + auto path = fs::path(executable_path); + return path.parent_path(); +} +void blocking_write(int fd, const void *buf, const size_t len) { + ssize_t tmp; + size_t i = 0; + while (i < len) { + tmp = write(fd, ((uint8_t*)buf) + i, len - i); + if (tmp < 0) { + throw std::exception(); + } + i += tmp; + } + assert(i == len); +} +void blocking_read(int fd, void *buf, const size_t len) { + ssize_t tmp; + size_t i = 0; + while (i < len) { + tmp = read(fd, ((uint8_t*)buf) + i, len - i); + if (tmp < 0) { + throw std::exception(); + } + i += tmp; + } + assert(i == len); +} + +int NotSDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, + const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) +{ + if (dst_data) { + *dst_data = NULL; + } + + if (dst_len) { + *dst_len = 0; + } + + if (!src_data) { + return 0; + } else if (src_len < 0) { + return 0; + } else if (!dst_data) { + return 0; + } else if (!dst_len) { + return 0; + } + + int retval = -1; + Uint8 *dst = NULL; + int dstlen = 0; + + SDL_AudioStream *stream = SDL_NewAudioStream(src_spec->format, src_spec->channels, src_spec->freq, dst_spec->format, dst_spec->channels, dst_spec->freq); + if (stream) { + SDL_AudioStreamPut(stream, src_data, src_len); + SDL_AudioStreamFlush(stream); + dstlen = SDL_AudioStreamAvailable(stream); + if (dstlen >= 0) { + dst = (Uint8 *)SDL_malloc(dstlen); + if (dst) { + retval = (SDL_AudioStreamGet(stream, dst, dstlen) >= 0) ? 0 : -1; + } + } + } + + *dst_data = dst; + *dst_len = dstlen; + + SDL_FreeAudioStream(stream); + return retval; +} \ No newline at end of file diff --git a/util.hpp b/util.hpp index 8aa03e4..0c97f6a 100644 --- a/util.hpp +++ b/util.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -10,6 +11,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include "log.hpp" +namespace fs = std::filesystem; std::string PadZeros(std::string input, size_t required_length); uint8_t TimeToComponentCount(double time_code); std::string TimeToString(double time_code, uint8_t min_components = 1); @@ -91,7 +99,18 @@ inline bool is_zeroes(void *ptr, size_t len) { } return true; } +inline void replace_all(std::string &input, std::string replace, std::string value) { + size_t pos = 0; + while (true) { + pos = input.find(replace, pos); + if (pos >= input.length()) break; + input.replace(pos, pos + replace.length(), value); + pos += value.length() - replace.length(); + } +} +fs::path get_base_dir(); int launch(std::vector args); +int launch_self(std::string process_type, std::vector extra_args); #ifdef MIN #undef MIN #endif @@ -307,6 +326,7 @@ class DynPtr { this->ptr = realloc(this->ptr, new_len); this->ptr_len = new_len; } + assert(this->ptr != NULL); } /// @brief Creates a DynPtr object and resizes it. /// @param len The minimum length of the pointer. @@ -318,6 +338,7 @@ class DynPtr { /// @returns The pointer, owned by the DynPtr object. template T *get() { + assert(ptr != NULL); if constexpr (std::is_same_v) { return ptr; } else { @@ -346,3 +367,121 @@ class DynPtr { } } }; + +template +class Fifo { +private: + std::vector buffer; + size_t head = 0; + size_t tail = 0; + size_t count = 0; + +public: + Fifo(size_t initial_capacity = 16) : buffer(initial_capacity) {} + + void resize(size_t new_capacity) { + std::vector new_buffer(new_capacity); + size_t new_count = std::min(count, new_capacity); + + for (size_t i = 0; i < new_count; ++i) { + new_buffer[i] = buffer[(head + i) % buffer.size()]; + } + + buffer = std::move(new_buffer); + head = 0; + tail = new_count; + count = new_count; + } + + size_t size() const { + return count; + } + + size_t capacity() const { + return buffer.size(); + } + void push(const T& value) { + if (count == buffer.size()) { + resize(buffer.size() * 2); + } + buffer[tail] = value; + tail = (tail + 1) % buffer.size(); + ++count; + } + + void push(const T* values, size_t length) { + if (count + length > buffer.size()) { + resize(std::max(buffer.size() * 2, count + length)); + } + + for (size_t i = 0; i < length; ++i) { + buffer[tail] = values[i]; + tail = (tail + 1) % buffer.size(); + ++count; + } + } + + T pop() { + if (count == 0) { + throw std::out_of_range("FIFO is empty"); + } + T value = buffer[head]; + head = (head + 1) % buffer.size(); + --count; + return value; + } + + size_t pop(T* output, size_t max_length) { + size_t popped = std::min(count, max_length); + for (size_t i = 0; i < popped; ++i) { + output[i] = buffer[head]; + head = (head + 1) % buffer.size(); + } + count -= popped; + return popped; + } + + T* reserve(size_t length) { + if (count + length > buffer.size()) { + resize(std::max(buffer.size() * 2, count + length)); + } + + T* start = &buffer[tail]; + tail = (tail + length) % buffer.size(); + count += length; + + return start; + } + + bool empty() const { + return count == 0; + } + void clear() { + count = 0; + head = 0; + tail = 0; + } + void shrink() { + size_t offs = size() - capacity(); + if (offs != 0) { + size_t old_head = head; + size_t old_tail = tail; + size_t new_head = head - offs; + size_t new_tail = tail - offs; + size_t start = std::min(head, tail); + size_t end = std::max(head, tail); + size_t len = end - start; + for (size_t i = 0; i < len; i++) { + size_t j = i + start; + buffer[i] = buffer[j]; + } + head = new_head; + tail = new_tail; + } + buffer.resize(size()); + } +}; +void blocking_write(int fd, const void *buf, const size_t len); +void blocking_read(int fd, void *buf, const size_t len); +int NotSDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, + const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len); \ No newline at end of file