# HG changeset patch # User Michael Pavone # Date 1522395428 25200 # Node ID 6ce36c3f250bdd3266c8a48aa5192f8b5c325715 # Parent 87350caf6dabf7e043d50c41240ff41f6f3e3208 More audio refactoring in preparation for allowing proper sync to video with dynamic audio rate control diff -r 87350caf6dab -r 6ce36c3f250b config.c --- a/config.c Thu Mar 29 00:40:41 2018 -0700 +++ b/config.c Fri Mar 30 00:37:08 2018 -0700 @@ -276,3 +276,10 @@ *num_exts_out = num_exts; return ext_list; } + +#define DEFAULT_LOWPASS_CUTOFF 3390 +uint32_t get_lowpass_cutoff(tern_node *config) +{ + char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval; + return lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : DEFAULT_LOWPASS_CUTOFF; +} diff -r 87350caf6dab -r 6ce36c3f250b config.h --- a/config.h Thu Mar 29 00:40:41 2018 -0700 +++ b/config.h Fri Mar 30 00:37:08 2018 -0700 @@ -14,6 +14,7 @@ uint8_t serialize_config_file(tern_node *config, char *path); void persist_config(tern_node *config); char **get_extension_list(tern_node *config, uint32_t *num_exts_out); +uint32_t get_lowpass_cutoff(tern_node *config); #endif //CONFIG_H_ diff -r 87350caf6dab -r 6ce36c3f250b genesis.c --- a/genesis.c Thu Mar 29 00:40:41 2018 -0700 +++ b/genesis.c Fri Mar 30 00:37:08 2018 -0700 @@ -1219,15 +1219,12 @@ gen->max_cycles = config_cycles ? atoi(config_cycles) : DEFAULT_SYNC_INTERVAL; gen->int_latency_prev1 = MCLKS_PER_68K * 32; gen->int_latency_prev2 = MCLKS_PER_68K * 16; - - char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval; - uint32_t lowpass_cutoff = lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : DEFAULT_LOWPASS_CUTOFF; gen->ym = malloc(sizeof(ym2612_context)); - ym_init(gen->ym, render_sample_rate(), gen->master_clock, MCLKS_PER_YM, render_audio_buffer(), system_opts, lowpass_cutoff); + ym_init(gen->ym, gen->master_clock, MCLKS_PER_YM, system_opts); gen->psg = malloc(sizeof(psg_context)); - psg_init(gen->psg, render_sample_rate(), gen->master_clock, MCLKS_PER_PSG, render_audio_buffer(), lowpass_cutoff); + psg_init(gen->psg, gen->master_clock, MCLKS_PER_PSG); gen->zram = calloc(1, Z80_RAM_BYTES); z80_map[0].buffer = gen->zram = calloc(1, Z80_RAM_BYTES); diff -r 87350caf6dab -r 6ce36c3f250b psg.c --- a/psg.c Thu Mar 29 00:40:41 2018 -0700 +++ b/psg.c Fri Mar 30 00:37:08 2018 -0700 @@ -10,19 +10,11 @@ #include #include #include -void psg_init(psg_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t samples_frame, uint32_t lowpass_cutoff) +void psg_init(psg_context * context, uint32_t master_clock, uint32_t clock_div) { memset(context, 0, sizeof(*context)); - context->audio = render_audio_source(1); - context->audio_buffer = render_audio_source_buffer(context->audio); + context->audio = render_audio_source(master_clock, clock_div, 1); context->clock_inc = clock_div; - context->sample_rate = sample_rate; - context->samples_frame = samples_frame; - double rc = (1.0 / (double)lowpass_cutoff) / (2.0 * M_PI); - double dt = 1.0 / ((double)master_clock / (double)clock_div); - double alpha = dt / (dt + rc); - context->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); - psg_adjust_master_clock(context, master_clock); for (int i = 0; i < 4; i++) { context->volume[i] = 0xF; } @@ -34,12 +26,9 @@ free(context); } -#define BUFFER_INC_RES 0x40000000UL - void psg_adjust_master_clock(psg_context * context, uint32_t master_clock) { - uint64_t old_inc = context->buffer_inc; - context->buffer_inc = ((BUFFER_INC_RES * (uint64_t)context->sample_rate) / (uint64_t)master_clock) * (uint64_t)context->clock_inc; + render_audio_adjust_clock(context->audio, master_clock, context->clock_inc); } void psg_write(psg_context * context, uint8_t value) @@ -117,34 +106,19 @@ } } - context->last_sample = context->accum; - context->accum = 0; + int16_t accum = 0; for (int i = 0; i < 3; i++) { if (context->output_state[i]) { - context->accum += volume_table[context->volume[i]]; + accum += volume_table[context->volume[i]]; } } if (context->noise_out) { - context->accum += volume_table[context->volume[3]]; + accum += volume_table[context->volume[3]]; } - int32_t tmp = context->accum * context->lowpass_alpha + context->last_sample * (0x10000 - context->lowpass_alpha); - context->accum = tmp >> 16; + + render_put_mono_sample(context->audio, accum); - context->buffer_fraction += context->buffer_inc; - while (context->buffer_fraction >= BUFFER_INC_RES) { - context->buffer_fraction -= BUFFER_INC_RES; - int32_t tmp = context->last_sample * ((context->buffer_fraction << 16) / context->buffer_inc); - tmp += context->accum * (0x10000 - ((context->buffer_fraction << 16) / context->buffer_inc)); - context->audio_buffer[context->buffer_pos++] = tmp >> 16; - - if (context->buffer_pos == context->samples_frame) { - if (!headless) { - context->audio_buffer = render_audio_ready(context->audio); - context->buffer_pos = 0; - } - } - } context->cycles += context->clock_inc; } } diff -r 87350caf6dab -r 6ce36c3f250b psg.h --- a/psg.h Thu Mar 29 00:40:41 2018 -0700 +++ b/psg.h Fri Mar 30 00:37:08 2018 -0700 @@ -11,21 +11,12 @@ #include "render.h" typedef struct { - int16_t *audio_buffer; audio_source *audio; - uint64_t buffer_fraction; - uint64_t buffer_inc; - uint32_t buffer_pos; uint32_t clock_inc; uint32_t cycles; - uint32_t sample_rate; - uint32_t samples_frame; - int32_t lowpass_alpha; uint16_t lsfr; uint16_t counter_load[4]; uint16_t counters[4]; - int16_t accum; - int16_t last_sample; uint8_t volume[4]; uint8_t output_state[4]; uint8_t noise_out; @@ -35,7 +26,7 @@ } psg_context; -void psg_init(psg_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t samples_frame, uint32_t lowpass_cutoff); +void psg_init(psg_context * context, uint32_t master_clock, uint32_t clock_div); void psg_free(psg_context *context); void psg_adjust_master_clock(psg_context * context, uint32_t master_clock); void psg_write(psg_context * context, uint8_t value); diff -r 87350caf6dab -r 6ce36c3f250b render.h --- a/render.h Thu Mar 29 00:40:41 2018 -0700 +++ b/render.h Fri Mar 30 00:37:08 2018 -0700 @@ -117,10 +117,10 @@ uint32_t render_elapsed_ms(void); void render_sleep_ms(uint32_t delay); uint8_t render_has_gl(void); -audio_source *render_audio_source(uint8_t channels); -int16_t *render_audio_source_buffer(audio_source *src); -int16_t *render_audio_ready(audio_source *src); -void render_reset_sources(void); +audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels); +void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider); +void render_put_mono_sample(audio_source *src, int16_t value); +void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right); void render_pause_source(audio_source *src); void render_resume_source(audio_source *src); void render_free_source(audio_source *src); diff -r 87350caf6dab -r 6ce36c3f250b render_sdl.c --- a/render_sdl.c Thu Mar 29 00:40:41 2018 -0700 +++ b/render_sdl.c Fri Mar 30 00:37:08 2018 -0700 @@ -15,6 +15,7 @@ #include "util.h" #include "ppm.h" #include "png.h" +#include "config.h" #ifndef DISABLE_OPENGL #include @@ -50,6 +51,12 @@ SDL_cond *cond; int16_t *front; int16_t *back; + uint64_t buffer_fraction; + uint64_t buffer_inc; + uint32_t buffer_pos; + uint32_t lowpass_alpha; + int16_t last_left; + int16_t last_right; uint8_t num_channels; uint8_t front_populated; }; @@ -161,7 +168,14 @@ SDL_CloseAudio(); } -audio_source *render_audio_source(uint8_t channels) +#define BUFFER_INC_RES 0x40000000UL + +void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) +{ + src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider; +} + +audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) { audio_source *ret = NULL; SDL_LockMutex(audio_mutex); @@ -177,6 +191,16 @@ SDL_UnlockMutex(audio_mutex); if (!ret) { fatal_error("Too many audio sources!"); + } else { + render_audio_adjust_clock(ret, master_clock, sample_divider); + double lowpass_cutoff = get_lowpass_cutoff(config); + double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); + double dt = 1.0 / ((double)master_clock / (double)(sample_divider)); + double alpha = dt / (dt + rc); + ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); + ret->buffer_pos = 0; + ret->buffer_fraction = 0; + ret->last_left = ret->last_right = 0; } return ret; } @@ -214,6 +238,71 @@ free(src); } +static void do_audio_ready(audio_source *src) +{ + SDL_LockMutex(audio_mutex); + while (src->front_populated) { + SDL_CondWait(src->cond, audio_mutex); + } + int16_t *tmp = src->front; + src->front = src->back; + src->back = tmp; + src->front_populated = 1; + src->buffer_pos = 0; + SDL_CondSignal(audio_ready); + SDL_UnlockMutex(audio_mutex); +} + +static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current) +{ + int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha); + current = tmp >> 16; + return current; +} + +static void interp_sample(audio_source *src, int16_t last, int16_t current) +{ + int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc); + tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc)); + src->back[src->buffer_pos++] = tmp >> 16; +} + +void render_put_mono_sample(audio_source *src, int16_t value) +{ + value = lowpass_sample(src, src->last_left, value); + src->buffer_fraction += src->buffer_inc; + while (src->buffer_fraction > BUFFER_INC_RES) + { + src->buffer_fraction -= BUFFER_INC_RES; + interp_sample(src, src->last_left, value); + + if (src->buffer_pos == buffer_samples) { + do_audio_ready(src); + } + } + src->last_left = value; +} + +void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) +{ + left = lowpass_sample(src, src->last_left, left); + right = lowpass_sample(src, src->last_right, right); + src->buffer_fraction += src->buffer_inc; + while (src->buffer_fraction > BUFFER_INC_RES) + { + src->buffer_fraction -= BUFFER_INC_RES; + + interp_sample(src, src->last_left, left); + interp_sample(src, src->last_right, right); + + if (src->buffer_pos == buffer_samples * 2) { + do_audio_ready(src); + } + } + src->last_left = left; + src->last_right = left; +} + static SDL_Joystick * joysticks[MAX_JOYSTICKS]; static int joystick_sdl_index[MAX_JOYSTICKS]; @@ -1300,26 +1389,6 @@ in_toggle = 0; } -int16_t *render_audio_source_buffer(audio_source *src) -{ - return src->back; -} - -int16_t *render_audio_ready(audio_source *src) -{ - SDL_LockMutex(audio_mutex); - while (src->front_populated) { - SDL_CondWait(src->cond, audio_mutex); - } - int16_t *tmp = src->front; - src->front = src->back; - src->back = tmp; - src->front_populated = 1; - SDL_CondSignal(audio_ready); - SDL_UnlockMutex(audio_mutex); - return src->back; -} - uint32_t render_audio_buffer() { return buffer_samples; diff -r 87350caf6dab -r 6ce36c3f250b sms.c --- a/sms.c Thu Mar 29 00:40:41 2018 -0700 +++ b/sms.c Fri Mar 30 00:37:08 2018 -0700 @@ -513,14 +513,11 @@ sms->bank_regs[3] = 0x8000 >> 14; } - char * lowpass_cutoff_str = tern_find_path(config, "audio\0lowpass_cutoff\0", TVAL_PTR).ptrval; - uint32_t lowpass_cutoff = lowpass_cutoff_str ? atoi(lowpass_cutoff_str) : 3390; - //TODO: Detect region and pick master clock based off of that sms->normal_clock = sms->master_clock = 53693175; sms->psg = malloc(sizeof(psg_context)); - psg_init(sms->psg, render_sample_rate(), sms->master_clock, 15*16, render_audio_buffer(), lowpass_cutoff); + psg_init(sms->psg, sms->master_clock, 15*16); sms->vdp = malloc(sizeof(vdp_context)); init_vdp_context(sms->vdp, 0); diff -r 87350caf6dab -r 6ce36c3f250b ym2612.c --- a/ym2612.c Thu Mar 29 00:40:41 2018 -0700 +++ b/ym2612.c Fri Mar 30 00:37:08 2018 -0700 @@ -116,12 +116,10 @@ } log_context = NULL; } -#define BUFFER_INC_RES 0x40000000UL void ym_adjust_master_clock(ym2612_context * context, uint32_t master_clock) { - uint64_t old_inc = context->buffer_inc; - context->buffer_inc = ((BUFFER_INC_RES * (uint64_t)context->sample_rate) / (uint64_t)master_clock) * (uint64_t)context->clock_inc * NUM_OPERATORS; + render_audio_adjust_clock(context->audio, master_clock, context->clock_inc * NUM_OPERATORS); } #ifdef __ANDROID__ @@ -163,23 +161,13 @@ } } -void ym_init(ym2612_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t sample_limit, uint32_t options, uint32_t lowpass_cutoff) +void ym_init(ym2612_context * context, uint32_t master_clock, uint32_t clock_div, uint32_t options) { static uint8_t registered_finalize; dfopen(debug_file, "ym_debug.txt", "w"); memset(context, 0, sizeof(*context)); - context->audio = render_audio_source(2); - context->audio_buffer = render_audio_source_buffer(context->audio); - context->sample_rate = sample_rate; context->clock_inc = clock_div * 6; - ym_adjust_master_clock(context, master_clock); - - double rc = (1.0 / (double)lowpass_cutoff) / (2.0 * M_PI); - double dt = 1.0 / ((double)master_clock / (double)(context->clock_inc * NUM_OPERATORS)); - double alpha = dt / (dt + rc); - context->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); - - context->sample_limit = sample_limit*2; + context->audio = render_audio_source(master_clock, context->clock_inc * NUM_OPERATORS, 2); //some games seem to expect that the LR flags start out as 1 for (int i = 0; i < NUM_CHANNELS; i++) { @@ -191,7 +179,7 @@ fprintf(stderr, "Failed to open WAVE log file %s for writing\n", fname); continue; } - if (!wave_init(f, sample_rate, 16, 1)) { + if (!wave_init(f, master_clock / (context->clock_inc * NUM_OPERATORS), 16, 1)) { fclose(f); context->channels[i].logfile = NULL; } @@ -604,7 +592,6 @@ if (context->current_op == NUM_OPERATORS) { context->current_op = 0; - context->buffer_fraction += context->buffer_inc; int16_t left = 0, right = 0; for (int i = 0; i < NUM_CHANNELS; i++) { int16_t value = context->channels[i].output; @@ -618,7 +605,7 @@ value |= 0xC000; } } - if (context->channels[i].logfile && context->buffer_fraction > BUFFER_INC_RES) { + if (context->channels[i].logfile) { fwrite(&value, sizeof(value), 1, context->channels[i].logfile); } if (context->channels[i].lr & 0x80) { @@ -628,31 +615,7 @@ right += (value * YM_VOLUME_MULTIPLIER) / YM_VOLUME_DIVIDER; } } - int32_t tmp = left * context->lowpass_alpha + context->last_left * (0x10000 - context->lowpass_alpha); - left = tmp >> 16; - tmp = right * context->lowpass_alpha + context->last_right * (0x10000 - context->lowpass_alpha); - right = tmp >> 16; - while (context->buffer_fraction > BUFFER_INC_RES) { - context->buffer_fraction -= BUFFER_INC_RES; - - int64_t tmp = context->last_left * ((context->buffer_fraction << 16) / context->buffer_inc); - tmp += left * (0x10000 - ((context->buffer_fraction << 16) / context->buffer_inc)); - context->audio_buffer[context->buffer_pos] = tmp >> 16; - - tmp = context->last_right * ((context->buffer_fraction << 16) / context->buffer_inc); - tmp += right * (0x10000 - ((context->buffer_fraction << 16) / context->buffer_inc)); - context->audio_buffer[context->buffer_pos+1] = tmp >> 16; - - context->buffer_pos += 2; - if (context->buffer_pos == context->sample_limit) { - if (!headless) { - context->audio_buffer = render_audio_ready(context->audio); - context->buffer_pos = 0; - } - } - } - context->last_left = left; - context->last_right = right; + render_put_stereo_sample(context->audio, left, right); } } diff -r 87350caf6dab -r 6ce36c3f250b ym2612.h --- a/ym2612.h Thu Mar 29 00:40:41 2018 -0700 +++ b/ym2612.h Fri Mar 30 00:37:08 2018 -0700 @@ -63,14 +63,8 @@ #define YM_PART2_REGS (YM_REG_END-YM_PART2_START) typedef struct { - int16_t *audio_buffer; audio_source *audio; - uint64_t buffer_fraction; - uint64_t buffer_inc; uint32_t clock_inc; - uint32_t buffer_pos; - uint32_t sample_rate; - uint32_t sample_limit; uint32_t current_cycle; //TODO: Condense the next two fields into one uint32_t write_cycle; @@ -82,8 +76,6 @@ uint16_t timer_a_load; uint16_t env_counter; ym_supp ch3_supp[3]; - int16_t last_left; - int16_t last_right; uint8_t timer_b; uint8_t sub_timer_b; uint8_t timer_b_load; @@ -132,7 +124,7 @@ REG_LR_AMS_PMS = 0xB4 }; -void ym_init(ym2612_context * context, uint32_t sample_rate, uint32_t master_clock, uint32_t clock_div, uint32_t sample_limit, uint32_t options, uint32_t lowpass_cutoff); +void ym_init(ym2612_context * context, uint32_t master_clock, uint32_t clock_div, uint32_t options); void ym_reset(ym2612_context *context); void ym_free(ym2612_context *context); void ym_adjust_master_clock(ym2612_context * context, uint32_t master_clock);