Mostly fix ZSM support

This commit is contained in:
Zachary Hall 2024-10-14 21:27:16 -07:00
parent 856226e641
commit 80ff7bdcf3
36 changed files with 1252 additions and 531 deletions

View file

@ -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": ["<Workspace Folder>"],
"where": "<Workspace Folder>",
"encoding": "ISO-8859-1",
"flags": 4099,
"files_scanner_flags": 1
}

Binary file not shown.

View file

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<Session Name="/mnt/dev/catmeow/neko-player/neko-player.workspace">
<int Value="18" Name="m_selectedTab"/>
<wxString Value="/mnt/dev/catmeow/neko-player/neko-player.workspace" Name="m_workspaceName"/>
<TabInfoArray Name="TabInfoArray">
<TabInfo>
<wxString Value="/home/catmeow/neko-player/subprojects/vgmstream/src/base/info.c" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/CMakeLists.txt" Name="FileName"/>
<int Value="445" Name="FirstVisibleLine"/>
<int Value="446" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/backends/ui/imgui/main.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/backends/playback/vgmstream/vgmstream.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/log.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/backends/playback/zsm/zsm_backend.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/log.hpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/usr/include/fmt/format.h" Name="FileName"/>
<int Value="30" Name="FirstVisibleLine"/>
<int Value="32" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/usr/include/fmt/core.h" Name="FileName"/>
<int Value="5" Name="FirstVisibleLine"/>
<int Value="7" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/backends/ui/imgui/main.h" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/tests.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/util.hpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/rpc.hpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/translation.hpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/util.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/options.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/main.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/file_backend.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/backends/playback/zsm/zsm_backend.hpp" Name="FileName"/>
<int Value="54" Name="FirstVisibleLine"/>
<int Value="74" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/backends/playback/zsm/x16emu/ymfm/src/ymfm_fm.h" Name="FileName"/>
<int Value="28" Name="FirstVisibleLine"/>
<int Value="30" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/backends/playback/zsm/x16emu/ymglue.h" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
<TabInfo>
<wxString Value="/home/catmeow/neko-player/backends/playback/zsm/x16emu/ymglue.cpp" Name="FileName"/>
<int Value="0" Name="FirstVisibleLine"/>
<int Value="0" Name="CurrentLine"/>
<wxArrayString Name="Bookmarks"/>
<IntVector Name="CollapsedFolds"/>
</TabInfo>
</TabInfoArray>
<SerializedObject Name="m_breakpoints">
<long Value="0" Name="Count"/>
</SerializedObject>
<wxString Value="*.c;*.cpp;*.cxx;*.cc;*.h;*.hpp;*.inc;*.mm;*.m;*.xrc;*.plist;*.txt" Name="m_findInFilesMask"/>
</Session>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace>
<PinnedProjects/>
</Workspace>

View file

3
.gitignore vendored
View file

@ -16,3 +16,6 @@ __pycache__
.gradle
/sdl-android-project/app/jni
wasm-rt/wasmer
cmake-build-*/
.ctagsd
*.rej

3
.gitmodules vendored
View file

@ -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

View file

@ -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)

BIN
assets/test.fur Normal file

Binary file not shown.

BIN
assets/test.zsm Normal file

Binary file not shown.

View file

@ -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() {

View file

@ -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)

View file

@ -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 <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 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;
}
}

View file

@ -5,6 +5,7 @@
#pragma once
#include <SDL.h>
#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

View file

@ -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) {

View file

@ -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);

View file

@ -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 <stddef.h>
#include <string.h>
#include <file_backend.hpp>
#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<uint64_t> 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;

View file

