Merge pull request #510 from jdgleaver/stereo-filter

This commit is contained in:
Autechre
2022-04-06 15:39:44 +01:00
committed by GitHub
2 changed files with 267 additions and 6 deletions

View File

@@ -804,6 +804,181 @@ static void palette_switch_set_index(uint32_t palette_index)
* Palette switching END
* ======================================== */
/* ========================================
* Stereo Filter START
* ======================================== */
enum stereo_filter_type
{
STEREO_FILTER_NULL = 0,
STEREO_FILTER_DELAY
};
static enum stereo_filter_type current_stereo_filter = STEREO_FILTER_NULL;
#define STEREO_FILTER_DELAY_MS_DEFAULT 15.0f;
typedef struct
{
int32_t *samples;
size_t samples_size;
size_t samples_pos;
size_t delay_count;
} stereo_filter_delay_t;
static stereo_filter_delay_t stereo_filter_delay;
static float stereo_filter_delay_ms = STEREO_FILTER_DELAY_MS_DEFAULT;
static void stereo_filter_apply_null(int32_t *sound_buffer, size_t size)
{
size_t i;
/* Each element of sound_buffer is a 16 bit mono sample
* stored in a 32 bit value. We convert this to stereo
* by copying the mono sample to both the high and low
* 16 bit regions of the value and casting sound_buffer
* to int16_t when uploading to the frontend */
for (i = 0; i < size; i++)
sound_buffer[i] = (sound_buffer[i] << 16) |
(sound_buffer[i] & 0xFFFF);
}
static void stereo_filter_apply_delay(int32_t *sound_buffer, size_t size)
{
size_t delay_capacity = stereo_filter_delay.samples_size -
stereo_filter_delay.samples_pos;
size_t i;
/* Copy current samples into the delay buffer
* (resizing if required) */
if (delay_capacity < size)
{
int32_t *tmp_buffer = NULL;
size_t tmp_buffer_size;
tmp_buffer_size = stereo_filter_delay.samples_size + (size - delay_capacity);
tmp_buffer_size = (tmp_buffer_size << 1) - (tmp_buffer_size >> 1);
tmp_buffer = (int32_t *)malloc(tmp_buffer_size * sizeof(int32_t));
memcpy(tmp_buffer, stereo_filter_delay.samples,
stereo_filter_delay.samples_pos * sizeof(int32_t));
free(stereo_filter_delay.samples);
stereo_filter_delay.samples = tmp_buffer;
stereo_filter_delay.samples_size = tmp_buffer_size;
}
for (i = 0; i < size; i++)
stereo_filter_delay.samples[i +
stereo_filter_delay.samples_pos] = sound_buffer[i];
stereo_filter_delay.samples_pos += size;
/* If we have enough samples in the delay
* buffer, mix them into the output */
if (stereo_filter_delay.samples_pos >
stereo_filter_delay.delay_count)
{
size_t delay_index = 0;
size_t samples_to_mix = stereo_filter_delay.samples_pos -
stereo_filter_delay.delay_count;
samples_to_mix = (samples_to_mix > size) ?
size : samples_to_mix;
/* Perform 'null' filtering for any samples for
* which a delay buffer entry is unavailable */
if (size > samples_to_mix)
for (i = 0; i < size - samples_to_mix; i++)
sound_buffer[i] = (sound_buffer[i] << 16) |
(sound_buffer[i] & 0xFFFF);
/* Each element of sound_buffer is a 16 bit mono sample
* stored in a 32 bit value. We convert this to stereo
* by copying the mono sample to the high (left channel)
* 16 bit region and the delayed sample to the low
* (right channel) region, casting sound_buffer
* to int16_t when uploading to the frontend */
for (i = size - samples_to_mix; i < size; i++)
sound_buffer[i] = (sound_buffer[i] << 16) |
(stereo_filter_delay.samples[delay_index++] & 0xFFFF);
/* Remove the mixed samples from the delay buffer */
memmove(stereo_filter_delay.samples,
stereo_filter_delay.samples + samples_to_mix,
(stereo_filter_delay.samples_pos - samples_to_mix) *
sizeof(int32_t));
stereo_filter_delay.samples_pos -= samples_to_mix;
}
/* Otherwise apply the regular 'null' filter */
else
for (i = 0; i < size; i++)
sound_buffer[i] = (sound_buffer[i] << 16) |
(sound_buffer[i] & 0xFFFF);
}
static void (*stereo_filter_apply)(int32_t *sound_buffer, size_t size) = stereo_filter_apply_null;
static void stereo_filter_deinit_delay(void)
{
if (stereo_filter_delay.samples)
free(stereo_filter_delay.samples);
stereo_filter_delay.samples = NULL;
stereo_filter_delay.samples_size = 0;
stereo_filter_delay.samples_pos = 0;
stereo_filter_delay.delay_count = 0;
}
static void stereo_filter_init_delay(void)
{
size_t initial_samples_size;
/* Convert delay (ms) to number of samples */
stereo_filter_delay.delay_count = (size_t)(
(stereo_filter_delay_ms / 1000.0f) *
(float)sndsamplerate);
/* Preallocate delay_count + worst case expected
* samples per frame to minimise reallocation of
* the samples buffer during runtime */
initial_samples_size = stereo_filter_delay.delay_count +
(size_t)((float)sndsamplerate / NES_PAL_FPS) + 1;
stereo_filter_delay.samples = (int32_t *)malloc(
initial_samples_size * sizeof(int32_t));
stereo_filter_delay.samples_size = initial_samples_size;
stereo_filter_delay.samples_pos = 0;
/* Assign function pointer */
stereo_filter_apply = stereo_filter_apply_delay;
}
static void stereo_filter_deinit(void)
{
/* Clean up */
stereo_filter_deinit_delay();
/* Assign default function pointer */
stereo_filter_apply = stereo_filter_apply_null;
}
static void stereo_filter_init(void)
{
stereo_filter_deinit();
/* Use a case statement to simplify matters
* if more filter types are added in the
* future... */
switch (current_stereo_filter)
{
case STEREO_FILTER_DELAY:
stereo_filter_init_delay();
break;
default:
break;
}
}
/* ========================================
* Stereo Filter END
* ======================================== */
#ifdef HAVE_NTSC_FILTER
/* ntsc */
#include "nes_ntsc.h"
@@ -1137,6 +1312,7 @@ static bool update_option_visibility(void)
"fceumm_sndvolume",
"fceumm_sndquality",
"fceumm_sndlowpass",
"fceumm_sndstereodelay",
"fceumm_swapduty",
"fceumm_apu_1",
"fceumm_apu_2",
@@ -1516,6 +1692,7 @@ void retro_deinit (void)
NTSCFilter_Cleanup();
#endif
palette_switch_deinit();
stereo_filter_deinit();
}
void retro_reset(void)
@@ -1537,6 +1714,7 @@ static void check_variables(bool startup)
struct retro_variable var = {0};
char key[256];
int i, enable_apu;
bool stereo_filter_updated = false;
/* 1 = Performs only geometry update: e.g. overscans */
/* 2 = Performs video/geometry update when needed and timing changes: e.g. region and filter change */
@@ -1831,6 +2009,45 @@ static void check_variables(bool startup)
FCEUI_SetLowPass(lowpass);
}
var.key = "fceumm_sndstereodelay";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
enum stereo_filter_type filter_type = STEREO_FILTER_NULL;
float filter_delay_ms = STEREO_FILTER_DELAY_MS_DEFAULT;
if (strcmp(var.value, "disabled") &&
(strlen(var.value) > 1))
{
char value_str[3];
value_str[0] = var.value[0];
value_str[1] = var.value[1];
value_str[2] = '\0';
filter_type = STEREO_FILTER_DELAY;
filter_delay_ms = (float)atoi(var.value);
filter_delay_ms = (filter_delay_ms < 1.0f) ?
1.0f : filter_delay_ms;
filter_delay_ms = (filter_delay_ms > 32.0f) ?
32.0f : filter_delay_ms;
}
if ((filter_type != current_stereo_filter) ||
((filter_type == STEREO_FILTER_DELAY) &&
(filter_delay_ms != stereo_filter_delay_ms)))
{
current_stereo_filter = filter_type;
stereo_filter_delay_ms = filter_delay_ms;
stereo_filter_updated = true;
}
}
if ((stereo_filter_updated ||
(audio_video_updated == 2)) && !startup)
stereo_filter_init();
var.key = "fceumm_sndvolume";
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
@@ -2365,12 +2582,10 @@ void retro_run(void)
FCEUD_UpdateInput();
FCEUI_Emulate(&gfx, &sound, &ssize, 0);
for (i = 0; i < ssize; i++)
sound[i] = (sound[i] << 16) | (sound[i] & 0xffff);
audio_batch_cb((const int16_t*)sound, ssize);
retro_run_blit(gfx);
stereo_filter_apply(sound, ssize);
audio_batch_cb((const int16_t*)sound, ssize);
}
size_t retro_serialize_size(void)
@@ -3025,6 +3240,7 @@ bool retro_load_game(const struct retro_game_info *info)
ResetPalette();
FCEUD_SoundToggle();
check_variables(true);
stereo_filter_init();
PowerNES();
FCEUI_DisableFourScore(1);

