From 9472a40ea090a0400f4a0d1b63dbcdc470e9adbd Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 26 Oct 2026 18:06:44 +0100 Subject: [PATCH 1/2] Offer Audio Sync Fix 1.0.1 through the official regional mod catalogs --- CMakeLists.txt | 11 ++++++ cmake/TombaAudioSync.cmake | 17 ++++++++ docs/regional/audio-sync.md | 17 ++++++++ eu/CMakeLists.txt | 4 ++ eu2/CMakeLists.txt | 4 ++ jp/CMakeLists.txt | 4 ++ .../psx.audio.low-latency/1.0.1/manifest.toml | 20 ++++++++++ src/mods/tomba_audio_sync_plugin.c | 7 ++++ tests/test_audio_sync_catalog.cmake | 20 ++++++++++ tests/test_audio_sync_mod.c | 39 +++++++++++++++++++ 10 files changed, 143 insertions(+) create mode 100644 cmake/TombaAudioSync.cmake create mode 100644 docs/regional/audio-sync.md create mode 100644 mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml create mode 100644 src/mods/tomba_audio_sync_plugin.c create mode 100644 tests/test_audio_sync_catalog.cmake create mode 100644 tests/test_audio_sync_mod.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bdd896..e7e5c76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,3 +164,14 @@ if(BUILD_TESTING) "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_seamless_asset_probe.py") endif() endif() + +if(BUILD_TESTING) + add_executable(tomba-audio-sync-tests tests/test_audio_sync_mod.c + src/mods/tomba_audio_sync_plugin.c "${PSXRECOMP_ROOT}/runtime/src/audio_sync.c") + target_include_directories(tomba-audio-sync-tests PRIVATE "${PSXRECOMP_ROOT}/runtime/include") + add_test(NAME tomba_audio_sync COMMAND tomba-audio-sync-tests) + add_test(NAME tomba_audio_sync_catalog COMMAND "${CMAKE_COMMAND}" + "-DTOMBA_ROOT=${CMAKE_CURRENT_SOURCE_DIR}" + "-DSCRATCH=${CMAKE_CURRENT_BINARY_DIR}/audio-catalog-tests" + -P "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_audio_sync_catalog.cmake") +endif() diff --git a/cmake/TombaAudioSync.cmake b/cmake/TombaAudioSync.cmake new file mode 100644 index 0000000..acbbe88 --- /dev/null +++ b/cmake/TombaAudioSync.cmake @@ -0,0 +1,17 @@ +# Merge one shared audio declaration into the existing regional mod staging. +function(tomba_audio_sync_catalog output original) + set(staged "${CMAKE_CURRENT_BINARY_DIR}/audio-sync-preloaded") + # Reconfiguration must not keep packages removed by a regional revision. + # Only our fixed generated staging directory may be replaced. + cmake_path(IS_PREFIX CMAKE_CURRENT_BINARY_DIR "${staged}" NORMALIZE within_build) + if(NOT within_build OR "${staged}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") + message(FATAL_ERROR "Audio catalog staging must stay inside the build directory") + endif() + file(REMOVE_RECURSE "${staged}") + if(NOT "${original}" STREQUAL "NONE") + file(COPY "${original}/" DESTINATION "${staged}") + endif() + file(COPY "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../mods/preloaded/packages/psx.audio.low-latency" + DESTINATION "${staged}/packages") + set(${output} "${staged}" PARENT_SCOPE) +endfunction() diff --git a/docs/regional/audio-sync.md b/docs/regional/audio-sync.md new file mode 100644 index 0000000..320a3d2 --- /dev/null +++ b/docs/regional/audio-sync.md @@ -0,0 +1,17 @@ +# Audio Sync Fix + +Audio Sync Fix is enabled by default for new profiles. It reduces host buffering +and prevents accelerated boot/fast-forward audio from building a delayed queue. +After a real presentation underrun it also avoids queuing accelerated catch-up. +Guest speed and SPU simulation are unchanged. Fast-forward is intentionally mute. + +There is no buffer slider. ON uses a fixed 80 ms target; OFF restores the game's +configured fallback. Existing enabled/disabled settings remain authoritative; +legacy target_ms values are ignored. Changes apply at the next game start. +High Quality SPU / Enhanced Audio is independent. Quick Mods and Mods use the +same normal saved feature state. No account or network service is involved. + +A long display-mode switch may still cause brief silence while recovering. +These host-buffer changes do not measure or control external TV/receiver delay. +The shared runtime uses no regional game addresses. Platform and regional +listening checks remain necessary; this is not a campaign-wide guarantee. diff --git a/eu/CMakeLists.txt b/eu/CMakeLists.txt index 1db7517..d278002 100644 --- a/eu/CMakeLists.txt +++ b/eu/CMakeLists.txt @@ -42,6 +42,8 @@ if(TOMBI_PLAYER_RELEASE) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/release/mods/packages/" DESTINATION "${tombi_mods}/packages") endif() +include("${TOMBA_ROOT}/cmake/TombaAudioSync.cmake") +tomba_audio_sync_catalog(tombi_mods "${tombi_mods}") psxrecomp_add_game_runtime(${tombi_target} WINDOW_TITLE "Tombi! Europe Rev 1" DEFAULT_GAME_CONFIG_PATH "${tombi_config}" @@ -169,3 +171,5 @@ else() set_target_properties(${tombi_target} PROPERTIES OUTPUT_NAME "Tombi_EU_Prototype") endif() # Original EU profiles supply guest addresses; host policies remain shared. + +target_sources(${tombi_target} PRIVATE "${TOMBA_ROOT}/src/mods/tomba_audio_sync_plugin.c") diff --git a/eu2/CMakeLists.txt b/eu2/CMakeLists.txt index 55a9bcf..4ce59be 100644 --- a/eu2/CMakeLists.txt +++ b/eu2/CMakeLists.txt @@ -38,6 +38,8 @@ if(TOMBI_EU2_SEAMLESS) execute_process(COMMAND "${Python3_EXECUTABLE}" -I -B "${CMAKE_CURRENT_SOURCE_DIR}/widescreen_profile.py" COMMAND_ERROR_IS_FATAL ANY) endif() +include("${TOMBA_ROOT}/cmake/TombaAudioSync.cmake") +tomba_audio_sync_catalog(eu2_mods "${eu2_mods}") psxrecomp_add_game_runtime(tombi-eu2 WINDOW_TITLE "Tombi! (Europe, SCES-01331)" DEFAULT_GAME_CONFIG_PATH "game.toml" @@ -112,3 +114,5 @@ if(TOMBI_EU2_SEAMLESS) endif() endif() endif() + +target_sources(tombi-eu2 PRIVATE "${TOMBA_ROOT}/src/mods/tomba_audio_sync_plugin.c") diff --git a/jp/CMakeLists.txt b/jp/CMakeLists.txt index 71899ea..94b1c2d 100644 --- a/jp/CMakeLists.txt +++ b/jp/CMakeLists.txt @@ -21,6 +21,8 @@ if(TOMBA_JP_ENHANCEMENTS) execute_process(COMMAND "${Python3_EXECUTABLE}" -I -B "${CMAKE_CURRENT_SOURCE_DIR}/widescreen_profile.py" --output "${jp_generated}" COMMAND_ERROR_IS_FATAL ANY) set(jp_mods "${CMAKE_CURRENT_SOURCE_DIR}/mods") endif() +include("${TOMBA_ROOT}/cmake/TombaAudioSync.cmake") +tomba_audio_sync_catalog(jp_mods "${jp_mods}") psxrecomp_add_game_runtime(tomba-jp PGXP WINDOW_TITLE "Ore! Tomba" @@ -57,3 +59,5 @@ endif() # The shared launcher already embeds the UTF-8 application manifest # (recomp-ui windows/recomp_utf8.manifest); a second manifest resource would # collide with it at link time. + +target_sources(tomba-jp PRIVATE "${TOMBA_ROOT}/src/mods/tomba_audio_sync_plugin.c") diff --git a/mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml b/mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml new file mode 100644 index 0000000..d8adb62 --- /dev/null +++ b/mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml @@ -0,0 +1,20 @@ +format_version = 5 +id = "psx.audio.low-latency" +version = "1.0.1" +name = "Audio Sync Fix" +author = "TombiRecomp contributors" +description = "Reduces audio latency and prevents excess audio buffering during startup, fast-forward and display-mode recovery, keeping sound effects synchronized with gameplay." +resolver = "declarative" +save_compatibility = "shared" +[[target]] +game_id = "*" +[[feature]] +id = "low-latency-audio" +name = "Audio Sync Fix" +description = "Changes apply at the next game start. Uses a fixed low-latency buffer. Accelerated audio is discarded during fast-forward while the SPU keeps running; normal playback resumes automatically. OFF restores the original buffer. High Quality SPU and game speed are unchanged." +group = "Audio" +default_enabled = true +quick = true +[[plugin]] +feature = "low-latency-audio" +id = "psx.low-latency-audio" diff --git a/src/mods/tomba_audio_sync_plugin.c b/src/mods/tomba_audio_sync_plugin.c new file mode 100644 index 0000000..f50052c --- /dev/null +++ b/src/mods/tomba_audio_sync_plugin.c @@ -0,0 +1,7 @@ +/* Host output policy only; shared by the official regional players. */ +#include "audio_sync.h" +#include "mod_plugins.h" +static void activate_audio_sync(void) { psx_audio_sync_set_enabled(1); } +PSX_MOD_CONSTRUCTOR(tomba_register_audio_sync_plugin) { + (void)psx_mod_register_activation_plugin("psx.low-latency-audio", activate_audio_sync); +} diff --git a/tests/test_audio_sync_catalog.cmake b/tests/test_audio_sync_catalog.cmake new file mode 100644 index 0000000..3e25fca --- /dev/null +++ b/tests/test_audio_sync_catalog.cmake @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.20) +if(NOT IS_DIRECTORY "${TOMBA_ROOT}/cmake" OR NOT SCRATCH) + message(FATAL_ERROR "Pass TOMBA_ROOT and a private build SCRATCH directory") +endif() +string(RANDOM LENGTH 12 ALPHABET 0123456789abcdef nonce) +set(CMAKE_CURRENT_BINARY_DIR "${SCRATCH}/run-${nonce}") +set(original "${CMAKE_CURRENT_BINARY_DIR}/original") +file(MAKE_DIRECTORY "${original}/packages/revision-only/1.0.0") +file(WRITE "${original}/packages/revision-only/1.0.0/manifest.toml" "test fixture") +include("${TOMBA_ROOT}/cmake/TombaAudioSync.cmake") +tomba_audio_sync_catalog(staged "${original}") +if(NOT EXISTS "${staged}/packages/revision-only/1.0.0/manifest.toml") + message(FATAL_ERROR "Regional declarations were not preserved") +endif() +file(RENAME "${original}/packages/revision-only" "${CMAKE_CURRENT_BINARY_DIR}/removed") +tomba_audio_sync_catalog(staged "${original}") +if(EXISTS "${staged}/packages/revision-only" OR NOT EXISTS "${staged}/packages/psx.audio.low-latency/1.0.1/manifest.toml") + message(FATAL_ERROR "Stale regional package or missing shared audio declaration") +endif() +message(STATUS "PASS shared audio staging preserves current regional declarations without stale revision packages") diff --git a/tests/test_audio_sync_mod.c b/tests/test_audio_sync_mod.c new file mode 100644 index 0000000..75e29ae --- /dev/null +++ b/tests/test_audio_sync_mod.c @@ -0,0 +1,39 @@ +#ifdef NDEBUG +#undef NDEBUG +#endif +#include "audio_sync.h" +#include "mod_plugins.h" +#include +#include +#include +static PSXModActivationCallback activate; +static const char *option; +int psx_mod_register_activation_plugin(const char *id,PSXModActivationCallback fn) { + assert(!strcmp(id,"psx.low-latency-audio")); activate=fn; return 1; +} +int psx_mod_option_value(const char *pkg,const char *feature,const char *id,char *out,uint32_t size) { + assert(!strcmp(pkg,"psx.audio.low-latency")); + assert(!strcmp(feature,"low-latency-audio")); assert(!strcmp(id,"target_ms")); + if(!option || strlen(option)>=size)return 0; + strcpy(out,option);return 1; +} +int main(void) { + assert(activate); + psx_audio_sync_set_enabled(0);assert(psx_audio_sync_target(180)==180);assert(!psx_audio_sync_enabled()); + option="80";activate();assert(psx_audio_sync_enabled());assert(psx_audio_sync_target(180)==80); + option="40";activate();assert(psx_audio_sync_target(180)==80); + option="60";activate();assert(psx_audio_sync_target(180)==80); + option="70";activate();assert(psx_audio_sync_target(180)==80); + option="120";activate();assert(psx_audio_sync_target(180)==80); + option="180";activate();assert(psx_audio_sync_target(180)==80); + psx_audio_sync_set_enabled(0);assert(psx_audio_sync_target(180)==180);assert(!psx_audio_sync_enabled()); + /* A configured fallback is never overwritten merely by disabling the mod. */ + assert(psx_audio_sync_target(160)==160); + option=NULL;activate();assert(psx_audio_sync_target(180)==80); + option="80oops";activate();assert(psx_audio_sync_target(180)==80); + option="999999999999999999999999999999";activate();assert(psx_audio_sync_target(180)==80); + option="-1";activate();assert(psx_audio_sync_target(180)==80); + option="999";activate();assert(psx_audio_sync_target(180)==80); + psx_audio_sync_set_enabled(0);assert(psx_audio_sync_target(180)==180);assert(!psx_audio_sync_enabled()); + puts("PASS fixed80ms activation, legacy option values ignored, reset and configured OFF fallback"); +} From c59c2cd52d65e077421ef57abeab2995f0919db4 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 28 Oct 2026 15:12:17 +0100 Subject: [PATCH 2/2] Use committed audio target options with a recommended 70 ms default --- CMakeLists.txt | 14 +++++++ docs/regional/audio-sync.md | 18 +++++++-- .../psx.audio.low-latency/1.0.1/manifest.toml | 26 ++++++++++-- src/mods/tomba_audio_sync_plugin.c | 19 ++++++++- tests/test_audio_sync_mod.c | 36 +++++++---------- tests/test_audio_sync_persistence.cpp | 40 +++++++++++++++++++ 6 files changed, 124 insertions(+), 29 deletions(-) create mode 100644 tests/test_audio_sync_persistence.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dfb286..56eeefc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,6 +205,20 @@ if(BUILD_TESTING) src/mods/tomba_audio_sync_plugin.c "${PSXRECOMP_ROOT}/runtime/src/audio_sync.c") target_include_directories(tomba-audio-sync-tests PRIVATE "${PSXRECOMP_ROOT}/runtime/include") add_test(NAME tomba_audio_sync COMMAND tomba-audio-sync-tests) + add_executable(tomba-audio-sync-persistence-tests + tests/test_audio_sync_persistence.cpp + "${PSXRECOMP_ROOT}/runtime/src/mod_packages.cpp" + "${PSXRECOMP_ROOT}/runtime/src/mod_media.cpp" + "${PSXRECOMP_ROOT}/runtime/src/iso_reader.cpp" + "${PSXRECOMP_ROOT}/runtime/src/cue_sheet.cpp" + "${PSXRECOMP_ROOT}/runtime/src/crc32.c" + "${PSXRECOMP_ROOT}/runtime/src/psx_sha256.c") + target_include_directories(tomba-audio-sync-persistence-tests PRIVATE + "${PSXRECOMP_ROOT}/runtime/include" "${PSXRECOMP_ROOT}/recompiler/include" + "${PSXRECOMP_ROOT}/recompiler/lib/toml11") + target_link_libraries(tomba-audio-sync-persistence-tests PRIVATE chdr-static) + add_test(NAME tomba_audio_sync_persistence COMMAND tomba-audio-sync-persistence-tests + "${CMAKE_CURRENT_SOURCE_DIR}/mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml") add_test(NAME tomba_audio_sync_catalog COMMAND "${CMAKE_COMMAND}" "-DTOMBA_ROOT=${CMAKE_CURRENT_SOURCE_DIR}" "-DSCRATCH=${CMAKE_CURRENT_BINARY_DIR}/audio-catalog-tests" diff --git a/docs/regional/audio-sync.md b/docs/regional/audio-sync.md index 320a3d2..aa11f30 100644 --- a/docs/regional/audio-sync.md +++ b/docs/regional/audio-sync.md @@ -5,13 +5,25 @@ and prevents accelerated boot/fast-forward audio from building a delayed queue. After a real presentation underrun it also avoids queuing accelerated catch-up. Guest speed and SPU simulation are unchanged. Fast-forward is intentionally mute. -There is no buffer slider. ON uses a fixed 80 ms target; OFF restores the game's -configured fallback. Existing enabled/disabled settings remain authoritative; -legacy target_ms values are ignored. Changes apply at the next game start. +ON uses a 70 ms target by default; OFF restores the game's configured fallback. +The Audio Buffer slider offers 20–180 ms in 5 ms steps. 70 ms is Recommended; +lower values are Experimental (20 ms is known unstable), and higher values give +more reserve. Both views use the same saved mod option. Reset to Defaults resets +only that target to 70 ms. Existing enabled/disabled and target choices remain +authoritative. Changes apply at the next game start, not to a running device. High Quality SPU / Enhanced Audio is independent. Quick Mods and Mods use the same normal saved feature state. No account or network service is involved. +Audio Diagnostics optionally emits target, actual fill and host lifecycle +counters once per second to the runtime log. It is off by default. Fill is not +measured end-to-end output latency; Windows, HDMI and receivers add delay that +has not been measured here. Underruns count depletion episodes (including some +intentional draining), overflow counts dropped frames, and device closes do not +include driver-internal resets. There is no callback file I/O. + A long display-mode switch may still cause brief silence while recovering. These host-buffer changes do not measure or control external TV/receiver delay. The shared runtime uses no regional game addresses. Platform and regional listening checks remain necessary; this is not a campaign-wide guarantee. +The owner confirmed 70 ms without audible issues on one system. Lower targets +can stutter or drop out; no universal safe minimum is claimed. diff --git a/mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml b/mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml index d8adb62..34fae79 100644 --- a/mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml +++ b/mods/preloaded/packages/psx.audio.low-latency/1.0.1/manifest.toml @@ -3,7 +3,7 @@ id = "psx.audio.low-latency" version = "1.0.1" name = "Audio Sync Fix" author = "TombiRecomp contributors" -description = "Reduces audio latency and prevents excess audio buffering during startup, fast-forward and display-mode recovery, keeping sound effects synchronized with gameplay." +description = "Reduces audio latency and prevents excess buffering during startup, fast-forward and display mode changes." resolver = "declarative" save_compatibility = "shared" [[target]] @@ -11,10 +11,30 @@ game_id = "*" [[feature]] id = "low-latency-audio" name = "Audio Sync Fix" -description = "Changes apply at the next game start. Uses a fixed low-latency buffer. Accelerated audio is discarded during fast-forward while the SPU keeps running; normal playback resumes automatically. OFF restores the original buffer. High Quality SPU and game speed are unchanged." +description = "Changes apply at the next game start. The Audio Buffer target applies at the next game start (70 ms default and recommended). Values below 70 ms are experimental; higher values provide more buffer reserve. 20 ms is known unstable. Restores low buffering after display-mode changes. Accelerated audio is discarded during fast-forward while the SPU keeps running; normal playback resumes automatically. OFF restores the original buffer. High Quality SPU and game speed are unchanged." group = "Audio" default_enabled = true -quick = true [[plugin]] feature = "low-latency-audio" id = "psx.low-latency-audio" + +[[option]] +feature = "low-latency-audio" +id = "target_ms" +label = "Audio Buffer" +description = "Target buffer, not measured output latency. 70 ms default and Recommended; below 70 ms Experimental; above 70 ms more buffer reserve; 20 ms unstable. Applies at the next game start." +group = "Audio" +type = "integer" +min = 20 +max = 180 +step = 5 +default = 70 + +[[option]] +feature = "low-latency-audio" +id = "diagnostics" +label = "Audio Diagnostics" +description = "Optional once-per-second target, fill, underrun, overflow and device-lifecycle diagnostics in the runtime log. Applies at next game start. No account data." +group = "Audio" +type = "boolean" +default = "false" diff --git a/src/mods/tomba_audio_sync_plugin.c b/src/mods/tomba_audio_sync_plugin.c index f50052c..c19e148 100644 --- a/src/mods/tomba_audio_sync_plugin.c +++ b/src/mods/tomba_audio_sync_plugin.c @@ -1,7 +1,24 @@ /* Host output policy only; shared by the official regional players. */ #include "audio_sync.h" #include "mod_plugins.h" -static void activate_audio_sync(void) { psx_audio_sync_set_enabled(1); } +#include +#include +#include +static void activate_audio_sync(void) { + char value[32], *end; + psx_audio_sync_set_enabled(1); + /* target_ms is an absolute buffer size, never a reduction. Only the + * committed plan is read; editing options cannot retarget a running device. */ + if (psx_mod_current_option_value("target_ms", value, sizeof(value))) { + errno = 0; + const long target = strtol(value, &end, 10); + if (!errno && end != value && !*end && target >= PSX_AUDIO_SYNC_MIN_MS && + target <= PSX_AUDIO_SYNC_MAX_MS) + (void)psx_audio_sync_set_target((int)target); + } + if (psx_mod_current_option_value("diagnostics", value, sizeof(value))) + psx_audio_sync_set_diagnostics(!strcmp(value,"true") || !strcmp(value,"1")); +} PSX_MOD_CONSTRUCTOR(tomba_register_audio_sync_plugin) { (void)psx_mod_register_activation_plugin("psx.low-latency-audio", activate_audio_sync); } diff --git a/tests/test_audio_sync_mod.c b/tests/test_audio_sync_mod.c index 75e29ae..e16bd70 100644 --- a/tests/test_audio_sync_mod.c +++ b/tests/test_audio_sync_mod.c @@ -11,29 +11,21 @@ static const char *option; int psx_mod_register_activation_plugin(const char *id,PSXModActivationCallback fn) { assert(!strcmp(id,"psx.low-latency-audio")); activate=fn; return 1; } -int psx_mod_option_value(const char *pkg,const char *feature,const char *id,char *out,uint32_t size) { - assert(!strcmp(pkg,"psx.audio.low-latency")); - assert(!strcmp(feature,"low-latency-audio")); assert(!strcmp(id,"target_ms")); - if(!option || strlen(option)>=size)return 0; - strcpy(out,option);return 1; +int psx_mod_current_option_value(const char *id,char *out,uint32_t size) { + if (strcmp(id,"target_ms") || !option || strlen(option)>=size) return 0; + strcpy(out,option); return 1; } int main(void) { assert(activate); - psx_audio_sync_set_enabled(0);assert(psx_audio_sync_target(180)==180);assert(!psx_audio_sync_enabled()); - option="80";activate();assert(psx_audio_sync_enabled());assert(psx_audio_sync_target(180)==80); - option="40";activate();assert(psx_audio_sync_target(180)==80); - option="60";activate();assert(psx_audio_sync_target(180)==80); - option="70";activate();assert(psx_audio_sync_target(180)==80); - option="120";activate();assert(psx_audio_sync_target(180)==80); - option="180";activate();assert(psx_audio_sync_target(180)==80); - psx_audio_sync_set_enabled(0);assert(psx_audio_sync_target(180)==180);assert(!psx_audio_sync_enabled()); - /* A configured fallback is never overwritten merely by disabling the mod. */ - assert(psx_audio_sync_target(160)==160); - option=NULL;activate();assert(psx_audio_sync_target(180)==80); - option="80oops";activate();assert(psx_audio_sync_target(180)==80); - option="999999999999999999999999999999";activate();assert(psx_audio_sync_target(180)==80); - option="-1";activate();assert(psx_audio_sync_target(180)==80); - option="999";activate();assert(psx_audio_sync_target(180)==80); - psx_audio_sync_set_enabled(0);assert(psx_audio_sync_target(180)==180);assert(!psx_audio_sync_enabled()); - puts("PASS fixed80ms activation, legacy option values ignored, reset and configured OFF fallback"); + psx_audio_sync_set_enabled(0); assert(psx_audio_sync_target(160)==160); + option=NULL; activate(); assert(psx_audio_sync_target(180)==70); + option="50"; activate(); assert(psx_audio_sync_target(180)==50); + /* Editing the provider alone does not alter the running session. */ + option="80"; assert(psx_audio_sync_target(180)==50); + psx_audio_sync_set_enabled(0); activate(); assert(psx_audio_sync_target(180)==80); + option="180"; psx_audio_sync_set_enabled(0); activate(); assert(psx_audio_sync_target(180)==180); + const char *bad[]={"19","181","71","70oops","999999999999999999999999999"}; + for(int i=0;i<5;i++) { option=bad[i]; psx_audio_sync_set_enabled(0); activate(); assert(psx_audio_sync_target(180)==70); } + psx_audio_sync_set_enabled(0); assert(psx_audio_sync_target(180)==180); + puts("PASS default70, saved target, next-start activation, validation and OFF fallback"); } diff --git a/tests/test_audio_sync_persistence.cpp b/tests/test_audio_sync_persistence.cpp new file mode 100644 index 0000000..cbf30c8 --- /dev/null +++ b/tests/test_audio_sync_persistence.cpp @@ -0,0 +1,40 @@ +#include "mod_packages.h" +#include +#include +#include +#include + +using namespace PSXRecompV4; +namespace fs = std::filesystem; +static void check(bool value, const std::string& message) { + if (!value) throw std::runtime_error(message); +} +int main(int argc, char** argv) { + check(argc == 2, "Pass the Audio Sync manifest path"); + const auto nonce = std::chrono::steady_clock::now().time_since_epoch().count(); + const fs::path root = fs::temp_directory_path() / + ("tomba-audio-persistence-" + std::to_string(nonce)); + const fs::path package = root / "packages/psx.audio.low-latency/1.0.1"; + fs::create_directories(package); + fs::copy_file(argv[1], package / "manifest.toml"); + const char* id = "psx.audio.low-latency"; + const char* feature = "low-latency-audio"; + const char* option = "target_ms"; + std::string error; + ModPackageManager fresh(root); + check(fresh.scan(&error) && fresh.load_state(&error), error); + check(fresh.feature_option_value(id, feature, option) == "70", "fresh 70"); + check(fresh.set_feature_option(id, feature, option, "80", &error) && + fresh.save_state(&error), error); + ModPackageManager saved(root); + check(saved.scan(&error) && saved.load_state(&error), error); + check(saved.feature_option_value(id, feature, option) == "80", "saved 80 retained"); + check(saved.set_feature_option(id, feature, option, "180", &error), "upper bound"); + check(saved.set_feature_option(id, feature, option, "70", &error) && + saved.save_state(&error), error); + ModPackageManager reset(root); + check(reset.scan(&error) && reset.load_state(&error), error); + check(reset.feature_option_value(id, feature, option) == "70", "reset 70 retained"); + fs::remove_all(root); + std::cout << "PASS fresh70, saved80, upper180 and reset70 persistence\n"; +}