aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAutechre2022-04-01 14:01:50 +0100
committerGitHub2022-04-01 14:01:50 +0100
commit23f759bc4bf2e39733296f7749e446418e3cd0f3 (patch)
treebc3681918953c382d1bcc808eb8d6c9916f1c1bf
parent962e034447a49cf506940bb995228fd23424ef38 (diff)
parent8252e54e14489eb217af8bd40bc63a37b408abea (diff)
downloadsnes9x2005-23f759bc4bf2e39733296f7749e446418e3cd0f3.tar.gz
snes9x2005-23f759bc4bf2e39733296f7749e446418e3cd0f3.tar.bz2
snes9x2005-23f759bc4bf2e39733296f7749e446418e3cd0f3.zip
Merge pull request #96 from jdgleaver/low-pass-filter
Snes9x2005 Non-Plus: Add optional low pass audio filter
-rw-r--r--libretro.c48
-rw-r--r--libretro_core_options.h47
-rw-r--r--source/soundux.c126
-rw-r--r--source/soundux.h1
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 <retro_inline.h>
-#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