diff --git a/src/driver.h b/src/driver.h index b17376c..3498639 100644 --- a/src/driver.h +++ b/src/driver.h @@ -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); diff --git a/src/drivers/libretro/libretro.c b/src/drivers/libretro/libretro.c index efeb8d8..fde6342 100644 --- a/src/drivers/libretro/libretro.c +++ b/src/drivers/libretro/libretro.c @@ -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) diff --git a/src/drivers/libretro/libretro_core_options.h b/src/drivers/libretro/libretro_core_options.h index ffceb68..e17ca26 100644 --- a/src/drivers/libretro/libretro_core_options.h +++ b/src/drivers/libretro/libretro_core_options.h @@ -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", diff --git a/src/fceu.h b/src/fceu.h index 4ee4f2d..4534588 100644 --- a/src/fceu.h +++ b/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; diff --git a/src/sound.c b/src/sound.c index 97ffbd7..a0c15e7 100644 --- a/src/sound.c +++ b/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();