# HG changeset patch # User Michael Pavone # Date 1553386690 25200 # Node ID 51417bb557b625b2d200d9f20243f07dc39feb75 # Parent a02b4ed940b64a7c4c72e67497572dfbf119e5c2 Configurable gain for overall output and individual components diff -r a02b4ed940b6 -r 51417bb557b6 genesis.c --- a/genesis.c Sat Mar 23 00:05:37 2019 -0700 +++ b/genesis.c Sat Mar 23 17:18:10 2019 -0700 @@ -1334,10 +1334,20 @@ io_keyboard_up(&gen->io, scancode); } +static void set_gain_config(genesis_context *gen) +{ + char *config_gain; + config_gain = tern_find_path(config, "audio\0psg_gain\0", TVAL_PTR).ptrval; + render_audio_source_gaindb(gen->psg->audio, config_gain ? atof(config_gain) : 0.0f); + config_gain = tern_find_path(config, "audio\0fm_gain\0", TVAL_PTR).ptrval; + render_audio_source_gaindb(gen->ym->audio, config_gain ? atof(config_gain) : 0.0f); +} + static void config_updated(system_header *system) { genesis_context *gen = (genesis_context *)system; setup_io_devices(config, &system->info, &gen->io); + set_gain_config(gen); } genesis_context *alloc_init_genesis(rom_info *rom, void *main_rom, void *lock_on, uint32_t system_opts, uint8_t force_region) @@ -1391,6 +1401,8 @@ gen->psg = malloc(sizeof(psg_context)); psg_init(gen->psg, gen->master_clock, MCLKS_PER_PSG); + + set_gain_config(gen); z80_map[0].buffer = gen->zram = calloc(1, Z80_RAM_BYTES); #ifndef NO_Z80 diff -r a02b4ed940b6 -r 51417bb557b6 nuklear_ui/blastem_nuklear.c --- a/nuklear_ui/blastem_nuklear.c Sat Mar 23 00:05:37 2019 -0700 +++ b/nuklear_ui/blastem_nuklear.c Sat Mar 23 17:18:10 2019 -0700 @@ -1438,6 +1438,21 @@ } } +void settings_float_property(struct nk_context *context, char *label, char *name, char *path, float def, float min, float max, float step) +{ + char *curstr = tern_find_path(config, path, TVAL_PTR).ptrval; + float curval = curstr ? atof(curstr) : def; + nk_label(context, label, NK_TEXT_LEFT); + float val = curval; + nk_property_float(context, name, min, &val, max, step, step); + if (val != curval) { + char buffer[64]; + sprintf(buffer, "%f", val); + config_dirty = 1; + config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR); + } +} + typedef struct { char *fragment; char *vertex; @@ -1672,6 +1687,9 @@ selected_rate = settings_dropdown(context, "Rate in Hz", rates, num_rates, selected_rate, "audio\0rate\0"); selected_size = settings_dropdown(context, "Buffer Samples", sizes, num_sizes, selected_size, "audio\0buffer\0"); settings_int_input(context, "Lowpass Cutoff Hz", "audio\0lowpass_cutoff\0", "3390"); + settings_float_property(context, "Gain", "Overall", "audio\0gain\0", 0, -30.0f, 30.0f, 0.5f); + settings_float_property(context, "", "FM", "audio\0fm_gain\0", 0, -30.0f, 30.0f, 0.5f); + settings_float_property(context, "", "PSG", "audio\0psg_gain\0", 0, -30.0f, 30.0f, 0.5f); if (nk_button_label(context, "Back")) { pop_view(); } diff -r a02b4ed940b6 -r 51417bb557b6 render.h --- a/render.h Sat Mar 23 00:05:37 2019 -0700 +++ b/render.h Sat Mar 23 17:18:10 2019 -0700 @@ -131,6 +131,7 @@ void render_sleep_ms(uint32_t delay); uint8_t render_has_gl(void); audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels); +void render_audio_source_gaindb(audio_source *src, float gain); 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); diff -r a02b4ed940b6 -r 51417bb557b6 render_sdl.c --- a/render_sdl.c Sat Mar 23 00:05:37 2019 -0700 +++ b/render_sdl.c Sat Mar 23 17:18:10 2019 -0700 @@ -61,6 +61,7 @@ double dt; uint64_t buffer_fraction; uint64_t buffer_inc; + float gain_mult; uint32_t buffer_pos; uint32_t read_start; uint32_t read_end; @@ -78,77 +79,72 @@ static uint8_t num_inactive_audio_sources; static uint8_t sync_to_audio; static uint32_t min_buffered; +static float overall_gain_mult, *mix_buf; +static int sample_size; -typedef int32_t (*mix_func)(audio_source *audio, void *vstream, int len); +typedef void (*conv_func)(float *samples, void *vstream, int sample_count); + +static void convert_null(float *samples, void *vstream, int sample_count) +{ + memset(vstream, 0, sample_count * sample_size); +} -static int32_t mix_s16(audio_source *audio, void *vstream, int len) +static void convert_s16(float *samples, void *vstream, int sample_count) { - int samples = len/(sizeof(int16_t)*2); int16_t *stream = vstream; - int16_t *end = stream + output_channels*samples; + for (int16_t *end = stream + sample_count; stream < end; stream++, samples++) + { + float sample = *samples; + int16_t out_sample; + if (sample >= 1.0f) { + out_sample = 0x7FFF; + } else if (sample <= -1.0f) { + out_sample = -0x8000; + } else { + out_sample = sample * 0x7FFF; + } + *stream = out_sample; + } +} + +static void clamp_f32(float *samples, void *vstream, int sample_count) +{ + for (; sample_count > 0; sample_count--, samples++) + { + float sample = *samples; + if (sample > 1.0f) { + sample = 1.0f; + } else if (sample < -1.0f) { + sample = -1.0f; + } + *samples = sample; + } +} + +static int32_t mix_f32(audio_source *audio, float *stream, int samples) +{ + float *end = stream + samples; int16_t *src = audio->front; uint32_t i = audio->read_start; uint32_t i_end = audio->read_end; - int16_t *cur = stream; + float *cur = stream; + float gain_mult = audio->gain_mult * overall_gain_mult; size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; if (audio->num_channels == 1) { while (cur < end && i != i_end) { - *cur += src[i]; - cur += first_add; - *cur += src[i++]; - cur += second_add; - i &= audio->mask; - } - } else { - while (cur < end && i != i_end) - { - *cur += src[i++]; + *cur += gain_mult * ((float)src[i]) / 0x7FFF; cur += first_add; - *cur += src[i++]; - cur += second_add; - i &= audio->mask; - } - } - - if (cur != end) { - debug_message("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); - } - if (!sync_to_audio) { - audio->read_start = i; - } - if (cur != end) { - return (cur-end)/2; - } else { - return ((i_end - i) & audio->mask) / audio->num_channels; - } -} - -static int32_t mix_f32(audio_source *audio, void *vstream, int len) -{ - int samples = len/(sizeof(float)*2); - float *stream = vstream; - float *end = stream + 2*samples; - int16_t *src = audio->front; - uint32_t i = audio->read_start; - uint32_t i_end = audio->read_end; - float *cur = stream; - size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; - if (audio->num_channels == 1) { - while (cur < end && i != i_end) - { - *cur += ((float)src[i]) / 0x7FFF; - cur += first_add; - *cur += ((float)src[i++]) / 0x7FFF; + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += second_add; i &= audio->mask; } } else { while(cur < end && i != i_end) { - *cur += ((float)src[i++]) / 0x7FFF; + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += first_add; - *cur += ((float)src[i++]) / 0x7FFF; + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += second_add; i &= audio->mask; } @@ -164,17 +160,15 @@ } } -static int32_t mix_null(audio_source *audio, void *vstream, int len) -{ - return 0; -} - -static mix_func mix; +static conv_func convert; static void audio_callback(void * userdata, uint8_t *byte_stream, int len) { uint8_t num_populated; - memset(byte_stream, 0, len); + float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; + + int samples = len / sample_size; + memset(mix_dest, 0, samples * sizeof(float)); SDL_LockMutex(audio_mutex); do { num_populated = 0; @@ -192,12 +186,13 @@ if (!quitting) { for (uint8_t i = 0; i < num_audio_sources; i++) { - mix(audio_sources[i], byte_stream, len); + mix_f32(audio_sources[i], mix_dest, samples); audio_sources[i]->front_populated = 0; SDL_CondSignal(audio_sources[i]->cond); } } SDL_UnlockMutex(audio_mutex); + convert(mix_dest, byte_stream, samples); } #define NO_LAST_BUFFERED -2000000000 @@ -210,21 +205,24 @@ static uint32_t min_remaining_buffer; static void audio_callback_drc(void *userData, uint8_t *byte_stream, int len) { - memset(byte_stream, 0, len); if (cur_min_buffered < 0) { //underflow last frame, but main thread hasn't gotten a chance to call SDL_PauseAudio yet return; } cur_min_buffered = 0x7FFFFFFF; min_remaining_buffer = 0xFFFFFFFF; + float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; + int samples = len / sample_size; + memset(mix_dest, 0, samples * sizeof(float)); for (uint8_t i = 0; i < num_audio_sources; i++) { - int32_t buffered = mix(audio_sources[i], byte_stream, len); + int32_t buffered = mix_f32(audio_sources[i], mix_dest, samples); cur_min_buffered = buffered < cur_min_buffered ? buffered : cur_min_buffered; uint32_t remaining = (audio_sources[i]->mask + 1)/audio_sources[i]->num_channels - buffered; min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer; } + convert(mix_dest, byte_stream, samples); } static void lock_audio() @@ -250,6 +248,10 @@ SDL_LockMutex(audio_mutex); quitting = 1; SDL_CondSignal(audio_ready); + if (mix_buf) { + free(mix_buf); + mix_buf = NULL; + } SDL_UnlockMutex(audio_mutex); SDL_CloseAudio(); } @@ -298,6 +300,16 @@ return ret; } +static float db_to_mult(float gain) +{ + return powf(10.0f, gain/20.0f); +} + +void render_audio_source_gaindb(audio_source *src, float gain) +{ + src->gain_mult = db_to_mult(gain); +} + void render_pause_source(audio_source *src) { uint8_t need_pause = 0; @@ -1044,17 +1056,23 @@ sample_rate = actual.freq; output_channels = actual.channels; debug_message("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); + sample_size = SDL_AUDIO_BITSIZE(actual.format) / 8; if (actual.format == AUDIO_S16SYS) { debug_message("signed 16-bit int format"); - mix = mix_s16; + convert = convert_s16; + mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); } else if (actual.format == AUDIO_F32SYS) { debug_message("32-bit float format"); - mix = mix_f32; + convert = clamp_f32; + mix_buf = NULL; } else { debug_message("unsupported format %X\n", actual.format); warning("Unsupported audio sample format: %X\n", actual.format); - mix = mix_null; + convert = convert_null; + mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); } + char * gain_str = tern_find_path(config, "audio\0gain\0", TVAL_PTR).ptrval; + overall_gain_mult = db_to_mult(gain_str ? atof(gain_str) : 0.0f); } void window_setup(void) diff -r a02b4ed940b6 -r 51417bb557b6 sms.c --- a/sms.c Sat Mar 23 00:05:37 2019 -0700 +++ b/sms.c Sat Mar 23 17:18:10 2019 -0700 @@ -569,6 +569,13 @@ io_keyboard_up(&sms->io, scancode); } +static void set_gain_config(sms_context *sms) +{ + char *config_gain; + config_gain = tern_find_path(config, "audio\0psg_gain\0", TVAL_PTR).ptrval; + render_audio_source_gaindb(sms->psg->audio, config_gain ? atof(config_gain) : 0.0f); +} + static void config_updated(system_header *system) { sms_context *sms = (sms_context *)system; @@ -620,6 +627,8 @@ sms->psg = malloc(sizeof(psg_context)); psg_init(sms->psg, sms->master_clock, 15*16); + set_gain_config(sms); + sms->vdp = init_vdp_context(0); sms->vdp->system = &sms->header;