changeset 1867:fa4745d42093

Merge
author Michael Pavone <pavone@retrodev.com>
date Thu, 20 Jun 2019 23:28:18 -0700
parents 84f16a804ce5 (diff) 96323d73b8ab (current diff)
children bfacedbae5f0
files
diffstat 10 files changed, 621 insertions(+), 450 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sun Jun 02 23:16:15 2019 -0700
+++ b/Makefile	Thu Jun 20 23:28:18 2019 -0700
@@ -197,7 +197,7 @@
 AUDIOOBJS=ym2612.o psg.o wave.o
 CONFIGOBJS=config.o tern.o util.o paths.o 
 NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o
-RENDEROBJS=ppm.o controller_info.o
+RENDEROBJS=ppm.o controller_info.o render_audio.o
 ifdef USE_FBDEV
 RENDEROBJS+= render_fbdev.o
 else
--- a/psg.c	Sun Jun 02 23:16:15 2019 -0700
+++ b/psg.c	Thu Jun 20 23:28:18 2019 -0700
@@ -4,7 +4,6 @@
  BlastEm is free software distributed under the terms of the GNU General Public License version 3 or greater. See COPYING for full license text.
 */
 #include "psg.h"
-#include "render.h"
 #include "blastem.h"
 #include <string.h>
 #include <stdlib.h>
--- a/psg.h	Sun Jun 02 23:16:15 2019 -0700
+++ b/psg.h	Thu Jun 20 23:28:18 2019 -0700
@@ -8,7 +8,7 @@
 
 #include <stdint.h>
 #include "serialize.h"
-#include "render.h"
+#include "render_audio.h"
 
 typedef struct {
 	audio_source *audio;
--- a/render.h	Sun Jun 02 23:16:15 2019 -0700
+++ b/render.h	Thu Jun 20 23:28:18 2019 -0700
@@ -92,7 +92,6 @@
 #define RENDER_NOT_MAPPED -2
 #define RENDER_NOT_PLUGGED_IN -3
 
-typedef struct audio_source audio_source;
 typedef void (*drop_handler)(const char *filename);
 typedef void (*window_close_handler)(uint8_t which);
 typedef void (*ui_render_fun)(void);
@@ -109,9 +108,7 @@
 void render_set_video_standard(vid_std std);
 void render_toggle_fullscreen();
 void render_update_caption(char *title);
-void render_wait_quit(vdp_context * context);
-uint32_t render_audio_buffer();
-uint32_t render_sample_rate();
+void render_wait_quit(void);
 void process_events();
 int render_width();
 int render_height();
@@ -133,14 +130,6 @@
 uint32_t render_elapsed_ms(void);
 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);
-void render_pause_source(audio_source *src);
-void render_resume_source(audio_source *src);
-void render_free_source(audio_source *src);
 void render_config_updated(void);
 void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create);
 void render_set_ui_render_fun(ui_render_fun);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_audio.c	Thu Jun 20 23:28:18 2019 -0700
