changeset 1555:6ce36c3f250b

More audio refactoring in preparation for allowing proper sync to video with dynamic audio rate control
author Michael Pavone <pavone@retrodev.com>
date Fri, 30 Mar 2018 00:37:08 -0700
parents 87350caf6dab
children 075df0844baa
files config.c config.h genesis.c psg.c psg.h render.h render_sdl.c sms.c ym2612.c ym2612.h
diffstat 10 files changed, 121 insertions(+), 130 deletions(-) [+]
line wrap: on
line diff
--- 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;
+}
--- 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_
 
--- 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);
--- 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 <stdlib.h>
 #include <stdio.h>
 #include <math.h>
-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;
 	}
 }
--- 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);
--- 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);
--- 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 <GL/glew.h>
@@ -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;
--- 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);
--- 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);
 		}
 		
 	}
--- 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);