mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 19:07:47 -04:00
audio: expose raw buffer target in launcher
This commit is contained in:
@@ -321,13 +321,16 @@ audio production cadence:
|
||||
```toml
|
||||
[audio]
|
||||
buffer_ms = 60
|
||||
offer_buffer_ms = true
|
||||
```
|
||||
|
||||
`buffer_ms` accepts 30–500 milliseconds and defaults to 180. Lower values
|
||||
reduce audible input-to-sound delay, but leave less reserve for frames where a
|
||||
game temporarily produces no audio and can therefore crackle on affected
|
||||
titles. This is deliberately a per-game developer choice; it is not read from
|
||||
the player's `settings.toml`.
|
||||
`buffer_ms` defaults to 180. Lower values reduce audible input-to-sound delay,
|
||||
but leave less reserve for frames where a game temporarily produces no audio
|
||||
and can therefore crackle on affected titles. `offer_buffer_ms` defaults to
|
||||
false. When a game explicitly enables it, recomp-ui shows a raw, unconstrained
|
||||
Audio latency debug field and persists the player's choice as
|
||||
`[audio] buffer_ms` in `settings.toml`. Games that do not opt in keep their
|
||||
validated developer value and show no control.
|
||||
|
||||
## Video Block
|
||||
|
||||
|
||||
@@ -551,12 +551,12 @@ static RuntimeConfig parse_runtime_block(const toml::value& cfg, const fs::path&
|
||||
const toml::value& audio = toml::find(cfg, "audio");
|
||||
if (audio.contains("buffer_ms")) {
|
||||
const auto n = toml::find<int64_t>(audio, "buffer_ms");
|
||||
if (n < 30 || n > 500) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"[audio] buffer_ms out of range (30..500): {}", n));
|
||||
}
|
||||
rt.audio_buffer_ms = static_cast<int>(n);
|
||||
}
|
||||
if (audio.contains("offer_buffer_ms")) {
|
||||
rt.audio_offer_buffer_ms =
|
||||
toml::find<bool>(audio, "offer_buffer_ms");
|
||||
}
|
||||
if (audio.contains("spu_hq")) {
|
||||
rt.audio_spu_hq = toml::find<bool>(audio, "spu_hq");
|
||||
}
|
||||
@@ -2012,6 +2012,10 @@ UserSettings load_user_settings(const fs::path& path) {
|
||||
}
|
||||
if (doc.contains("audio")) {
|
||||
const toml::value& a = toml::find(doc, "audio");
|
||||
if (a.contains("buffer_ms")) try_get([&]{
|
||||
const int n = toml::find<int>(a, "buffer_ms");
|
||||
s.audio_buffer_ms = n; s.has_audio_buffer_ms = true;
|
||||
});
|
||||
if (a.contains("spu_hq")) try_get([&]{
|
||||
s.spu_hq = toml::find<bool>(a, "spu_hq"); s.has_spu_hq = true;
|
||||
});
|
||||
@@ -2174,6 +2178,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) {
|
||||
if (s.has_adaptive_view)
|
||||
f << "adaptive_view = " << (s.adaptive_view ? "true" : "false") << "\n";
|
||||
f << "\n[audio]\n";
|
||||
if (s.has_audio_buffer_ms)
|
||||
f << "buffer_ms = " << s.audio_buffer_ms << "\n";
|
||||
if (s.has_spu_hq)
|
||||
f << "spu_hq = " << (s.spu_hq ? "true" : "false") << "\n";
|
||||
if (s.has_skip_launcher)
|
||||
|
||||
@@ -401,9 +401,14 @@ struct RuntimeConfig {
|
||||
// buffer_ms: steady-state host playback cushion. The ecosystem default
|
||||
// remains 180 ms because it survives long streamed-stage production gaps;
|
||||
// games with smoother production cadence may opt into a lower value to
|
||||
// reduce controller-to-sound latency. This is a game-developer setting,
|
||||
// not a player preference.
|
||||
// reduce controller-to-sound latency.
|
||||
// When exposed through offer_buffer_ms this is intentionally a raw debug
|
||||
// value: recomp-ui does not clamp or quantize it.
|
||||
int audio_buffer_ms = 180;
|
||||
// offer_buffer_ms: expose the buffer target in recomp-ui for this game.
|
||||
// Hidden by default because many games need their validated developer
|
||||
// value to survive irregular audio-production stalls.
|
||||
bool audio_offer_buffer_ms = false;
|
||||
|
||||
// spu_hq: enable the SPU float-shadow re-render (Catmull-Rom resample, float
|
||||
// headroom). Verified-enhancement, default OFF — spu_render output is
|
||||
@@ -980,6 +985,7 @@ struct UserSettings {
|
||||
bool has_adaptive_view = false; bool adaptive_view = false;
|
||||
// [audio]
|
||||
bool has_spu_hq = false; bool spu_hq = false;
|
||||
bool has_audio_buffer_ms = false; int audio_buffer_ms = 180;
|
||||
// [bios] / [disc] / [memcard]
|
||||
bool has_bios_path = false; std::filesystem::path bios_path;
|
||||
bool has_disc_path = false; std::filesystem::path disc_path;
|
||||
|
||||
@@ -137,21 +137,44 @@ note = "Test-only fixture"
|
||||
|
||||
[audio]
|
||||
buffer_ms = 60
|
||||
offer_buffer_ms = true
|
||||
)toml");
|
||||
const auto audio_config = PSXRecompV4::load_game_config(audio_buffer);
|
||||
check(audio_config.runtime.audio_buffer_ms == 60,
|
||||
"parser preserves per-game audio buffer target");
|
||||
check(audio_config.runtime.audio_buffer_ms == 60 &&
|
||||
audio_config.runtime.audio_offer_buffer_ms,
|
||||
"parser preserves per-game audio buffer target and UI opt-in");
|
||||
|
||||
const auto bad_audio_buffer = write_config(root, "bad-audio-buffer", R"toml(
|
||||
const auto audio_settings = write_config(root, "audio-settings", R"toml(
|
||||
[audio]
|
||||
buffer_ms = 30
|
||||
)toml");
|
||||
const auto user_audio =
|
||||
PSXRecompV4::load_user_settings(audio_settings);
|
||||
check(user_audio.has_audio_buffer_ms && user_audio.audio_buffer_ms == 30,
|
||||
"user settings preserve an opted-in audio buffer choice");
|
||||
|
||||
PSXRecompV4::UserSettings raw_user_audio;
|
||||
raw_user_audio.has_audio_buffer_ms = true;
|
||||
raw_user_audio.audio_buffer_ms = -7;
|
||||
const auto raw_audio_settings = root / "raw-audio-settings.toml";
|
||||
check(PSXRecompV4::save_user_settings(raw_audio_settings, raw_user_audio),
|
||||
"user settings write a raw audio buffer choice");
|
||||
const auto raw_audio_roundtrip =
|
||||
PSXRecompV4::load_user_settings(raw_audio_settings);
|
||||
check(raw_audio_roundtrip.has_audio_buffer_ms &&
|
||||
raw_audio_roundtrip.audio_buffer_ms == -7,
|
||||
"user settings round-trip raw audio buffer choices without clamping");
|
||||
|
||||
const auto raw_audio_buffer = write_config(root, "raw-audio-buffer", R"toml(
|
||||
[runtime]
|
||||
|
||||
[audio]
|
||||
buffer_ms = 20
|
||||
buffer_ms = 1
|
||||
)toml");
|
||||
check_throws(
|
||||
[&] { (void)PSXRecompV4::load_game_config(bad_audio_buffer); },
|
||||
"[audio] buffer_ms out of range (30..500)",
|
||||
"parser rejects unsafe audio buffer target");
|
||||
const auto raw_audio_config =
|
||||
PSXRecompV4::load_game_config(raw_audio_buffer);
|
||||
check(raw_audio_config.runtime.audio_buffer_ms == 1,
|
||||
"parser leaves debug audio buffer targets unconstrained");
|
||||
|
||||
const auto duplicate_address = write_config(root, "duplicate-address", R"toml(
|
||||
[[recompiler.patch]]
|
||||
|
||||
@@ -4931,6 +4931,7 @@ int main(int argc, char** argv) {
|
||||
bool ws_adaptive_view_supported = false;
|
||||
bool frame_interpolation_offered = true;
|
||||
bool skip_fmv_offered = true;
|
||||
bool audio_buffer_offered = false;
|
||||
bool vulkan_offered = false; /* game.toml [video] offer_vulkan; developer opt-in for launcher visibility */
|
||||
int resolved_deadzone = -1; /* <0 => keep input.ini/runtime default (12000) */
|
||||
/* Localization: the effective language (game.toml default -> settings.toml ->
|
||||
@@ -5195,6 +5196,7 @@ int main(int argc, char** argv) {
|
||||
(int)gc.ws_backdrop_x_sites.size());
|
||||
g_audio_spu_hq = gc.runtime.audio_spu_hq;
|
||||
g_audio_buffer_ms = (double)gc.runtime.audio_buffer_ms;
|
||||
audio_buffer_offered = gc.runtime.audio_offer_buffer_ms;
|
||||
g_auto_skip_fmv = gc.runtime.video_auto_skip_fmv ? 1 : 0;
|
||||
/* [controller] game-declared input defaults (settings.toml/launcher
|
||||
* still override below). */
|
||||
@@ -5514,6 +5516,8 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
if (us.has_adaptive_view) g_ws_adaptive_view = us.adaptive_view;
|
||||
if (us.has_spu_hq) g_audio_spu_hq = us.spu_hq;
|
||||
if (audio_buffer_offered && us.has_audio_buffer_ms)
|
||||
g_audio_buffer_ms = (double)us.audio_buffer_ms;
|
||||
/* Bundled BIOS: ignore any persisted bios_path (same clamp-as-well-
|
||||
* as-hide treatment as lock_mode / ws_offered below) so a stale
|
||||
* settings.toml or launcher-less build can never point a bundled
|
||||
@@ -5775,6 +5779,8 @@ int main(int argc, char** argv) {
|
||||
seed.adaptive_view = g_ws_adaptive_view;
|
||||
seed.has_adaptive_view = ws_offered;
|
||||
seed.spu_hq = g_audio_spu_hq; seed.has_spu_hq = true;
|
||||
seed.audio_buffer_ms = (int)g_audio_buffer_ms;
|
||||
seed.has_audio_buffer_ms = audio_buffer_offered;
|
||||
seed.skip_launcher = skip_launcher_setting; seed.has_skip_launcher = true;
|
||||
if (has_netplay_player_name) {
|
||||
seed.netplay_player_name = netplay_player_name;
|
||||
@@ -5865,6 +5871,7 @@ int main(int argc, char** argv) {
|
||||
ls.frame_interp = seed.frame_interpolation ? 1 : 0;
|
||||
ls.frame_interp_fps = seed.frame_interpolation_fps;
|
||||
ls.spu_hq = seed.spu_hq ? 1 : 0;
|
||||
ls.audio_buffer_ms = seed.audio_buffer_ms;
|
||||
ls.auto_skip_fmv = seed.auto_skip_fmv ? 1 : 0;
|
||||
ls.turbo_loads = seed.turbo_loads ? 1 : 0;
|
||||
/* Localization: index of resolved_language within lang_menu_options
|
||||
@@ -5976,6 +5983,7 @@ int main(int argc, char** argv) {
|
||||
gi.renderer_labels = kPsxRendererLabels;
|
||||
gi.num_renderers = vulkan_offered ? 3 : 2;
|
||||
gi.has_skip_fmv = skip_fmv_offered ? 1 : 0;
|
||||
gi.has_audio_buffer_ms = audio_buffer_offered ? 1 : 0;
|
||||
/* Localization menu: shown only when the game declares languages. */
|
||||
if (!rui_lang_labels.empty()) {
|
||||
gi.language_labels = rui_lang_labels.data();
|
||||
@@ -6077,6 +6085,10 @@ int main(int argc, char** argv) {
|
||||
seed.frame_interpolation_fps = ls.frame_interp_fps;
|
||||
seed.has_frame_interpolation_fps = frame_interpolation_offered;
|
||||
seed.spu_hq = ls.spu_hq != 0; seed.has_spu_hq = true;
|
||||
if (audio_buffer_offered) {
|
||||
seed.audio_buffer_ms = ls.audio_buffer_ms;
|
||||
seed.has_audio_buffer_ms = true;
|
||||
}
|
||||
seed.auto_skip_fmv = ls.auto_skip_fmv != 0;
|
||||
seed.has_auto_skip_fmv = skip_fmv_offered;
|
||||
seed.turbo_loads = ls.turbo_loads != 0; seed.has_turbo_loads = true;
|
||||
@@ -6167,6 +6179,8 @@ int main(int argc, char** argv) {
|
||||
g_video_aspect_den = ws_offered ? seed.aspect_den : 3;
|
||||
g_ws_adaptive_view = ws_offered && seed.adaptive_view;
|
||||
g_audio_spu_hq = seed.spu_hq;
|
||||
if (audio_buffer_offered)
|
||||
g_audio_buffer_ms = (double)seed.audio_buffer_ms;
|
||||
skip_launcher_setting = seed.skip_launcher;
|
||||
if (seed.has_bios_path) {
|
||||
settings_bios_storage = seed.bios_path.string();
|
||||
|
||||
@@ -278,7 +278,12 @@ static void rab__update_controller(rab_bridge *b, int pull_frames) {
|
||||
double dt_ms = (double)pull_frames * 1000.0 / c->host_rate;
|
||||
if (dt_ms < 0.1) dt_ms = 0.1;
|
||||
if (dt_ms > 100.0) dt_ms = 100.0;
|
||||
double err = (fill_ms - c->target_ms) / c->target_ms;
|
||||
/* `target_ms` may be exposed as an unconstrained debug value. Keep zero
|
||||
* mathematically defined without silently changing the requested target. */
|
||||
double norm_ms = fabs(c->target_ms);
|
||||
if (norm_ms < 1000.0 / c->source_rate)
|
||||
norm_ms = 1000.0 / c->source_rate;
|
||||
double err = (fill_ms - c->target_ms) / norm_ms;
|
||||
if (fill_ms > c->target_ms - c->deadband_ms &&
|
||||
fill_ms < c->target_ms + c->deadband_ms) {
|
||||
err = 0.0; /* deadband */
|
||||
|
||||
Reference in New Issue
Block a user