@@ -0,0 +1,375 @@
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include "render_audio.h"
+#include "util.h"
+#include "config.h"
+#include "blastem.h"
+
+static uint8_t output_channels;
+static uint32_t buffer_samples, sample_rate;
+static uint32_t missing_count;
+
+static audio_source *audio_sources[8];
+static audio_source *inactive_audio_sources[8];
+static uint8_t num_audio_sources;
+static uint8_t num_inactive_audio_sources;
+
+static float overall_gain_mult, *mix_buf;
+static int sample_size;
+
+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 void convert_s16(float *samples, void *vstream, int sample_count)
+{
+	int16_t *stream = vstream;
+	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;
+	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 += gain_mult * ((float)src[i]) / 0x7FFF;
+			cur += first_add;
+			*cur += gain_mult * ((float)src[i++]) / 0x7FFF;
+			cur += second_add;
+			i &= audio->mask;
+		}
+	} else {
+		while(cur < end && i != i_end)
+		{
+			*cur += gain_mult * ((float)src[i++]) / 0x7FFF;
+			cur += first_add;
+			*cur += gain_mult * ((float)src[i++]) / 0x7FFF;
+			cur += second_add;
+			i &= audio->mask;
+		}
+	}
+	if (!render_is_audio_sync()) {
+		audio->read_start = i;
+	}
+	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);
+		return (cur-end)/2;
+	} else {
+		return ((i_end - i) & audio->mask) / audio->num_channels;
+	}
+}
+
+static conv_func convert;
+
+
+int mix_and_convert(unsigned char *byte_stream, int len, int *min_remaining_out)
+{
+	int samples = len / sample_size;
+	float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream;
+	memset(mix_dest, 0, samples * sizeof(float));
+	int min_buffered = INT_MAX;
+	int min_remaining_buffer = INT_MAX;
+	for (uint8_t i = 0; i < num_audio_sources; i++)
+	{
+		int buffered = mix_f32(audio_sources[i], mix_dest, samples);
+		int remaining = (audio_sources[i]->mask + 1) / audio_sources[i]->num_channels - buffered;
+		min_buffered = buffered < min_buffered ? buffered : min_buffered;
+		min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer;
+		audio_sources[i]->front_populated = 0;
+		render_buffer_consumed(audio_sources[i]);
+	}
+	convert(mix_dest, byte_stream, samples);
+	if (min_remaining_out) {
+		*min_remaining_out = min_remaining_buffer;
+	}
+	return min_buffered;
+}
+
+uint8_t all_sources_ready(void)
+{
+	uint8_t num_populated = 0;
+	num_populated = 0;
+	for (uint8_t i = 0; i < num_audio_sources; i++)
+	{
+		if (audio_sources[i]->front_populated) {
+			num_populated++;
+		}
+	}
+	return num_populated == num_audio_sources;
+}
+
+#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;
+}
+
+void render_audio_adjust_speed(float adjust_ratio)
+{
+	for (uint8_t i = 0; i < num_audio_sources; i++)
+	{
+		audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5;
+	}
+}
+
+audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels)
+{
+	audio_source *ret = NULL;
+	uint32_t alloc_size = render_is_audio_sync() ? channels * buffer_samples : nearest_pow2(render_min_buffered() * 4 * channels);
+	render_lock_audio();
+		if (num_audio_sources < 8) {
+			ret = calloc(1, sizeof(audio_source));
+			ret->back = malloc(alloc_size * sizeof(int16_t));
+			ret->front = render_is_audio_sync() ? malloc(alloc_size * sizeof(int16_t)) : ret->back;
+			ret->front_populated = 0;
+			ret->opaque = render_new_audio_opaque();
+			ret->num_channels = channels;
+			audio_sources[num_audio_sources++] = ret;
+		}
+	render_unlock_audio();
+	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);
+		ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider));
+		double alpha = ret->dt / (ret->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;
+		ret->read_start = 0;
+		ret->read_end = render_is_audio_sync() ? buffer_samples * channels : 0;
+		ret->mask = render_is_audio_sync() ? 0xFFFFFFFF : alloc_size-1;
+		ret->gain_mult = 1.0f;
+	}
+	render_audio_created(ret);
+	
+	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 found = 0, remaining_sources;
+	render_lock_audio();
+		for (uint8_t i = 0; i < num_audio_sources; i++)
+		{
+			if (audio_sources[i] == src) {
+				audio_sources[i] = audio_sources[--num_audio_sources];
+				found = 1;
+				remaining_sources = num_audio_sources;
+				break;
+			}
+		}
+		
+	render_unlock_audio();
+	if (found) {
+		render_source_paused(src, remaining_sources);
+	}
+	inactive_audio_sources[num_inactive_audio_sources++] = src;
+}
+
+void render_resume_source(audio_source *src)
+{
+	render_lock_audio();
+		if (num_audio_sources < 8) {
+			audio_sources[num_audio_sources++] = src;
+		}
+	render_unlock_audio();
+	for (uint8_t i = 0; i < num_inactive_audio_sources; i++)
+	{
+		if (inactive_audio_sources[i] == src) {
+			inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources];
+		}
+	}
+	render_source_resumed(src);
+}
+
+void render_free_source(audio_source *src)
+{
+	render_pause_source(src);
+	
+	free(src->front);
+	if (render_is_audio_sync()) {
+		free(src->back);
+		render_free_audio_opaque(src->opaque);
+	}
+	free(src);
+	num_inactive_audio_sources--;
+}
+
+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;
+}
+
+static uint32_t sync_samples;
+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;
+	uint32_t base = render_is_audio_sync() ? 0 : src->read_end;
+	while (src->buffer_fraction > BUFFER_INC_RES)
+	{
+		src->buffer_fraction -= BUFFER_INC_RES;
+		interp_sample(src, src->last_left, value);
+		
+		if (((src->buffer_pos - base) & src->mask) >= sync_samples) {
+			render_do_audio_ready(src);
+		}
+		src->buffer_pos &= src->mask;
+	}
+	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;
+	uint32_t base = render_is_audio_sync() ? 0 : src->read_end;
+	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 - base) & src->mask)/2 >= sync_samples) {
+			render_do_audio_ready(src);
+		}
+		src->buffer_pos &= src->mask;
+	}
+	src->last_left = left;
+	src->last_right = right;
+}
+
+static void update_source(audio_source *src, double rc, uint8_t sync_changed)
+{
+	double alpha = src->dt / (src->dt + rc);
+	int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha);
+	src->lowpass_alpha = lowpass_alpha;
+	if (sync_changed) {
+		uint32_t alloc_size = render_is_audio_sync() ? src->num_channels * buffer_samples : nearest_pow2(render_min_buffered() * 4 * src->num_channels);
+		src->back = realloc(src->back, alloc_size * sizeof(int16_t));
+		if (render_is_audio_sync()) {
+			src->front = malloc(alloc_size * sizeof(int16_t));
+		} else {
+			free(src->front);
+			src->front = src->back;
+		}
+		src->mask = render_is_audio_sync() ? 0xFFFFFFFF : alloc_size-1;
+		src->read_start = 0;
+		src->read_end = render_is_audio_sync() ? buffer_samples * src->num_channels : 0;
+		src->buffer_pos = 0;
+	}
+}
+
+uint8_t old_audio_sync;
+void render_audio_initialized(render_audio_format format, uint32_t rate, uint8_t channels, uint32_t buffer_size, int sample_size_in)
+{
+	sample_rate = rate;
+	output_channels = channels;
+	buffer_samples = buffer_size;
+	sample_size = sample_size_in;
+	if (mix_buf) {
+		free(mix_buf);
+		mix_buf = NULL;
+	}
+	switch(format)
+	{
+	case RENDER_AUDIO_S16:
+		convert = convert_s16;
+		mix_buf = calloc(output_channels * buffer_samples, sizeof(float));
+		break;
+	case RENDER_AUDIO_FLOAT:
+		convert = clamp_f32;
+		break;
+	case RENDER_AUDIO_UNKNOWN:
+		convert = convert_null;
+		mix_buf = calloc(output_channels * buffer_samples, sizeof(float));
+		break;
+	}
+	uint32_t syncs = render_audio_syncs_per_sec();
+	if (syncs) {
+		sync_samples = rate / syncs;
+	} else {
+		sync_samples = buffer_samples;
+	}
+	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);
+	uint8_t sync_changed = old_audio_sync != render_is_audio_sync();
+	old_audio_sync = render_is_audio_sync();
+	double lowpass_cutoff = get_lowpass_cutoff(config);
+	double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI);
+	render_lock_audio();
+		for (uint8_t i = 0; i < num_audio_sources; i++)
+		{
+			update_source(audio_sources[i], rc, sync_changed);
+		}
+	render_unlock_audio();
+	for (uint8_t i = 0; i < num_inactive_audio_sources; i++)
+	{
+		update_source(inactive_audio_sources[i], rc, sync_changed);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/render_audio.h	Thu Jun 20 23:28:18 2019 -0700
@@ -0,0 +1,57 @@
+#ifndef RENDER_AUDIO_H_
+#define RENDER_AUDIO_H_
+
+#include <stdint.h>
+typedef enum {
+	RENDER_AUDIO_S16,
+	RENDER_AUDIO_FLOAT,
+	RENDER_AUDIO_UNKNOWN
+} render_audio_format;
+
+typedef struct {
+	void     *opaque;
+	int16_t  *front;
+	int16_t  *back;
+	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;
+	uint32_t lowpass_alpha;
+	uint32_t mask;
+	int16_t  last_left;
+	int16_t  last_right;
+	uint8_t  num_channels;
+	uint8_t  front_populated;
+} audio_source;
+
+//public interface
+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);
+void render_pause_source(audio_source *src);
+void render_resume_source(audio_source *src);
+void render_free_source(audio_source *src);
+//interface for render backends
+void render_audio_initialized(render_audio_format format, uint32_t rate, uint8_t channels, uint32_t buffer_size, int sample_size);
+int mix_and_convert(unsigned char *byte_stream, int len, int *min_remaining_out);
+uint8_t all_sources_ready(void);
+void render_audio_adjust_speed(float adjust_ratio);
+//to be implemented by render backend
+uint8_t render_is_audio_sync(void);
+void render_buffer_consumed(audio_source *src);
+void *render_new_audio_opaque(void);
+void render_free_audio_opaque(void *opaque);
+void render_lock_audio(void);
+void render_unlock_audio(void);
+uint32_t render_min_buffered(void);
+uint32_t render_audio_syncs_per_sec(void);
+void render_audio_created(audio_source *src);
+void render_do_audio_ready(audio_source *src);
+void render_source_paused(audio_source *src, uint8_t remaining_sources);
+void render_source_resumed(audio_source *src);
+#endif //RENDER_AUDIO_H_
--- a/render_sdl.c	Sun Jun 02 23:16:15 2019 -0700
+++ b/render_sdl.c	Thu Jun 20 23:28:18 2019 -0700
@@ -46,151 +46,41 @@
 
 static uint32_t last_frame = 0;
 
