libretro: add Sound Samplerate (Hint) core option
Some checks failed
CI / build-ps2 (push) Has been cancelled

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.
This commit is contained in:
LibretroAdmin
2026-06-15 07:45:26 +00:00
committed by U-DESKTOP-SPFP6AQ\twistedtechre
parent 214c77052e
commit 58e4c11603
2 changed files with 122 additions and 0 deletions

View File

@@ -10,6 +10,14 @@
#endif
#include <libretro.h>
/* 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 <string/stdstring.h>
#include <file/file_path.h>
#include <streams/file_stream.h>
@@ -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)

View File

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