Mostly fix ZSM support
This commit is contained in:
parent
856226e641
commit
80ff7bdcf3
36 changed files with 1252 additions and 531 deletions
13
.codelite/find-in-files.json
Normal file
13
.codelite/find-in-files.json
Normal 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
|
||||
}
|
BIN
.codelite/neko-player-someone.tags
Normal file
BIN
.codelite/neko-player-someone.tags
Normal file
Binary file not shown.
165
.codelite/neko-player.session
Normal file
165
.codelite/neko-player.session
Normal 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>
|
4
.codelite/neko-player.workspace.someone
Normal file
4
.codelite/neko-player.workspace.someone
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace>
|
||||
<PinnedProjects/>
|
||||
</Workspace>
|
0
.codelite/valgrind.memcheck.supp
Normal file
0
.codelite/valgrind.memcheck.supp
Normal file
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -16,3 +16,6 @@ __pycache__
|
|||
.gradle
|
||||
/sdl-android-project/app/jni
|
||||
wasm-rt/wasmer
|
||||
cmake-build-*/
|
||||
.ctagsd
|
||||
*.rej
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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
BIN
assets/test.fur
Normal file
Binary file not shown.
BIN
assets/test.zsm
Normal file
BIN
assets/test.zsm
Normal file
Binary file not shown.
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 { }
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
#if DEBUG_MODE_VALUE==1
|
||||
#define DEBUG_MODE
|
||||
#endif
|
||||
#undef DEBUG_MODE_VALUE
|
||||
#undef DEBUG_MODE_VALUE
|
11
include/playback_backend.h
Normal file
11
include/playback_backend.h
Normal 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();
|
2
log.cpp
2
log.cpp
|
@ -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
17
log.hpp
|
@ -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__
|
||||
|
|
16
main.cpp
16
main.cpp
|
@ -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
232
neko-player.project
Normal 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="<Use Defaults>" DbgSetName="<Use Defaults>">
|
||||
<![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
5
neko-player.sh
Normal 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
14
neko-player.workspace
Normal 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>
|
53
playback.cpp
53
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;
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = {});
|
||||
|
|
75
protobuf_c_optional-1.patch
Normal file
75
protobuf_c_optional-1.patch
Normal 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
|
1
subprojects/protobuf-c
Submodule
1
subprojects/protobuf-c
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 8c201f6e47a53feaab773922a743091eb6c8972a
|
84
util.cpp
84
util.cpp
|
@ -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
139
util.hpp
|
@ -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);
|
Loading…
Reference in a new issue