-static uint8_t output_channels;
-static uint32_t buffer_samples, sample_rate;
-static uint32_t missing_count;
-
 static SDL_mutex * audio_mutex;
 static SDL_cond * audio_ready;
 static uint8_t quitting = 0;
 
-struct audio_source {
-	SDL_cond *cond;
-	int16_t  *front;
-	int16_t  *back;
-	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;
-	uint32_t lowpass_alpha;
-	uint32_t mask;
-	int16_t  last_left;
-	int16_t  last_right;
-	uint8_t  num_channels;
-	uint8_t  front_populated;
-};
-
-static audio_source *audio_sources[8];
-static audio_source *inactive_audio_sources[8];
-static uint8_t num_audio_sources;
-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 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 void convert_s16(float *samples, void *vstream, int sample_count)
+uint32_t render_min_buffered(void)
 {
-	int16_t *stream = vstream;
-	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;
-	}
+	return min_buffered;
 }
 
-static void clamp_f32(float *samples, void *vstream, int sample_count)
+uint8_t render_is_audio_sync(void)
 {
-	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;
-	}
+	return sync_to_audio;
 }
 
-static int32_t mix_f32(audio_source *audio, float *stream, int samples)
+void render_buffer_consumed(audio_source *src)
 {
-	float *end = stream + samples;
-	int16_t *src = audio->front;
-	uint32_t i = audio->read_start;
-	uint32_t i_end = audio->read_end;
-	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 += gain_mult * ((float)src[i]) / 0x7FFF;
-			cur += first_add;
-			*cur += gain_mult * ((float)src[i++]) / 0x7FFF;
-			cur += second_add;
-			i &= audio->mask;
-		}
-	} else {
-		while(cur < end && i != i_end)
-		{
-			*cur += gain_mult * ((float)src[i++]) / 0x7FFF;
-			cur += first_add;
-			*cur += gain_mult * ((float)src[i++]) / 0x7FFF;
-			cur += second_add;
-			i &= audio->mask;
-		}
-	}
-	if (!sync_to_audio) {
-		audio->read_start = i;
-	}
-	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);
-		return (cur-end)/2;
-	} else {
-		return ((i_end - i) & audio->mask) / audio->num_channels;
-	}
+	SDL_CondSignal(src->opaque);
 }
 
-static conv_func convert;
-
 static void audio_callback(void * userdata, uint8_t *byte_stream, int len)
 {
-	uint8_t num_populated;
 	SDL_LockMutex(audio_mutex);
+		uint8_t all_ready;
 		do {
-			num_populated = 0;
-			for (uint8_t i = 0; i < num_audio_sources; i++)
-			{
-				if (audio_sources[i]->front_populated) {
-					num_populated++;
-				}
-			}
-			if (!quitting && num_populated < num_audio_sources) {
-				fflush(stdout);
+			all_ready = all_sources_ready();
+			if (!quitting && !all_ready) {
 				SDL_CondWait(audio_ready, audio_mutex);
 			}
-		} while(!quitting && num_populated < num_audio_sources);
-		int samples = len / sample_size;
-		float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream;
-		memset(mix_dest, 0, samples * sizeof(float));
+		} while(!quitting && !all_ready);
 		if (!quitting) {
-			for (uint8_t i = 0; i < num_audio_sources; i++)
-			{
-				mix_f32(audio_sources[i], mix_dest, samples);
-				audio_sources[i]->front_populated = 0;
-				SDL_CondSignal(audio_sources[i]->cond);
-			}
+			mix_and_convert(byte_stream, len, NULL);
 		}
-		convert(mix_dest, byte_stream, samples);
 	SDL_UnlockMutex(audio_mutex);
 }
 
