From 8252e54e14489eb217af8bd40bc63a37b408abea Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Fri, 1 Apr 2022 11:56:13 +0100 Subject: Snes9x2005 Non-Plus: Add optional low pass audio filter --- libretro.c | 48 +++++++++++++++--- libretro_core_options.h | 47 ++++++++++++++++++ source/soundux.c | 126 +++++++++++++++++++++++++++++++++++++++++++----- source/soundux.h | 1 + 4 files changed, 202 insertions(+), 20 deletions(-) diff --git a/libretro.c b/libretro.c index 2b04506..f64b71c 100644 --- a/libretro.c +++ b/libretro.c @@ -62,6 +62,11 @@ static float audio_samples_per_frame = 0.0f; static float audio_samples_accumulator = 0.0f; #endif +#ifndef USE_BLARGG_APU +static bool audio_low_pass_enabled = false; +static int32_t audio_low_pass_range = (60 * 65536) / 100; +#endif + static unsigned frameskip_type = 0; static unsigned frameskip_threshold = 0; static uint16_t frameskip_counter = 0; @@ -412,7 +417,12 @@ static void audio_upload_samples(void) audio_samples_accumulator -= 1.0f; } - S9xMixSamples(audio_out_buffer, available_frames << 1); + if (audio_low_pass_enabled) + S9xMixSamplesLowPass(audio_out_buffer, + available_frames << 1, audio_low_pass_range); + else + S9xMixSamples(audio_out_buffer, available_frames << 1); + audio_batch_cb(audio_out_buffer, available_frames); #endif } @@ -474,6 +484,10 @@ void retro_deinit(void) frameskip_type = 0; frameskip_threshold = 0; frameskip_counter = 0; +#ifndef USE_BLARGG_APU + audio_low_pass_enabled = false; + audio_low_pass_range = (60 * 65536) / 100; +#endif retro_audio_buff_active = false; retro_audio_buff_occupancy = 0; retro_audio_buff_underrun = false; @@ -528,6 +542,9 @@ static void check_variables(bool first_run) var.key = "snes9x_2005_region"; var.value = NULL; + Settings.ForceNTSC = 0; + Settings.ForcePAL = 0; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { Settings.ForceNTSC = !strcmp(var.value, "NTSC"); @@ -557,9 +574,30 @@ static void check_variables(bool first_run) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) frameskip_threshold = strtol(var.value, NULL, 10); +#ifndef USE_BLARGG_APU + var.key = "snes9x_2005_low_pass_filter"; + var.value = NULL; + + audio_low_pass_enabled = false; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + if (strcmp(var.value, "enabled") == 0) + audio_low_pass_enabled = true; + + var.key = "snes9x_2005_low_pass_range"; + var.value = NULL; + + audio_low_pass_range = (60 * 0x10000) / 100; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + audio_low_pass_range = (strtol(var.value, NULL, 10) * 0x10000) / 100; +#endif + var.key = "snes9x_2005_overclock_cycles"; var.value = NULL; + overclock_cycles = false; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) { if (strcmp(var.value, "compatible") == 0) @@ -576,20 +614,16 @@ static void check_variables(bool first_run) slow_one_c = 3; two_c = 3; } - else - overclock_cycles = false; } var.key = "snes9x_2005_reduce_sprite_flicker"; var.value = NULL; + reduce_sprite_flicker = false; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) - { if (strcmp(var.value, "enabled") == 0) reduce_sprite_flicker = true; - else - reduce_sprite_flicker = false; - } /* Reinitialise frameskipping, if required */ if (!first_run && diff --git a/libretro_core_options.h b/libretro_core_options.h index 6f46d16..28bb210 100644 --- a/libretro_core_options.h +++ b/libretro_core_options.h @@ -122,6 +122,53 @@ struct retro_core_option_v2_definition option_defs_us[] = { }, "33" }, +#ifndef USE_BLARGG_APU + { + "snes9x_2005_low_pass_filter", + "Audio Filter", + NULL, + "Enable a low pass audio filter to soften treble artifacts and mimic the 'bassy' sound of original SNES hardware.", + NULL, + NULL, + { + { "disabled", NULL }, + { "enabled", NULL }, + { NULL, NULL }, + }, + "disabled" + }, + { + "snes9x_2005_low_pass_range", + "Audio Filter Level", + NULL, + "Specify the cut-off frequency of the low pass audio filter. A higher value increases the perceived 'strength' of the filter, since a wider range of the high frequency spectrum is attenuated.", + NULL, + NULL, + { + { "5", "5%" }, + { "10", "10%" }, + { "15", "15%" }, + { "20", "20%" }, + { "25", "25%" }, + { "30", "30%" }, + { "35", "35%" }, + { "40", "40%" }, + { "45", "45%" }, + { "50", "50%" }, + { "55", "55%" }, + { "60", "60%" }, + { "65", "65%" }, + { "70", "70%" }, + { "75", "75%" }, + { "80", "80%" }, + { "85", "85%" }, + { "90", "90%" }, + { "95", "95%" }, + { NULL, NULL }, + }, + "60" + }, +#endif { "snes9x_2005_overclock_cycles", "Reduce Slowdown (Hack, Unsafe, Restart)", diff --git a/source/soundux.c b/source/soundux.c index 66b546b..1c59ff0 100644 --- a/source/soundux.c +++ b/source/soundux.c @@ -8,25 +8,18 @@ #include -#define CLIP16(v) \ -if ((v) < -32768) \ - (v) = -32768; \ -else \ -if ((v) > 32767) \ - (v) = 32767 - -#define CLIP8(v) \ -if ((v) < -128) \ - (v) = -128; \ -else if ((v) > 127) \ - (v) = 127 - #include "snes9x.h" #include "soundux.h" #include "apu.h" #include "memmap.h" #include "cpuexec.h" +#define CLIP16(v) \ +(v) = (((v) <= -32768) ? -32768 : (((v) >= 32767) ? 32767 : (v))) + +#define CLIP8(v) \ +(v) = (((v) <= -128) ? -128 : (((v) >= 127) ? 127 : (v))) + extern int32_t Echo [24000]; extern int32_t MixBuffer [SOUND_BUFFER_SIZE]; extern int32_t EchoBuffer [SOUND_BUFFER_SIZE]; @@ -89,6 +82,15 @@ uint32_t KeyOffERate [10]; #define LAST_SAMPLE 0xffffff #define JUST_PLAYED_LAST_SAMPLE(c) ((c)->sample_pointer >= LAST_SAMPLE) +/* Used by S9xMixSamplesLowPass() to store the + * last output samples for the next iteration + * of the low pass filter. This should go in + * the SSoundData struct, but doing so would + * break compatibility with existing save + * states (with little in the way of tangible + * benefits) */ +static int32_t MixOutputPrev[2]; + static INLINE uint8_t* S9xGetSampleAddress(int32_t sample_number) { uint32_t addr = (((APU.DSP[APU_DIR] << 8) + (sample_number << 2)) & 0xffff); @@ -807,6 +809,102 @@ void S9xMixSamples(int16_t* buffer, int32_t sample_count) } } +void S9xMixSamplesLowPass(int16_t* buffer, int32_t sample_count, int32_t low_pass_range) +{ + int32_t J; + int32_t I; + + /* Single-pole low-pass filter (6 dB/octave) */ + int32_t low_pass_factor_a = low_pass_range; + int32_t low_pass_factor_b = 0x10000 - low_pass_factor_a; + + if (SoundData.echo_enable) + memset(EchoBuffer, 0, sample_count * sizeof(EchoBuffer [0])); + memset(MixBuffer, 0, sample_count * sizeof(MixBuffer [0])); + MixStereo(sample_count); + + /* Mix and convert waveforms */ + if (SoundData.echo_enable && SoundData.echo_buffer_size) + { + /* 16-bit stereo sound with echo enabled ... */ + if (FilterTapDefinitionBitfield == 0) + { + /* ... but no filter defined. */ + for (J = 0; J < sample_count; J++) + { + int32_t *low_pass_sample = &MixOutputPrev[J & 0x1]; + int32_t E = Echo [SoundData.echo_ptr]; + Echo[SoundData.echo_ptr++] = (E * SoundData.echo_feedback) / 128 + EchoBuffer [J]; + + if (SoundData.echo_ptr >= SoundData.echo_buffer_size) + SoundData.echo_ptr = 0; + + I = (MixBuffer[J] * SoundData.master_volume [J & 1] + E * SoundData.echo_volume [J & 1]) / VOL_DIV16; + CLIP16(I); + + /* Apply low-pass filter */ + (*low_pass_sample) = ((*low_pass_sample) * low_pass_factor_a) + (I * low_pass_factor_b); + /* 16.16 fixed point */ + (*low_pass_sample) >>= 16; + + buffer[J] = (int16_t)(*low_pass_sample); + } + } + else + { + /* ... with filter defined. */ + for (J = 0; J < sample_count; J++) + { + int32_t *low_pass_sample = &MixOutputPrev[J & 0x1]; + int32_t E; + Loop [(Z - 0) & 15] = Echo [SoundData.echo_ptr]; + E = Loop [(Z - 0) & 15] * FilterTaps [0]; + if (FilterTapDefinitionBitfield & 0x02) E += Loop [(Z - 2) & 15] * FilterTaps [1]; + if (FilterTapDefinitionBitfield & 0x04) E += Loop [(Z - 4) & 15] * FilterTaps [2]; + if (FilterTapDefinitionBitfield & 0x08) E += Loop [(Z - 6) & 15] * FilterTaps [3]; + if (FilterTapDefinitionBitfield & 0x10) E += Loop [(Z - 8) & 15] * FilterTaps [4]; + if (FilterTapDefinitionBitfield & 0x20) E += Loop [(Z - 10) & 15] * FilterTaps [5]; + if (FilterTapDefinitionBitfield & 0x40) E += Loop [(Z - 12) & 15] * FilterTaps [6]; + if (FilterTapDefinitionBitfield & 0x80) E += Loop [(Z - 14) & 15] * FilterTaps [7]; + E /= 128; + Z++; + + Echo[SoundData.echo_ptr++] = (E * SoundData.echo_feedback) / 128 + EchoBuffer[J]; + + if (SoundData.echo_ptr >= SoundData.echo_buffer_size) + SoundData.echo_ptr = 0; + + I = (MixBuffer[J] * SoundData.master_volume [J & 1] + E * SoundData.echo_volume [J & 1]) / VOL_DIV16; + CLIP16(I); + + /* Apply low-pass filter */ + (*low_pass_sample) = ((*low_pass_sample) * low_pass_factor_a) + (I * low_pass_factor_b); + /* 16.16 fixed point */ + (*low_pass_sample) >>= 16; + + buffer[J] = (int16_t)(*low_pass_sample); + } + } + } + else + { + /* 16-bit mono or stereo sound, no echo */ + for (J = 0; J < sample_count; J++) + { + int32_t *low_pass_sample = &MixOutputPrev[J & 0x1]; + I = (MixBuffer[J] * SoundData.master_volume [J & 1]) / VOL_DIV16; + CLIP16(I); + + /* Apply low-pass filter */ + (*low_pass_sample) = ((*low_pass_sample) * low_pass_factor_a) + (I * low_pass_factor_b); + /* 16.16 fixed point */ + (*low_pass_sample) >>= 16; + + buffer[J] = (int16_t)(*low_pass_sample); + } + } +} + void S9xResetSound(bool full) { int32_t i; @@ -866,6 +964,8 @@ void S9xResetSound(bool full) SoundData.master_volume [0] = SoundData.master_volume [1] = 127; so.mute_sound = true; + + memset(MixOutputPrev, 0, sizeof(MixOutputPrev)); } void S9xSetPlaybackRate(uint32_t playback_rate) diff --git a/source/soundux.h b/source/soundux.h index 5cb4218..8a0e02e 100644 --- a/source/soundux.h +++ b/source/soundux.h @@ -135,6 +135,7 @@ void S9xFixEnvelope(int32_t channel, uint8_t gain, uint8_t adsr1, uint8_t adsr2) void S9xStartSample(int32_t channel); void S9xMixSamples(int16_t* buffer, int32_t sample_count); +void S9xMixSamplesLowPass(int16_t* buffer, int32_t sample_count, int32_t low_pass_range); void S9xSetPlaybackRate(uint32_t rate); #endif #endif -- cgit v1.2.3