@ -1,9 +1,25 @@
#pragma once
#include "playback_backend.hpp"
#include <omp.h>
#include "x16emu/ymglue.h"
#include <cstdint>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <streambuf>
#include <vector>
#include <util.hpp>
#include <SDL.h>
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<uint8_t> audio_buf;
Fifo<int16_t> 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<int16_t>(samples);
int16_t *pcm_ptr = pcm_buf.get_item_sized<int16_t>(samples);
psg_render(psg_ptr, samples / 2);
pcm_render(pcm_ptr, samples / 2);
int16_t *out_ptr = out_buf.get_item_sized<int16_t>(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<int16_t>(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<int16_t>(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<uint64_t> 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 { }
};

View file

@ -99,6 +99,7 @@ void MainLoop::Init() {
accent_color.y = (float)get_option<double>("ui.imgui.accent_color.s", accent_color.y);
accent_color.z = (float)get_option<double>("ui.imgui.accent_color.v", accent_color.z);
accent_color.w = (float)get_option<double>("ui.imgui.accent_color.a", accent_color.w);
debug_mode = get_option<bool>("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<int64_t>("ui.imgui.framerate", framerate);
}
if (ImGui::Checkbox(_TR_CTX("Preference | Debug menu enable", "Enable debug menu in release builds"), &debug_mode)) {
set_option<bool>("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;
}

View file

@ -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<UIBackend*> backends;

View file

@ -5,4 +5,4 @@
#if DEBUG_MODE_VALUE==1
#define DEBUG_MODE
#endif
#undef DEBUG_MODE_VALUE
#undef DEBUG_MODE_VALUE

View file

@ -0,0 +1,11 @@
#pragma once
#include <protobuf-c
typedef struct {
const char *name;
const char *id;
void (*seek)(playback_backend_t backend, double position);
void (*set_rate)(playback_backend_t backend, double value);
double (*get_rate)(playback_backend_t backend);
bool set(const char *path)
} playback_backend_t;
playback_backend_t create_backend();

View file

@ -6,7 +6,7 @@
#include <android/log.h>
#endif
namespace Looper::Log {
const std::locale LogStream::c_locale = std::locale("C");
std::set<FILE*> LogStream::global_outputs;
int LogStream::log_level =
#ifdef DEBUG_MODE

17
log.hpp
View file

@ -13,6 +13,8 @@
#include <android/log.h>
#endif
#include <functional>
#include <fmt/core.h>
#include <fmt/format.h>
namespace Looper::Log {
struct LogStream {
std::set<FILE *> 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 <typename... T>
inline void writef2(fmt::format_string<T...> 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 <typename... T>
inline void writefln2(fmt::format_string<T...> fmt, T&&... args) {
return vwritefln2(fmt::string_view(fmt), fmt::make_format_args(args...));
}
LogStream(std::initializer_list<std::string> names, std::initializer_list<LogStream*> streams, int log_level = 0);
#ifdef __ANDROID__

View file

@ -60,7 +60,6 @@ extern "C" int looper_run_as_executable(std::vector<std::string> 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<std::string> 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<std::string> args) {
std::string current_process_type;
extern int looper_run_playback_process(std::vector<std::string> 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<std::string, std::string>("--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<std::string>({"--help"}));
}
free(executable_path);
}

232
neko-player.project Normal file
View file

@ -0,0 +1,232 @@
<?xml version="1.0" encoding="UTF-8"?>
<CodeLite_Project Name="neko-player" Version="11000" InternalType="">
<VirtualDirectory Name="playback">
<VirtualDirectory Name="sdl_mixer_x">
<File Name="backends/playback/sdl_mixer_x/sdl_mixer_x.hpp"/>
<File Name="backends/playback/sdl_mixer_x/sdl_mixer_x.cpp"/>
</VirtualDirectory>
<VirtualDirectory Name="vgmstream">
<File Name="backends/playback/vgmstream/vgmstream.hpp"/>
<File Name="backends/playback/vgmstream/vgmstream.cpp"/>
</VirtualDirectory>
<VirtualDirectory Name="zsm">
<File Name="backends/playback/zsm/zsm_backend.hpp"/>
<File Name="backends/playback/zsm/zsm_backend.cpp"/>
<VirtualDirectory Name="x16emu">
<File Name="backends/playback/zsm/x16emu/ymglue.h"/>
<File Name="backends/playback/zsm/x16emu/ymglue.cpp"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_pcm.h"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_pcm.cpp"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_opm.h"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_opm.cpp"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_opl.h"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_opl.cpp"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_fm.h"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_adpcm.h"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm_adpcm.cpp"/>
<File Name="backends/playback/zsm/x16emu/ymfm/src/ymfm.h"/>
<File Name="backends/playback/zsm/x16emu/vera_psg.h"/>
<File Name="backends/playback/zsm/x16emu/vera_psg.c"/>
<File Name="backends/playback/zsm/x16emu/vera_pcm.h"/>
<File Name="backends/playback/zsm/x16emu/vera_pcm.c"/>
<File Name="backends/playback/zsm/x16emu/glue.h"/>
<File Name="backends/playback/zsm/x16emu/audio.h"/>
<File Name="backends/playback/zsm/x16emu/audio.c"/>
</VirtualDirectory>
</VirtualDirectory>
</VirtualDirectory>
<VirtualDirectory Name="ui">
<VirtualDirectory Name="imgui">
<File Name="backends/ui/imgui/imgui-filebrowser/imfilebrowser.h"/>
<File Name="backends/ui/imgui/ui_backend.hpp"/>
<File Name="backends/ui/imgui/theme.h"/>
<File Name="backends/ui/imgui/theme.cpp"/>
<File Name="backends/ui/imgui/main.h"/>
<File Name="backends/ui/imgui/main.cpp"/>
<File Name="backends/ui/imgui/imgui_config.h"/>
<File Name="backends/ui/imgui/file_browser.h"/>
<File Name="backends/ui/imgui/file_browser.cpp"/>
<File Name="backends/ui/imgui/emscripten_mainloop_stub.h"/>
<File Name="backends/ui/imgui/base85.h"/>
<File Name="backends/ui/imgui/RendererBackend.h"/>
<File Name="backends/ui/imgui/RendererBackend.cpp"/>
<VirtualDirectory Name="imgui">
<File Name="backends/ui/imgui/imgui/misc/cpp/imgui_stdlib.h"/>
<File Name="backends/ui/imgui/imgui/misc/cpp/imgui_stdlib.cpp"/>
<File Name="backends/ui/imgui/imgui/imstb_truetype.h"/>
<File Name="backends/ui/imgui/imgui/imstb_textedit.h"/>
<File Name="backends/ui/imgui/imgui/imstb_rectpack.h"/>
<File Name="backends/ui/imgui/imgui/imgui_widgets.cpp"/>
<File Name="backends/ui/imgui/imgui/imgui_tables.cpp"/>
<File Name="backends/ui/imgui/imgui/imgui_internal.h"/>
<File Name="backends/ui/imgui/imgui/imgui_draw.cpp"/>
<File Name="backends/ui/imgui/imgui/imgui_demo.cpp"/>
<File Name="backends/ui/imgui/imgui/imgui.h"/>
<File Name="backends/ui/imgui/imgui/imgui.cpp"/>
<File Name="backends/ui/imgui/imgui/imconfig.h"/>
</VirtualDirectory>
</VirtualDirectory>
<VirtualDirectory Name="gtk">
<File Name="backends/ui/gtk/theme.hpp"/>
<File Name="backends/ui/gtk/theme.cpp"/>
<File Name="backends/ui/gtk/options_window.hpp"/>
<File Name="backends/ui/gtk/options_window.cpp"/>
<File Name="backends/ui/gtk/my_slider.hpp"/>
<File Name="backends/ui/gtk/my_slider.cpp"/>
<File Name="backends/ui/gtk/main_window.hpp"/>
<File Name="backends/ui/gtk/main_window.cpp"/>
<File Name="backends/ui/gtk/main.h"/>
<File Name="backends/ui/gtk/main.cpp"/>
<File Name="backends/ui/gtk/about_window.hpp"/>
<File Name="backends/ui/gtk/about_window.cpp"/>
</VirtualDirectory>
</VirtualDirectory>
<VirtualDirectory Name="assets">
<File Name="assets/tomlplusplus_license.h"/>
<File Name="assets/testdata_test_wav.h"/>
<File Name="assets/testdata_test_flac.h"/>
<File Name="assets/test_assets.h"/>
<File Name="assets/sdl_mixer_x_license.h"/>
<File Name="assets/notosansjp_thin.h"/>
<File Name="assets/notosansjp_regular.h"/>
<File Name="assets/notosansjp_license.h"/>
<File Name="assets/notosans_thin.h"/>
<File Name="assets/notosans_regular.h"/>
<File Name="assets/notosans_license.h"/>
<File Name="assets/mpris_stub_proxy.hpp"/>
<File Name="assets/mpris_stub_adaptor.hpp"/>
<File Name="assets/looper_mit_license.h"/>
<File Name="assets/looper_license.h"/>
<File Name="assets/looper_gpl_license.h"/>
<File Name="assets/lgpl_3_0_license.h"/>
<File Name="assets/lgpl_2_1_license.h"/>
<File Name="assets/lgpl_2_0_license.h"/>
<File Name="assets/jsoncpp_license.h"/>
<File Name="assets/imgui_license.h"/>
<File Name="assets/imgui_filebrowser_license.h"/>
<File Name="assets/icon.h"/>
<File Name="assets/icnfntcpphdrs_license.h"/>
<File Name="assets/gtk_frontend_css.h"/>
<File Name="assets/forkawesome_license.h"/>
<File Name="assets/forkawesome.h"/>
<File Name="assets/dbus_stub_proxy.hpp"/>
<File Name="assets/dbus_stub_adaptor.hpp"/>
<File Name="assets/cli11_license.h"/>
<File Name="assets/assets.h"/>
</VirtualDirectory>
<VirtualDirectory Name="thirdparty">
<File Name="thirdparty/toml.hpp"/>
<File Name="thirdparty/CRC.hpp"/>
<File Name="thirdparty/CLI11.hpp"/>
</VirtualDirectory>
<Reconciliation>
<Regexes/>
<Excludepaths>
<Path>subprojects/</Path>
<Path>cmake-build-Debug/</Path>
<Path>build-wasm/</Path>
<Path>build/</Path>
<Path>build-codelite/</Path>
</Excludepaths>
<Ignorefiles/>
<Extensions>
<![CDATA[*.cpp;*.c;*.h;*.hpp;*.xrc;*.wxcp;*.fbp]]>
</Extensions>
<Topleveldir>/mnt/dev/catmeow/neko-player</Topleveldir>
</Reconciliation>
<Description/>
<Dependencies/>
<VirtualDirectory Name="src">
<File Name="util.cpp"/>
<File Name="translation.cpp"/>
<File Name="tests.cpp"/>
<File Name="proxy_backend.cpp"/>
<File Name="playback_process.cpp"/>
<File Name="playback_backend.cpp"/>
<File Name="playback.cpp"/>
<File Name="options.cpp"/>
<File Name="main.cpp"/>
<File Name="log.cpp"/>
<File Name="file_backend.cpp"/>
<File Name="dummy.c"/>
<File Name="dbus.cpp"/>
<File Name="daemon_backend.cpp"/>
<File Name="base85.cpp"/>
<File Name="backend.cpp"/>
</VirtualDirectory>
<VirtualDirectory Name="include">
<File Name="web_functions.hpp"/>
<File Name="util.hpp"/>
<File Name="translation.hpp"/>
<File Name="rpc.hpp"/>
<File Name="proxy_backend.hpp"/>
<File Name="playback_process.hpp"/>
<File Name="playback_backend.hpp"/>
<File Name="playback.h"/>
<File Name="options.hpp"/>
<File Name="log.hpp"/>
<File Name="license.hpp"/>
<File Name="include/playback_backend.h"/>
<File Name="file_backend.hpp"/>
<File Name="dbus.hpp"/>
<File Name="data.h"/>
<File Name="daemon_backend.hpp"/>
<File Name="config.hpp"/>
<File Name="base85.h"/>
<File Name="backend.hpp"/>
</VirtualDirectory>
<Settings Type="Executable">
<GlobalSettings>
<Compiler Options="" C_Options="" Assembler="">
<IncludePath Value="."/>
</Compiler>
<Linker Options="">
<LibraryPath Value="."/>
</Linker>
<ResourceCompiler Options=""/>
</GlobalSettings>
<Configuration Name="Debug" CompilerType="GCC" DebuggerType="GNU gdb debugger" Type="Executable" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
<Compiler Options="-g -Wall -march=native" C_Options=" -march=native" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0">
<IncludePath Value="."/>
</Compiler>
<Linker Options="-O0 -flto" Required="yes">
<LibraryPath Value="."/>
<LibraryPath Value="Debug"/>
</Linker>
<ResourceCompiler Options="" Required="no"/>
<General OutputFile="" IntermediateDirectory="" Command="$(WorkspacePath)/cmake-build-$(WorkspaceConfiguration)/neko-player/looper" CommandArguments="-m ../../assets/test.zsm" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="$(WorkspacePath)/cmake-build-$(WorkspaceConfiguration)/neko-player" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
<BuildSystem Name="CMake">-DDISABLE_GTK_UI=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_SOUNDTOUCH=ON</BuildSystem>
<Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
<![CDATA[CMAKE_C_COMPILER_LAUNCHER=ccache
CMAKE_CXX_COMPILER_LAUNCHER=ccache]]>
</Environment>
<Debugger IsRemote="no" RemoteHostName="" RemoteHostPort="" DebuggerPath="" IsExtended="no">
<DebuggerSearchPaths/>
<PostConnectCommands/>
<StartupCommands/>
</Debugger>
<PreBuild/>
<PostBuild/>
<CustomBuild Enabled="no">
<RebuildCommand/>
<CleanCommand/>
<BuildCommand/>
<PreprocessFileCommand/>
<SingleFileCommand/>
<MakefileGenerationCommand/>
<ThirdPartyToolName/>
<WorkingDirectory/>
</CustomBuild>
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild/>
</AdditionalRules>
<Completion EnableCpp11="no" EnableCpp14="no">
<ClangCmpFlagsC/>
<ClangCmpFlags/>
<ClangPP/>
<SearchPaths/>
</Completion>
</Configuration>
</Settings>
</CodeLite_Project>

5
neko-player.sh Normal file
View file

@ -0,0 +1,5 @@
echo Executing Pre Build commands ...
mkdir -p ./build-codelite
echo Done
cd build-codelite; ninja

14
neko-player.workspace Normal file
View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<CodeLite_Workspace Name="neko-player" Database="" Version="10000">
<Project Name="neko-player" Path="neko-player.project" Active="Yes"/>
<BuildMatrix>
<WorkspaceConfiguration Name="Debug">
<Environment/>
<Project Name="neko-player" ConfigName="Debug"/>
</WorkspaceConfiguration>
<WorkspaceConfiguration Name="Release">
<Environment/>
<Project Name="neko-player" ConfigName="Debug"/>
</WorkspaceConfiguration>
</BuildMatrix>
</CodeLite_Workspace>

View file

@ -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;

View file

@ -34,6 +34,8 @@ class PlaybackBackend {
size_t max_sample_requirement = std::numeric_limits<size_t>::max();
size_t min_sample_requirement = std::numeric_limits<size_t>::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<std::string> get_current_file() {
return open ? current_file : std::optional<std::string>();
}

View file

@ -394,13 +394,35 @@ PlaybackProcess::PlaybackProcess(std::vector<std::string> 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<bool>("playback.multi_process", false);
// multi_process = Looper::Options::get_option<bool>("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<std::string> 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<char>());
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<std::string> 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<Stream>(PropertyId::StreamsProperty, idx);

View file

@ -1,6 +1,7 @@
#pragma once
#include <any>
#include <google/protobuf/message.h>
#include <google/protobuf/any.h>
#include <mutex>
#include <atomic>
#include <stdint.h>
@ -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<uint64_t> idx = {});

View file

@ -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<std::string, std::string> 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

@ -0,0 +1 @@
Subproject commit 8c201f6e47a53feaab773922a743091eb6c8972a

View file

@ -5,6 +5,9 @@
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include "log.hpp"
#include <fmt/core.h>
#include <fmt/format.h>
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<std::string> args) {
}
return pid;
}
extern char *executable_path;
int launch_self(std::string process_type, std::vector<std::string> 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;
}

139
util.hpp
View file

@ -1,4 +1,5 @@
#pragma once
#include <exception>
#include <string>
#include <string.h>
#include <stddef.h>
@ -10,6 +11,13 @@
#include <mutex>
#include <cmath>
#include <atomic>
#include <stack>
#include <filesystem>
#include <queue>
#include <cassert>
#include <SDL.h>
#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<std::string> args);
int launch_self(std::string process_type, std::vector<std::string> 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<class T>
T *get() {
assert(ptr != NULL);
if constexpr (std::is_same_v<T, void>) {
return ptr;
} else {
@ -346,3 +367,121 @@ class DynPtr {
}
}
};
template <typename T>
class Fifo {
private:
std::vector<T> 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<T> 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);