@@ -208,23 +98,10 @@
 		//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_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);
+	cur_min_buffered = mix_and_convert(byte_stream, len, &min_remaining_buffer);
 }
 
-static void lock_audio()
+void render_lock_audio()
 {
 	if (sync_to_audio) {
 		SDL_LockMutex(audio_mutex);
@@ -233,7 +110,7 @@
 	}
 }
 
-static void unlock_audio()
+void render_unlock_audio()
 {
 	if (sync_to_audio) {
 		SDL_UnlockMutex(audio_mutex);
@@ -249,127 +126,55 @@
 		SDL_CondSignal(audio_ready);
 	SDL_UnlockMutex(audio_mutex);
 	SDL_CloseAudio();
+	/*
+	FIXME: move this to render_audio.c
 	if (mix_buf) {
 		free(mix_buf);
 		mix_buf = NULL;
 	}
+	*/
 }
 
-#define BUFFER_INC_RES 0x40000000UL
-
-void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider)
+void *render_new_audio_opaque(void)
 {
-	src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider;
+	return SDL_CreateCond();
 }
 
-audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels)
+void render_free_audio_opaque(void *opaque)
 {
-	audio_source *ret = NULL;
-	uint32_t alloc_size = sync_to_audio ? channels * buffer_samples : nearest_pow2(min_buffered * 4 * channels);
-	lock_audio();
-		if (num_audio_sources < 8) {
-			ret = malloc(sizeof(audio_source));
-			ret->back = malloc(alloc_size * sizeof(int16_t));
-			ret->front = sync_to_audio ? malloc(alloc_size * sizeof(int16_t)) : ret->back;
-			ret->front_populated = 0;
-			ret->cond = SDL_CreateCond();
-			ret->num_channels = channels;
-			audio_sources[num_audio_sources++] = ret;
-		}
-	unlock_audio();
-	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);
-		ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider));
-		double alpha = ret->dt / (ret->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;
-		ret->read_start = 0;
-		ret->read_end = sync_to_audio ? buffer_samples * channels : 0;
-		ret->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1;
-		ret->gain_mult = 1.0f;
-	}
+	SDL_DestroyCond(opaque);
+}
+
+void render_audio_created(audio_source *source)
+{
 	if (sync_to_audio && SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) {
 		SDL_PauseAudio(0);
 	}
-	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)
+void render_source_paused(audio_source *src, uint8_t remaining_sources)
 {
-	uint8_t need_pause = 0;
-	lock_audio();
-		for (uint8_t i = 0; i < num_audio_sources; i++)
-		{
-			if (audio_sources[i] == src) {
-				audio_sources[i] = audio_sources[--num_audio_sources];
-				if (sync_to_audio) {
-					SDL_CondSignal(audio_ready);
-				}
-				break;
-			}
-		}
-		if (!num_audio_sources) {
-			need_pause = 1;
-		}
-	unlock_audio();
-	if (need_pause) {
-		SDL_PauseAudio(1);
+	if (sync_to_audio) {
+		SDL_CondSignal(audio_ready);
 	}
-	inactive_audio_sources[num_inactive_audio_sources++] = src;
+	if (!remaining_sources) {
+		SDL_PauseAudio(0);
+	}
 }
 
-void render_resume_source(audio_source *src)
+void render_source_resumed(audio_source *src)
 {
-	lock_audio();
-		if (num_audio_sources < 8) {
-			audio_sources[num_audio_sources++] = src;
-		}
-	unlock_audio();
-	for (uint8_t i = 0; i < num_inactive_audio_sources; i++)
-	{
-		if (inactive_audio_sources[i] == src) {
-			inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources];
-		}
-	}
 	if (sync_to_audio) {
 		SDL_PauseAudio(0);
 	}
 }
 
-void render_free_source(audio_source *src)
-{
-	render_pause_source(src);
-	
-	free(src->front);
-	if (sync_to_audio) {
-		free(src->back);
-		SDL_DestroyCond(src->cond);
-	}
-	free(src);
-}
-static uint32_t sync_samples;
-static void do_audio_ready(audio_source *src)
+void render_do_audio_ready(audio_source *src)
 {
 	if (sync_to_audio) {
 		SDL_LockMutex(audio_mutex);
 			while (src->front_populated) {
-				SDL_CondWait(src->cond, audio_mutex);
+				SDL_CondWait(src->opaque, audio_mutex);
 			}
 			int16_t *tmp = src->front;
 			src->front = src->back;
@@ -390,60 +195,6 @@
 	}
 }
 
-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;
-	uint32_t base = sync_to_audio ? 0 : src->read_end;
-	while (src->buffer_fraction > BUFFER_INC_RES)
-	{
-		src->buffer_fraction -= BUFFER_INC_RES;
-		interp_sample(src, src->last_left, value);
-		
-		if (((src->buffer_pos - base) & src->mask) >= sync_samples) {
-			do_audio_ready(src);
-		}
-		src->buffer_pos &= src->mask;
-	}
-	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;
-	uint32_t base = sync_to_audio ? 0 : src->read_end;
-	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 - base) & src->mask)/2 >= sync_samples) {
-			do_audio_ready(src);
-		}
-		src->buffer_pos &= src->mask;
-	}
-	src->last_left = left;
-	src->last_right = right;
-}
-
 static SDL_Joystick * joysticks[MAX_JOYSTICKS];
 static int joystick_sdl_index[MAX_JOYSTICKS];
 static uint8_t joystick_index_locked[MAX_JOYSTICKS];
