From 58e4c11603442b9018a5737cc160f1cfb515f6de Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Mon, 15 Jun 2026 07:45:26 +0000 Subject: [PATCH] libretro: add Sound Samplerate (Hint) core option The NES has no sample-based audio hardware; sound is synthesized in realtime, so there is no fixed native output samplerate. Add an audio core option, "Sound Samplerate (Hint)", offering Auto/32KHz/44KHz/48KHz/ 96KHz. Higher rates lower latency and reduce aliasing and the low-pass "smearing" introduced by frontend resampling, at a small performance cost. "Auto" queries the frontend via RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE and snaps to whichever supported rate is closest to the host output rate. When the callback is unavailable (older frontends return false) or returns an implausible value, it falls back to a platform-appropriate default (32000 Hz on Wii, else 48000 Hz). Changing the option at runtime re-inits the synthesizer and propagates the new rate to the frontend via SET_SYSTEM_AV_INFO. The existing core filter coefficient tables already cover 44100/48000/96000, and the fixed WaveFinal[2048+512] buffer accommodates the worst-case per-frame fill (1920 samples at 96 kHz PAL), so no buffer resizing is required. The experimental env define is provided locally guarded by #ifndef so the core still builds against older bundled libretro.h headers. --- src/drivers/libretro/libretro.c | 105 +++++++++++++++++++ src/drivers/libretro/libretro_core_options.h | 17 +++ 2 files changed, 122 insertions(+) diff --git a/src/drivers/libretro/libretro.c b/src/drivers/libretro/libretro.c index 3f2673e..1818527 100644 --- a/src/drivers/libretro/libretro.c +++ b/src/drivers/libretro/libretro.c @@ -10,6 +10,14 @@ #endif #include + +/* Older bundled libretro.h headers may not define this experimental + * environment callback. Define it locally so the core still builds and + * can probe for it at runtime (the frontend returns false when absent). */ +#ifndef RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE +#define RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE (81 | RETRO_ENVIRONMENT_EXPERIMENTAL) +#endif + #include #include #include @@ -293,6 +301,9 @@ static bpp_t* fceu_video_out; /* Some timing-related variables. */ static unsigned sndsamplerate; +/* User-selected samplerate hint: 0 = Auto, else an explicit rate in Hz + * (one of the supported 32000/44100/48000/96000 values). */ +static unsigned sndsamplerate_hint = 0; static unsigned sndquality; static unsigned sndvolume; unsigned swapDuty; @@ -2002,6 +2013,59 @@ static void set_apu_channels(int chan) FSettings.PCMVolume = (chan & 0x10) ? 256 : 0; } +/* Resolve the user's samplerate hint into an actual output rate in Hz. + * + * The NES has no sample-based audio hardware; sound is synthesized in + * realtime, so there is no single 'native' rate. We support 32000, 44100, + * 48000 and 96000 Hz. When the hint is "Auto", we query the frontend for + * its target output rate via RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE and + * snap to whichever of our supported rates is closest, minimising the work + * the frontend's resampler has to do (lower latency, less aliasing, no + * low-pass "smearing", better time-domain resolution at the higher rates). + * + * Fallback: if the frontend does not implement the callback (older + * frontends return false), or returns an implausible value, we fall back + * to a platform-appropriate default (32000 Hz on Wii, else 48000 Hz). */ +static unsigned resolve_samplerate_hint(void) +{ + static const unsigned supported[4] = { 32000, 44100, 48000, 96000 }; + + /* Explicit user selection always wins over Auto. */ + if (sndsamplerate_hint != 0) + return sndsamplerate_hint; + + /* Auto: ask the frontend what it is targeting. */ + { + unsigned target = 0; + if (environ_cb(RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE, &target) && + target >= 8000 && target <= 384000) + { + unsigned best = supported[0]; + unsigned best_dist = (target > supported[0]) ? + (target - supported[0]) : (supported[0] - target); + int i; + for (i = 1; i < 4; i++) + { + unsigned dist = (target > supported[i]) ? + (target - supported[i]) : (supported[i] - target); + if (dist < best_dist) + { + best_dist = dist; + best = supported[i]; + } + } + return best; + } + } + + /* Fallback: callback unavailable or returned an implausible value. */ +#ifdef GEKKO + return 32000; +#else + return 48000; +#endif +} + static void check_variables(bool startup) { struct retro_variable var = {0}; @@ -2376,6 +2440,47 @@ static void check_variables(bool startup) } } + var.key = "fceumm_sndrate_hint"; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + unsigned oldhint = sndsamplerate_hint; + unsigned oldrate = sndsamplerate; + unsigned newrate; + + if (!strcmp(var.value, "32KHz")) + sndsamplerate_hint = 32000; + else if (!strcmp(var.value, "44KHz")) + sndsamplerate_hint = 44100; + else if (!strcmp(var.value, "48KHz")) + sndsamplerate_hint = 48000; + else if (!strcmp(var.value, "96KHz")) + sndsamplerate_hint = 96000; + else /* "Auto" */ + sndsamplerate_hint = 0; + + newrate = resolve_samplerate_hint(); + + if (startup) + { + /* During load, just settle the rate before the av_info is first + * reported to the frontend. No SET_SYSTEM_AV_INFO needed yet. */ + if (newrate != oldrate) + { + sndsamplerate = newrate; + FCEUI_Sound(sndsamplerate); + } + } + /* Re-init the synthesizer and request a frontend av_info update only + * when the resolved output rate actually changes at runtime. */ + else if (sndsamplerate_hint != oldhint || newrate != oldrate) + { + sndsamplerate = newrate; + FCEUI_Sound(sndsamplerate); + audio_video_updated = 2; + } + } + var.key = "fceumm_sndquality"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) diff --git a/src/drivers/libretro/libretro_core_options.h b/src/drivers/libretro/libretro_core_options.h index 3affc04..65ef3cb 100644 --- a/src/drivers/libretro/libretro_core_options.h +++ b/src/drivers/libretro/libretro_core_options.h @@ -292,6 +292,23 @@ struct retro_core_option_v2_definition option_defs[] = { "disabled" }, #endif + { + "fceumm_sndrate_hint", + "Sound Samplerate (Hint)", + NULL, + "The NES has no sample-based audio hardware; sound is synthesized in realtime, so there is no fixed output samplerate. Select the rate the core generates audio at. Higher rates lower latency and reduce aliasing and low-pass 'smearing' from frontend resampling, at a small performance cost. 'Auto' picks whichever supported rate is closest to the frontend's target output rate.", + NULL, + "audio", + { + { "Auto", NULL }, + { "32KHz", NULL }, + { "44KHz", NULL }, + { "48KHz", NULL }, + { "96KHz", NULL }, + { NULL, NULL }, + }, + "Auto", + }, { "fceumm_sndquality", "Sound Quality",