sound: add core option to reduce DMC channel popping (fixes #638)
Games like Castlevania II: Simon's Quest drive the DMC DAC directly
via $4011 writes to produce sample-style audio (separate from the
DPCM bit-stream path). The DAC jumps straight to the newly-written
7-bit value on every write, and those abrupt steps are audible as
clicks/pops when the writes form a low-frequency envelope.
Backport reference: negativeExponent's libretro-fceumm_next applies
a one-step midpoint smoother at the $4011 write site - take the
old DAC value and the new requested value, and store only the
average (rounded toward the new value). Successive writes still
converge on the target, so percussive samples retain their attack;
they just lose half of the per-write step amplitude, which is the
component that produces the click.
Backported as-is, gated on a new core option
`fceumm_reducedmcpopping` (default disabled, so the DAC
trajectory is bit-exact with the original code when the option is
not set). Verified to match _next's algebra exactly across the
boundary cases (full-range jumps in both directions, single-step
deltas, idle no-ops).
The DPCM bit-decode playback path (the +/-2 per bit RawDALatch
update further down in the DMC channel inner loop) is intentionally
NOT touched - that path is the well-behaved bit-stream output, no
popping, and smoothing it would attenuate normal samples.
This addresses the second half of issue #638 (the first being the
triangle ultrasonic gate, already landed as d3cd26e). The
follow-up comment on that issue asked specifically for DMC popping
reduction to land in libretro-fceumm since _next has no Android
port.
Build verified: sound.c -O2 / -O3 / aarch64 -O2 clean, libretro.c
clean.
This commit is contained in:
committed by
U-DESKTOP-SPFP6AQ\twistedtechre
parent
d3cd26e922
commit
5f8a864bf3
@@ -161,6 +161,7 @@ void FCEUI_CheatSearchSetCurrentAsOriginal(void);
|
||||
|
||||
void FCEUI_SetLowPass(int q);
|
||||
void FCEUI_RemoveTriangleNoise(int d);
|
||||
void FCEUI_ReduceDmcPopping(int d);
|
||||
|
||||
void FCEUI_NSFSetVis(int mode);
|
||||
int FCEUI_NSFChange(int amount);
|
||||
|
||||
@@ -1574,6 +1574,7 @@ static bool update_option_visibility(void)
|
||||
"fceumm_sndquality",
|
||||
"fceumm_sndlowpass",
|
||||
"fceumm_removetrianglenoise",
|
||||
"fceumm_reducedmcpopping",
|
||||
"fceumm_sndstereodelay",
|
||||
"fceumm_swapduty",
|
||||
"fceumm_apu_1",
|
||||
@@ -2400,6 +2401,14 @@ static void check_variables(bool startup)
|
||||
FCEUI_RemoveTriangleNoise(newval);
|
||||
}
|
||||
|
||||
var.key = "fceumm_reducedmcpopping";
|
||||
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
||||
{
|
||||
int newval = (!strcmp(var.value, "enabled")) ? 1 : 0;
|
||||
FCEUI_ReduceDmcPopping(newval);
|
||||
}
|
||||
|
||||
var.key = "fceumm_sndstereodelay";
|
||||
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
|
||||
|
||||
@@ -335,6 +335,20 @@ struct retro_core_option_v2_definition option_defs[] = {
|
||||
},
|
||||
"disabled",
|
||||
},
|
||||
{
|
||||
"fceumm_reducedmcpopping",
|
||||
"Reduce DMC Channel Popping",
|
||||
NULL,
|
||||
"Smooth direct writes to the DMC DAC register ($4011) by stepping the DAC only halfway toward each new value. Reduces the audible click when games (Castlevania II: Simon's Quest, etc.) pulse the DAC for manual sample playback. Normal DPCM bit-stream sample playback is unaffected.",
|
||||
NULL,
|
||||
"audio",
|
||||
{
|
||||
{ "disabled", NULL },
|
||||
{ "enabled", NULL },
|
||||
{ NULL, NULL },
|
||||
},
|
||||
"disabled",
|
||||
},
|
||||
{
|
||||
"fceumm_sndstereodelay",
|
||||
"Stereo Sound Effect",
|
||||
|
||||
12
src/fceu.h
12
src/fceu.h
@@ -104,6 +104,18 @@ typedef struct {
|
||||
* that behaviour in HQ. Default off to
|
||||
* preserve existing HQ output bit-exactly
|
||||
* when the option is not set. */
|
||||
int ReduceDMCPopping; /* Smooth direct writes to the DMC DAC
|
||||
* register ($4011) by stepping the DAC
|
||||
* only halfway from its previous value
|
||||
* toward the newly written value, which
|
||||
* attenuates the audible click games
|
||||
* like Castlevania II generate when they
|
||||
* pulse the DAC for manual sample
|
||||
* playback. Normal DPCM bit-decode
|
||||
* playback (the +/-2 per bit update) is
|
||||
* NOT affected. Default off so the DAC
|
||||
* trajectory is bit-exact when the
|
||||
* option is not set. */
|
||||
} FCEUS;
|
||||
|
||||
extern FCEUS FSettings;
|
||||
|
||||
24
src/sound.c
24
src/sound.c
@@ -313,7 +313,25 @@ static DECLFW(Write_DMCRegs) {
|
||||
DMCFormat = V;
|
||||
break;
|
||||
case 0x01: DoPCM();
|
||||
RawDALatch = V & 0x7F;
|
||||
{
|
||||
/* $4011 is the 7-bit DAC latch. Games like Castlevania II
|
||||
* pulse this register directly to produce sample-style audio
|
||||
* out-of-band from the DPCM bit-stream; the abrupt steps
|
||||
* between successive writes are the audible "pop". When
|
||||
* ReduceDMCPopping is on, take only the midpoint of the
|
||||
* old-vs-new transition - i.e. step the DAC halfway toward
|
||||
* the requested value rather than jumping straight to it.
|
||||
* This is the same algorithm libretro-fceumm_next ships
|
||||
* (backport reference: negativeExponent's fceumm_next).
|
||||
* The DPCM playback path (the +/-2 per bit update further
|
||||
* down) is left untouched. */
|
||||
uint8_t newval = V & 0x7F;
|
||||
uint8_t lastval = RawDALatch;
|
||||
RawDALatch = newval;
|
||||
if (FSettings.ReduceDMCPopping) {
|
||||
RawDALatch = (uint8_t)(newval - ((int)newval - (int)lastval) / 2);
|
||||
}
|
||||
}
|
||||
if (RawDALatch)
|
||||
DMC_7bit = 1;
|
||||
break;
|
||||
@@ -1261,6 +1279,10 @@ void FCEUI_RemoveTriangleNoise(int d) {
|
||||
FSettings.RemoveTriangleNoise = d ? 1 : 0;
|
||||
}
|
||||
|
||||
void FCEUI_ReduceDmcPopping(int d) {
|
||||
FSettings.ReduceDMCPopping = d ? 1 : 0;
|
||||
}
|
||||
|
||||
void FCEUI_SetSoundQuality(int quality) {
|
||||
FSettings.soundq = quality;
|
||||
SetSoundVariables();
|
||||
|
||||
Reference in New Issue
Block a user