@@ -1113,6 +864,7 @@
 static int source_frame_count;
 static int frame_repeat[60];
 
+static uint32_t sample_rate;
 static void init_audio()
 {
 	SDL_AudioSpec desired, actual;
@@ -1137,27 +889,20 @@
 	if (SDL_OpenAudio(&desired, &actual) < 0) {
 		fatal_error("Unable to open SDL audio: %s\n", SDL_GetError());
 	}
-	buffer_samples = actual.samples;
 	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;
+	render_audio_format format = RENDER_AUDIO_UNKNOWN;
 	if (actual.format == AUDIO_S16SYS) {
 		debug_message("signed 16-bit int format\n");
-		convert = convert_s16;
-		mix_buf = calloc(output_channels * buffer_samples, sizeof(float));
+		format = RENDER_AUDIO_S16;
 	} else if (actual.format == AUDIO_F32SYS) {
 		debug_message("32-bit float format\n");
-		convert = clamp_f32;
-		mix_buf = NULL;
+		format = RENDER_AUDIO_FLOAT;
 	} else {
 		debug_message("unsupported format %X\n", actual.format);
 		warning("Unsupported audio sample format: %X\n", actual.format);
-		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);
+	render_audio_initialized(format, actual.freq, actual.channels, actual.samples, SDL_AUDIO_BITSIZE(actual.format) / 8);
 }
 
 void window_setup(void)
@@ -1352,26 +1097,6 @@
 }
 #include<unistd.h>
 static int in_toggle;
-static void update_source(audio_source *src, double rc, uint8_t sync_changed)
-{
-	double alpha = src->dt / (src->dt + rc);
-	int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha);
-	src->lowpass_alpha = lowpass_alpha;
-	if (sync_changed) {
-		uint32_t alloc_size = sync_to_audio ? src->num_channels * buffer_samples : nearest_pow2(min_buffered * 4 * src->num_channels);
-		src->back = realloc(src->back, alloc_size * sizeof(int16_t));
-		if (sync_to_audio) {
-			src->front = malloc(alloc_size * sizeof(int16_t));
-		} else {
-			free(src->front);
-			src->front = src->back;
-		}
-		src->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1;
-		src->read_start = 0;
-		src->read_end = sync_to_audio ? buffer_samples * src->num_channels : 0;
-		src->buffer_pos = 0;
-	}
-}
 
 void render_config_updated(void)
 {
@@ -1438,18 +1163,6 @@
 	init_audio();
 	render_set_video_standard(video_standard);
 	
-	double lowpass_cutoff = get_lowpass_cutoff(config);
-	double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI);
-	lock_audio();
-		for (uint8_t i = 0; i < num_audio_sources; i++)
-		{
-			update_source(audio_sources[i], rc, old_sync_to_audio != sync_to_audio);
-		}
-	unlock_audio();
-	for (uint8_t i = 0; i < num_inactive_audio_sources; i++)
-	{
-		update_source(inactive_audio_sources[i], rc, old_sync_to_audio != sync_to_audio);
-	}
 	drain_events();
 	in_toggle = 0;
 	if (!was_paused) {
@@ -1462,6 +1175,12 @@
 	return main_window;
 }
 
+uint32_t render_audio_syncs_per_sec(void)
+{
+	//sync samples with audio thread approximately every 8 lines when doing sync to video
+	return sync_to_audio ? 0 : source_hz * (video_standard == VID_PAL ? 313 : 262) / 8;
+}
+
 void render_set_video_standard(vid_std std)
 {
 	video_standard = std;
@@ -1491,8 +1210,6 @@
 	}
 	source_frame = 0;
 	source_frame_count = frame_repeat[0];
-	//sync samples with audio thread approximately every 8 lines
-	sync_samples = sync_to_audio ? buffer_samples : 8 * sample_rate / (source_hz * (std == VID_PAL ? 313 : 262));
 	max_repeat++;
 	min_buffered = (((float)max_repeat * (float)sample_rate/(float)source_hz)/* / (float)buffer_samples*/);// + 0.9999;
 	//min_buffered *= buffer_samples;
@@ -1808,10 +1525,8 @@
 		}
 		if (adjust_ratio != 0.0f) {
 			average_change = 0;
-			for (uint8_t i = 0; i < num_audio_sources; i++)
-			{
-				audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5;
-			}
+			render_audio_adjust_speed(adjust_ratio);
+			
 		}
 		while (source_frame_count > 0)
 		{
@@ -1909,7 +1624,7 @@
 	return overscan_top[video_standard];
 }
 
-void render_wait_quit(vdp_context * context)
+void render_wait_quit(void)
 {
 	SDL_Event event;
 	while(SDL_WaitEvent(&event)) {
@@ -2077,16 +1792,6 @@
 	need_ui_fb_resize = 1;
 }
 
-uint32_t render_audio_buffer()
-{
-	return buffer_samples;
-}
-
-uint32_t render_sample_rate()
-{
-	return sample_rate;
-}
-
 void render_errorbox(char *title, char *message)
 {
 	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, message, NULL);
--- a/vdp.c	Sun Jun 02 23:16:15 2019 -0700
+++ b/vdp.c	Thu Jun 20 23:28:18 2019 -0700
@@ -144,7 +144,7 @@
 		context->cur_buffer = FRAMEBUFFER_ODD;
 		context->fb = render_get_framebuffer(FRAMEBUFFER_ODD, &context->output_pitch);
 	}
-	context->sprite_draws = MAX_DRAWS;
+	context->sprite_draws = MAX_SPRITES_LINE;
 	context->fifo_write = 0;
 	context->fifo_read = -1;
 	context->regs[REG_HINT] = context->hint_counter = 0xFF;