View File

@@ -21,7 +21,7 @@ extern "C" {
********************************
*/
#define MAX_CORE_OPTIONS 36
#define MAX_CORE_OPTIONS 37
/* RETRO_LANGUAGE_ENGLISH */
@@ -268,6 +268,51 @@ struct retro_core_option_v2_definition option_defs[] = {
},
"disabled",
},
{
"fceumm_sndstereodelay",
"Stereo Sound Effect",
NULL,
"Enable a fake stereo sound effect by delaying the right audio channel when upmixing the mono signal output from the NES.",
NULL,
"audio",
{
{ "disabled", NULL },
{ "01_ms_delay", "1ms Delay" },
{ "02_ms_delay", "2ms Delay" },
{ "03_ms_delay", "3ms Delay" },
{ "04_ms_delay", "4ms Delay" },
{ "05_ms_delay", "5ms Delay" },
{ "06_ms_delay", "6ms Delay" },
{ "07_ms_delay", "7ms Delay" },
{ "08_ms_delay", "8ms Delay" },
{ "09_ms_delay", "9ms Delay" },
{ "10_ms_delay", "10ms Delay" },
{ "11_ms_delay", "11ms Delay" },
{ "12_ms_delay", "12ms Delay" },
{ "13_ms_delay", "13ms Delay" },
{ "14_ms_delay", "14ms Delay" },
{ "15_ms_delay", "15ms Delay (Default)" },
{ "16_ms_delay", "16ms Delay" },
{ "17_ms_delay", "17ms Delay" },
{ "18_ms_delay", "18ms Delay" },
{ "19_ms_delay", "19ms Delay" },
{ "20_ms_delay", "20ms Delay" },
{ "21_ms_delay", "21ms Delay" },
{ "22_ms_delay", "22ms Delay" },
{ "23_ms_delay", "23ms Delay" },
{ "24_ms_delay", "24ms Delay" },
{ "25_ms_delay", "25ms Delay" },
{ "26_ms_delay", "26ms Delay" },
{ "27_ms_delay", "27ms Delay" },
{ "28_ms_delay", "28ms Delay" },
{ "29_ms_delay", "29ms Delay" },
{ "30_ms_delay", "30ms Delay" },
{ "31_ms_delay", "31ms Delay" },
{ "32_ms_delay", "32ms Delay" },
{ NULL, NULL },
},
"disabled",
},
{
"fceumm_swapduty",
"Swap Audio Duty Cycles",