changeset 1796:51417bb557b6

Configurable gain for overall output and individual components
author Michael Pavone <pavone@retrodev.com>
date Sat, 23 Mar 2019 17:18:10 -0700
parents a02b4ed940b6
children 5ff8f0d28188
files genesis.c nuklear_ui/blastem_nuklear.c render.h render_sdl.c sms.c
diffstat 5 files changed, 123 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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();
 		}
--- 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);
--- 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)
--- 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;