@@ -269,40 +269,63 @@
 
 static void render_sprite_cells(vdp_context * context)
 {
+	if (context->cur_slot > MAX_SPRITES_LINE) {
+		context->cur_slot--;
+		return;
+	}
+	if (context->cur_slot < 0) {
+		return;
+	}
 	sprite_draw * d = context->sprite_draw_list + context->cur_slot;
 	context->serial_address = d->address;
-	if (context->cur_slot >= context->sprite_draws) {
-
-		uint16_t dir;
-		int16_t x;
-		if (d->h_flip) {
-			x = d->x_pos + 7;
-			dir = -1;
-		} else {
-			x = d->x_pos;
-			dir = 1;
+	uint16_t dir;
+	int16_t x;
+	if (d->h_flip) {
+		x = d->x_pos + 7 + 8 * (d->width - 1);
+		dir = -1;
+	} else {
+		x = d->x_pos;
+		dir = 1;
+	}
+	if (d->x_pos) {
+		context->flags |= FLAG_CAN_MASK;
+		if (!(context->flags & FLAG_MASKED)) {
+			x -= 128;
+			//printf("Draw Slot %d of %d, Rendering sprite cell from %X to x: %d\n", context->cur_slot, context->sprite_draws, d->address, x);
+			
+			for (uint16_t address = d->address; address != ((d->address+4) & 0xFFFF); address++) {
+				if (x >= 0 && x < 320) {
+					if (!(context->linebuf[x] & 0xF)) {
+						context->linebuf[x] = (context->vdpmem[address] >> 4) | d->pal_priority;
+					} else if (context->vdpmem[address] >> 4) {
+						context->flags2 |= FLAG2_SPRITE_COLLIDE;
+					}
+				}
+				x += dir;
+				if (x >= 0 && x < 320) {
+					if (!(context->linebuf[x] & 0xF)) {
+						context->linebuf[x] = (context->vdpmem[address] & 0xF)  | d->pal_priority;
+					} else if (context->vdpmem[address] & 0xF) {
+						context->flags2 |= FLAG2_SPRITE_COLLIDE;
+					}
+				}
+				x += dir;
+			}
 		}
-		//printf("Draw Slot %d of %d, Rendering sprite cell from %X to x: %d\n", context->cur_slot, context->sprite_draws, d->address, x);
-		context->cur_slot--;
-		for (uint16_t address = d->address; address != ((d->address+4) & 0xFFFF); address++) {
-			if (x >= 0 && x < 320) {
-				if (!(context->linebuf[x] & 0xF)) {
-					context->linebuf[x] = (context->vdpmem[address] >> 4) | d->pal_priority;
-				} else if (context->vdpmem[address] >> 4) {
-					context->flags2 |= FLAG2_SPRITE_COLLIDE;
-				}
-			}
-			x += dir;
-			if (x >= 0 && x < 320) {
-				if (!(context->linebuf[x] & 0xF)) {
-					context->linebuf[x] = (context->vdpmem[address] & 0xF)  | d->pal_priority;
-				} else if (context->vdpmem[address] & 0xF) {
-					context->flags2 |= FLAG2_SPRITE_COLLIDE;
-				}
-			}
-			x += dir;
+	} else if (context->flags & FLAG_CAN_MASK) {
+		context->flags |= FLAG_MASKED;
+		context->flags &= ~FLAG_CAN_MASK;
+	}
+	if (d->width) {
+		d->width--;
+	}
+	if (d->width) {
+		d->address += d->height * 4;
+		if (!d->h_flip) {
+			d->x_pos += 8;
 		}
 	} else {
+		d->x_pos = 0;
 		context->cur_slot--;
 	}
 }
@@ -695,47 +718,13 @@
 			} else {
 				address = ((tileinfo & 0x7FF) << 5) + row * 4;
 			}
-			int16_t x = ((context->vdpmem[att_addr+ 2] & 0x3) << 8 | context->vdpmem[att_addr + 3]) & 0x1FF;
-			if (x) {
-				context->flags |= FLAG_CAN_MASK;
-			} else if(context->flags & (FLAG_CAN_MASK | FLAG_DOT_OFLOW)) {
-				context->flags |= FLAG_MASKED;
-			}
-
-			context->flags &= ~FLAG_DOT_OFLOW;
-			int16_t i;
-			if (context->flags & FLAG_MASKED) {
-				for (i=0; i < width && context->sprite_draws; i++) {
-					--context->sprite_draws;
-					context->sprite_draw_list[context->sprite_draws].x_pos = -128;
-					context->sprite_draw_list[context->sprite_draws].address = address + i * height * 4;
-				}
-			} else {
-				x -= 128;
-				int16_t base_x = x;
-				int16_t dir;
-				if (tileinfo & MAP_BIT_H_FLIP) {
-					x += (width-1) * 8;
-					dir = -8;
-				} else {
-					dir = 8;
-				}
-				//printf("Sprite %d | x: %d, y: %d, width: %d, height: %d, pal_priority: %X, row: %d, tile addr: %X\n", context->sprite_info_list[context->cur_slot].index, x, context->sprite_info_list[context->cur_slot].y, width, height, pal_priority, row, address);
-				for (i=0; i < width && context->sprite_draws; i++, x += dir) {
-					--context->sprite_draws;
-					context->sprite_draw_list[context->sprite_draws].address = address + i * height * 4;
-					context->sprite_draw_list[context->sprite_draws].x_pos = x;
-					context->sprite_draw_list[context->sprite_draws].pal_priority = pal_priority;
-					context->sprite_draw_list[context->sprite_draws].h_flip = (tileinfo & MAP_BIT_H_FLIP) ? 1 : 0;
-				}
-			}
-			//Used to be i < width
-			//TODO: Confirm this is the right condition on hardware
-			if (!context->sprite_draws) {
-				context->flags |= FLAG_DOT_OFLOW;
-			}
-		} else {
-			context->flags |= FLAG_DOT_OFLOW;
+			context->sprite_draws--;
+			context->sprite_draw_list[context->sprite_draws].x_pos = ((context->vdpmem[att_addr+ 2] & 0x3) << 8 | context->vdpmem[att_addr + 3]) & 0x1FF;
+			context->sprite_draw_list[context->sprite_draws].address = address;
+			context->sprite_draw_list[context->sprite_draws].pal_priority = pal_priority;
+			context->sprite_draw_list[context->sprite_draws].h_flip = (tileinfo & MAP_BIT_H_FLIP) ? 1 : 0;
+			context->sprite_draw_list[context->sprite_draws].width = width;
+			context->sprite_draw_list[context->sprite_draws].height = height;
 		}
 	}
 	context->cur_slot++;
@@ -2581,6 +2570,9 @@
 		CHECK_LIMIT
 	SPRITE_RENDER_H40(254)
 	case 255:
+		if (context->cur_slot >= 0 && context->sprite_draw_list[context->cur_slot].x_pos) {
+			context->flags |= FLAG_DOT_OFLOW;
+		}
 		render_map_3(context);
 		scan_sprite_table(context->vcounter, context);//Just a guess
 		CHECK_LIMIT
@@ -2592,8 +2584,7 @@
 		//so we set cur_slot to slot_counter and let it wrap around to
 		//the beginning of the list
 		context->cur_slot = context->slot_counter;
-		context->sprite_draws = MAX_DRAWS;
-		context->flags &= (~FLAG_CAN_MASK & ~FLAG_MASKED);
+		context->sprite_draws = MAX_SPRITES_LINE;
 		CHECK_LIMIT
 	COLUMN_RENDER_BLOCK(2, 1)
 	COLUMN_RENDER_BLOCK(4, 9)
@@ -2626,7 +2617,7 @@
 	//sprite render to line buffer starts
 	case 163:
 		OUTPUT_PIXEL(163)
-		context->cur_slot = MAX_DRAWS-1;
+		context->cur_slot = MAX_SPRITES_LINE-1;
 		memset(context->linebuf, 0, LINEBUF_SIZE);
 		render_border_garbage(
 			context,
@@ -2634,6 +2625,7 @@
 			context->tmp_buf_a, context->buf_a_off,
 			context->col_1
 		);
+		context->flags &= ~FLAG_MASKED;
 		render_sprite_cells(context);
 		CHECK_LIMIT
 	case 164:
@@ -2782,6 +2774,9 @@
 		CHECK_LIMIT
 	SPRITE_RENDER_H32(250)
 	case 251:
+		if (context->cur_slot >= 0 && context->sprite_draw_list[context->cur_slot].x_pos) {
+			context->flags |= FLAG_DOT_OFLOW;
+		}
 		render_map_1(context);
 		scan_sprite_table(context->vcounter, context);//Just a guess
 		CHECK_LIMIT
@@ -2807,8 +2802,7 @@
 		//filled rather than the number of available slots
 		//context->slot_counter = MAX_SPRITES_LINE - context->slot_counter;
 		context->cur_slot = context->slot_counter;
-		context->sprite_draws = MAX_DRAWS_H32;
-		context->flags &= (~FLAG_CAN_MASK & ~FLAG_MASKED);
+		context->sprite_draws = MAX_SPRITES_LINE_H32;
 		CHECK_LIMIT
 	COLUMN_RENDER_BLOCK(2, 1)
 	COLUMN_RENDER_BLOCK(4, 9)
@@ -2838,7 +2832,7 @@
 	//sprite render to line buffer starts
 	case 131:
 		OUTPUT_PIXEL(131)
-		context->cur_slot = MAX_DRAWS_H32-1;
+		context->cur_slot = MAX_SPRITES_LINE_H32-1;
 		memset(context->linebuf, 0, LINEBUF_SIZE);
 		render_border_garbage(
 			context,
@@ -2846,6 +2840,7 @@
 			context->tmp_buf_a, context->buf_a_off,
 			context->col_1
 		);
+		context->flags &= ~FLAG_MASKED;
 		render_sprite_cells(context);
 		CHECK_LIMIT
 	case 132:
@@ -3072,7 +3067,7 @@
 			buf_clear_slot = 163;
 			index_reset_slot = 167;
 			bg_end_slot = BG_START_SLOT + LINEBUF_SIZE/2;
-			max_draws = MAX_DRAWS-1;
+			max_draws = MAX_SPRITES_LINE-1;
 			max_sprites = MAX_SPRITES_LINE;
 			index_reset_value = 0x80;
 			vint_slot = VINT_SLOT_H40;
@@ -3081,7 +3076,7 @@
 			jump_dest = 229;
 		} else {
 			bg_end_slot = BG_START_SLOT + (256+HORIZ_BORDER)/2;
-			max_draws = MAX_DRAWS_H32-1;
+			max_draws = MAX_SPRITES_LINE_H32-1;
 			max_sprites = MAX_SPRITES_LINE_H32;
 			buf_clear_slot = 128;
 			index_reset_slot = 132;
@@ -3934,8 +3929,10 @@
 	}
 }
 
+#define VDP_STATE_VERSION 1
 void vdp_serialize(vdp_context *context, serialize_buffer *buf)
 {
+	save_int8(buf, VDP_STATE_VERSION);
 	save_int8(buf, VRAM_SIZE / 1024);//VRAM size in KB, needed for future proofing
 	save_buffer8(buf, context->vdpmem, VRAM_SIZE);
 	save_buffer16(buf, context->cram, CRAM_SIZE);
@@ -3990,13 +3987,15 @@
 	save_int8(buf, context->sprite_draws);
 	save_int8(buf, context->slot_counter);
 	save_int8(buf, context->cur_slot);
-	for (int i = 0; i < MAX_DRAWS; i++)
+	for (int i = 0; i < MAX_SPRITES_LINE; i++)
 	{
 		sprite_draw *draw = context->sprite_draw_list + i;
 		save_int16(buf, draw->address);
 		save_int16(buf, draw->x_pos);
 		save_int8(buf, draw->pal_priority);
 		save_int8(buf, draw->h_flip);
+		save_int8(buf, draw->width);
+		save_int8(buf, draw->height);
 	}
 	for (int i = 0; i < MAX_SPRITES_LINE; i++)
 	{
@@ -4015,7 +4014,17 @@
 void vdp_deserialize(deserialize_buffer *buf, void *vcontext)
 {
 	vdp_context *context = vcontext;
-	uint8_t vramk = load_int8(buf);
+	uint8_t version = load_int8(buf);
+	uint8_t vramk;
+	if (version == 64) {
+		vramk = version;
+		version = 0;
+	} else {
+		vramk = load_int8(buf);
+	}
+	if (version > VDP_STATE_VERSION) {
+		warning("Save state has VDP version %d, but this build only understands versions %d and lower", version, VDP_STATE_VERSION);
+	}
 	load_buffer8(buf, context->vdpmem, (vramk * 1024) <= VRAM_SIZE ? vramk * 1024 : VRAM_SIZE);
 	if ((vramk * 1024) > VRAM_SIZE) {
 		buf->cur_pos += (vramk * 1024) - VRAM_SIZE;
@@ -4077,13 +4086,50 @@
 	context->sprite_draws = load_int8(buf);
 	context->slot_counter = load_int8(buf);
 	context->cur_slot = load_int8(buf);
-	for (int i = 0; i < MAX_DRAWS; i++)
-	{
-		sprite_draw *draw = context->sprite_draw_list + i;
-		draw->address = load_int16(buf);
-		draw->x_pos = load_int16(buf);
-		draw->pal_priority = load_int8(buf);
-		draw->h_flip = load_int8(buf);
+	if (version == 0) {
+		int cur_draw = 0;
+		for (int i = 0; i < MAX_SPRITES_LINE * 2; i++)
+		{
+			if (cur_draw < MAX_SPRITES_LINE) {
+				sprite_draw *last = cur_draw ? context->sprite_draw_list + cur_draw - 1 : NULL;
+				sprite_draw *draw = context->sprite_draw_list + cur_draw++;
+				draw->address = load_int16(buf);
+				draw->x_pos = load_int16(buf);
+				draw->pal_priority = load_int8(buf);
+				draw->h_flip = load_int8(buf);
+				draw->width = 1;
+				draw->height = 8;
+				
+				if (last && last->width < 4 && last->h_flip == draw->h_flip && last->pal_priority == draw->pal_priority) {
+					int adjust_x = draw->x_pos + draw->h_flip ? -8 : 8;
+					int height = draw->address - last->address /4;
+					if (last->x_pos == adjust_x && (
+						(last->width > 1 && height == last->height) || 
+						(last->width == 1 && (height == 8 || height == 16 || height == 24 || height == 32))
+					)) {
+						//current draw appears to be part of the same sprite as the last one, combine it
+						cur_draw--;
+						last->width++;
+					}
+				}
+			} else {
+				load_int16(buf);
+				load_int16(buf);
+				load_int8(buf);
+				load_int8(buf);
+			}
+		}
+	} else {
+		for (int i = 0; i < MAX_SPRITES_LINE; i++)
+		{
+			sprite_draw *draw = context->sprite_draw_list + i;
+			draw->address = load_int16(buf);
+			draw->x_pos = load_int16(buf);
+			draw->pal_priority = load_int8(buf);
+			draw->h_flip = load_int8(buf);
+			draw->width = load_int8(buf);
+			draw->height = load_int8(buf);
+		}
 	}
 	for (int i = 0; i < MAX_SPRITES_LINE; i++)
 	{
--- a/vdp.h	Sun Jun 02 23:16:15 2019 -0700
+++ b/vdp.h	Thu Jun 20 23:28:18 2019 -0700
@@ -24,8 +24,6 @@
 #define LINEBUF_SIZE (320+HORIZ_BORDER) //H40 + full border
 #define SCROLL_BUFFER_SIZE 32
 #define BORDER_BOTTOM 13 //TODO: Replace with actual value
-#define MAX_DRAWS 40
-#define MAX_DRAWS_H32 32
 #define MAX_DRAWS_H32_MODE4 8
 #define MAX_SPRITES_LINE 20
 #define MAX_SPRITES_LINE_H32 16
@@ -133,6 +131,8 @@
 	int16_t x_pos;
 	uint8_t pal_priority;
 	uint8_t h_flip;
+	uint8_t width;
+	uint8_t height;
 } sprite_draw;
 
 typedef struct {
@@ -195,7 +195,7 @@
 	uint16_t       hscroll_b;
 	uint16_t       h40_lines;
 	uint16_t       output_lines;
-	sprite_draw    sprite_draw_list[MAX_DRAWS];
+	sprite_draw    sprite_draw_list[MAX_SPRITES_LINE];
 	sprite_info    sprite_info_list[MAX_SPRITES_LINE];
 	uint8_t        sat_cache[SAT_CACHE_SIZE];
 	uint16_t       col_1;
--- a/ym2612.h	Sun Jun 02 23:16:15 2019 -0700
+++ b/ym2612.h	Thu Jun 20 23:28:18 2019 -0700
@@ -9,7 +9,7 @@
 #include <stdint.h>
 #include <stdio.h>
 #include "serialize.h"
-#include "render.h"
+#include "render_audio.h"
 
 #define NUM_PART_REGS (0xB7-0x30)
 #define NUM_CHANNELS 6