changeset 2081:cfd53c94fffb

Initial stab at RF5C164 emulation
author Michael Pavone <pavone@retrodev.com>
date Thu, 03 Feb 2022 23:15:42 -0800
parents bafb757e1cd2
children 485834c0fea7
files Makefile cdd_fader.c cdd_mcu.c psg.c render_audio.c render_audio.h segacd.c segacd.h vgm.h vgmplay.c ym2612.c
diffstat 11 files changed, 335 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Feb 02 01:10:07 2022 -0800
+++ b/Makefile	Thu Feb 03 23:15:42 2022 -0800
@@ -196,7 +196,7 @@
 endif
 endif
 endif
-AUDIOOBJS=ym2612.o psg.o wave.o vgm.o event_log.o render_audio.o
+AUDIOOBJS=ym2612.o psg.o wave.o vgm.o event_log.o render_audio.o rf5c164.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
--- a/cdd_fader.c	Wed Feb 02 01:10:07 2022 -0800
+++ b/cdd_fader.c	Thu Feb 03 23:15:42 2022 -0800
@@ -3,7 +3,7 @@
 
 void cdd_fader_init(cdd_fader *fader)
 {
-	fader->audio = render_audio_source(16934400, 384, 2);
+	fader->audio = render_audio_source("CDDA", 16934400, 384, 2);
 	fader->cur_attenuation = 0x4000;
 	fader->dst_attenuation = 0x4000;
 	fader->attenuation_step = 0;
--- a/cdd_mcu.c	Wed Feb 02 01:10:07 2022 -0800
+++ b/cdd_mcu.c	Thu Feb 03 23:15:42 2022 -0800
@@ -77,7 +77,7 @@
 		if (context->seek_pba == context->head_pba) {
 			context->seeking = 0;
 		} else if (context->seek_pba > context->head_pba) {
-			if (context->seek_pba - context->head_pba >= COARSE_SEEK) {
+			if (context->seek_pba - context->head_pba >= COARSE_SEEK || context->head_pba < LEADIN_SECTORS) {
 				context->head_pba += COARSE_SEEK;
 			} else if (context->seek_pba - context->head_pba >= FINE_SEEK) {
 				context->head_pba += FINE_SEEK;
@@ -119,6 +119,8 @@
 		handle_seek(context);
 		if (!context->seeking) {
 			context->head_pba++;
+		}
+		if (context->head_pba >= LEADIN_SECTORS) {
 			uint8_t track = context->media->seek(context->media, context->head_pba - LEADIN_SECTORS);
 			if (context->media->tracks[track].type == TRACK_AUDIO) {
 				gate_array[GAO_CDD_CTRL] &= ~BIT_MUTE;
@@ -471,7 +473,7 @@
 			next_nibble = context->cycle;
 			context->current_status_nibble = 0;
 			gate_array[GAO_CDD_STATUS] |= BIT_DRS;
-			if (context->status == DS_PLAY && !context->seeking) {
+			if (context->status == DS_PLAY && context->head_pba >= LEADIN_SECTORS) {
 				context->current_sector_byte = 0;
 			}
 		}
--- a/psg.c	Wed Feb 02 01:10:07 2022 -0800
+++ b/psg.c	Thu Feb 03 23:15:42 2022 -0800
@@ -13,7 +13,7 @@
 void psg_init(psg_context * context, uint32_t master_clock, uint32_t clock_div)
 {
 	memset(context, 0, sizeof(*context));
-	context->audio = render_audio_source(master_clock, clock_div, 1);
+	context->audio = render_audio_source("PSG", master_clock, clock_div, 1);
 	context->clock_inc = clock_div;
 	for (int i = 0; i < 4; i++) {
 		context->volume[i] = 0xF;
@@ -111,7 +111,7 @@
 		}
 
 		int16_t accum = 0;
-		
+
 		for (int i = 0; i < 3; i++) {
 			if (context->output_state[i]) {
 				accum += volume_table[context->volume[i]];
@@ -120,7 +120,7 @@
 		if (context->noise_out) {
 			accum += volume_table[context->volume[3]];
 		}
-		
+
 		render_put_mono_sample(context->audio, accum);
 
 		context->cycles += context->clock_inc;
--- a/render_audio.c	Wed Feb 02 01:10:07 2022 -0800
+++ b/render_audio.c	Thu Feb 03 23:15:42 2022 -0800
@@ -139,7 +139,7 @@
 
 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;
+	src->buffer_inc = ((BUFFER_INC_RES * sample_divider * (uint64_t)sample_rate) / master_clock);
 }
 
 void render_audio_adjust_speed(float adjust_ratio)
@@ -150,13 +150,14 @@
 	}
 }
 
-audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels)
+audio_source *render_audio_source(const char *name, 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->name = name;
 			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;
@@ -183,7 +184,7 @@
 		ret->gain_mult = 1.0f;
 	}
 	render_audio_created(ret);
-	
+
 	return ret;
 }
 
@@ -211,7 +212,7 @@
 				break;
 			}
 		}
-		
+
 	render_unlock_audio();
 	if (found) {
 		render_source_paused(src, remaining_sources);
@@ -250,7 +251,7 @@
 		render_pause_source(src);
 		num_inactive_audio_sources--;
 	}
-	
+
 	free(src->front);
 	if (render_is_audio_sync()) {
 		free(src->back);
@@ -283,7 +284,7 @@
 	{
 		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);
 		}
@@ -301,10 +302,10 @@
 	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);
 		}
@@ -382,4 +383,4 @@
 	{
 		update_source(inactive_audio_sources[i], rc, sync_changed);
 	}
-}
\ No newline at end of file
+}
--- a/render_audio.h	Wed Feb 02 01:10:07 2022 -0800
+++ b/render_audio.h	Thu Feb 03 23:15:42 2022 -0800
@@ -9,6 +9,7 @@
 } render_audio_format;
 
 typedef struct {
+	const char *name;
 	void     *opaque;
 	int16_t  *front;
 	int16_t  *back;
@@ -28,7 +29,7 @@
 } audio_source;
 
 //public interface
-audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels);
+audio_source *render_audio_source(const char *name, 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);
--- a/segacd.c	Wed Feb 02 01:10:07 2022 -0800
+++ b/segacd.c	Thu Feb 03 23:15:42 2022 -0800
@@ -262,7 +262,14 @@
 
 static uint8_t pcm_read8(uint32_t address, void *vcontext)
 {
-	return 0;
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	if (address & 1) {
+		rf5c164_run(&cd->pcm, m68k->current_cycle);
+		return rf5c164_read(&cd->pcm, address >> 1);
+	} else {
+		return 0xFF;
+	}
 }
 
 static uint16_t pcm_read16(uint32_t address, void *vcontext)
@@ -272,6 +279,12 @@
 
 static void *pcm_write8(uint32_t address, void *vcontext, uint8_t value)
 {
+	m68k_context *m68k = vcontext;
+	segacd_context *cd = m68k->system;
+	if (address & 1) {
+		rf5c164_run(&cd->pcm, m68k->current_cycle);
+		rf5c164_write(&cd->pcm, address >> 1, value);
+	}
 	return vcontext;
 }
 
@@ -760,7 +773,7 @@
 		break;
 	case DST_PCM_RAM:
 		dma_addr &= (1 << 13) - 1;
-		//TODO: write to currently visible 8K bank of PCM RAM I guess?
+		rf5c164_write(&cd->pcm, 0x1000 | (dma_addr >> 1), value);
 		dma_addr += 2;
 		cd->cdc_dst_low = dma_addr & 7;
 		cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3;
@@ -807,6 +820,7 @@
 	timers_run(cd, cycle);
 	cdd_run(cd, cycle);
 	cd_graphics_run(cd, cycle);
+	rf5c164_run(&cd->pcm, cycle);
 }
 
 static m68k_context *sync_components(m68k_context * context, uint32_t address)
@@ -890,6 +904,7 @@
 			cd->graphics_int_cycle = 0;
 		}
 	}
+	cd->pcm.cycle -= deduction;
 }
 
 static uint16_t main_gate_read16(uint32_t address, void *vcontext)
@@ -1175,7 +1190,7 @@
 	cdd_mcu_init(&cd->cdd, media);
 	cd_graphics_init(cd);
 	cdd_fader_init(&cd->fader);
-
+	rf5c164_init(&cd->pcm, SCD_MCLKS, 4);
 	return cd;
 }
 
--- a/segacd.h	Wed Feb 02 01:10:07 2022 -0800
+++ b/segacd.h	Thu Feb 03 23:15:42 2022 -0800
@@ -3,6 +3,7 @@
 #include <stdint.h>
 #include "genesis.h"
 #include "cdd_mcu.h"
+#include "rf5c164.h"
 
 typedef struct {
 	m68k_context    *m68k;
@@ -34,6 +35,7 @@
 	uint8_t         reset;
 	uint8_t         need_reset;
 	uint8_t         memptr_start_index;
+	rf5c164         pcm;
 	lc8951          cdc;
 	cdd_mcu         cdd;
 	cdd_fader       fader;
--- a/vgm.h	Wed Feb 02 01:10:07 2022 -0800
+++ b/vgm.h	Thu Feb 03 23:15:42 2022 -0800
@@ -58,21 +58,28 @@
 	CMD_DAC_STREAM_START,
 	CMD_DAC_STREAM_STOP,
 	CMD_DAC_STREAM_STARTFAST,
+	CMD_PCM68_REG = 0xB0,
+	CMD_PCM164_REG,
+	CMD_PCM68_RAM = 0xC1,
+	CMD_PCM164_RAM = 0xC2,
 	CMD_DATA_SEEK = 0xE0
 };
 
 enum {
-	DATA_YM2612_PCM = 0
+	DATA_YM2612_PCM = 0,
+	DATA_RF5C68,
+	DATA_RF5C164,
 };
 
 #pragma pack(pop)
 
-typedef struct {
-	struct data_block *next;
-	uint8_t           *data;
-	uint32_t          size;
-	uint8_t           type;
-} data_block;
+typedef struct data_block data_block;
+struct data_block {
+	data_block  *next;
+	uint8_t     *data;
+	uint32_t    size;
+	uint8_t     type;
+};
 
 typedef struct {
 	vgm_header header;
--- a/vgmplay.c	Wed Feb 02 01:10:07 2022 -0800
+++ b/vgmplay.c	Thu Feb 03 23:15:42 2022 -0800
@@ -14,6 +14,7 @@
 #include <string.h>
 #include "vgm.h"
 #include "system.h"
+#include "rf5c164.h"
 
 #define MCLKS_NTSC 53693175
 #define MCLKS_PAL  53203395
@@ -90,34 +91,106 @@
 
 int headless = 0;
 
-#define CYCLE_LIMIT MCLKS_NTSC/60
-#define MAX_SOUND_CYCLES 100000
+#include <limits.h>
+#define ADJUST_BUFFER (12500000)
+#define MAX_NO_ADJUST (UINT_MAX-ADJUST_BUFFER)
+#define MAX_RUN_SAMPLES 128
 tern_node * config;
 
-void vgm_wait(ym2612_context * y_context, psg_context * p_context, uint32_t * current_cycle, uint32_t cycles)
+typedef struct chip_info chip_info;
+typedef void (*run_fun)(void *context, uint32_t cycle);
+typedef void (*adjust_fun)(chip_info *chip);
+struct chip_info {
+	void       *context;
+	run_fun    run;
+	adjust_fun adjust;
+	uint32_t   clock;
+	uint32_t   samples;
+};
+
+uint32_t cycles_to_samples(uint32_t clock_rate, uint32_t cycles)
 {
-	while (cycles > MAX_SOUND_CYCLES)
-	{
-		vgm_wait(y_context, p_context, current_cycle, MAX_SOUND_CYCLES);
-		cycles -= MAX_SOUND_CYCLES;
+	return ((uint64_t)cycles) * ((uint64_t)44100) / ((uint64_t)clock_rate);
+}
+
+uint32_t samples_to_cycles(uint32_t clock_rate, uint32_t cycles)
+{
+	return ((uint64_t)cycles) * ((uint64_t)clock_rate) / ((uint64_t)44100);
+}
+
+void ym_adjust(chip_info *chip)
+{
+	ym2612_context *ym = chip->context;
+	if (ym->current_cycle >= MAX_NO_ADJUST) {
+		uint32_t deduction = ym->current_cycle - ADJUST_BUFFER;
+		chip->samples -= cycles_to_samples(chip->clock, deduction);
+		ym->current_cycle -= deduction;
 	}
-	*current_cycle += cycles;
-	psg_run(p_context, *current_cycle);
-	ym_run(y_context, *current_cycle);
+}
+
+void psg_adjust(chip_info *chip)
+{
+	psg_context *psg = chip->context;
+	if (psg->cycles >= MAX_NO_ADJUST) {
+		uint32_t deduction = psg->cycles - ADJUST_BUFFER;
+		chip->samples -= cycles_to_samples(chip->clock, deduction);
+		psg->cycles -= deduction;
+	}
+}
 
-	if (*current_cycle > CYCLE_LIMIT) {
-		*current_cycle -= CYCLE_LIMIT;
-		p_context->cycles -= CYCLE_LIMIT;
-		y_context->current_cycle -= CYCLE_LIMIT;
+void pcm_adjust(chip_info *chip)
+{
+	rf5c164 *pcm = chip->context;
+	if (pcm->cycle >= MAX_NO_ADJUST) {
+		uint32_t deduction = pcm->cycle - ADJUST_BUFFER;
+		chip->samples -= cycles_to_samples(chip->clock, deduction);
+		pcm->cycle -= deduction;
+	}
+}
+
+void vgm_wait(chip_info *chips, uint32_t num_chips, uint32_t *completed_samples, uint32_t samples)
+{
+	while (samples > MAX_RUN_SAMPLES)
+	{
+		vgm_wait(chips, num_chips, completed_samples, MAX_RUN_SAMPLES);
+		samples -= MAX_RUN_SAMPLES;
+	}
+	*completed_samples += samples;
+	for (uint32_t i = 0; i < num_chips; i++)
+	{
+		chips[i].samples += samples;
+		chips[i].run(chips[i].context, samples_to_cycles(chips[i].clock, chips[i].samples));
+		chips[i].adjust(chips + i);
+	}
+	if (*completed_samples > 44100/60) {
 		process_events();
 	}
 }
 
+chip_info chips[64];
+
+uint8_t *find_block(data_block *head, uint32_t offset, uint32_t size)
+{
+	if (!head) {
+		return NULL;
+	}
+	while (head->size < offset) {
+		offset -= head->size;
+		head = head->next;
+	}
+	if (head->size - offset < size) {
+		return NULL;
+	}
+	return head->data + offset;
+}
+
 int main(int argc, char ** argv)
 {
 	set_exe_str(argv[0]);
 	data_block *blocks = NULL;
 	data_block *seek_block = NULL;
+	data_block *pcm68_blocks = NULL;
+	data_block *pcm164_blocks = NULL;
 	uint32_t seek_offset;
 	uint32_t block_offset;
 
@@ -129,34 +202,90 @@
 	if (argc >= 3 && !strcmp(argv[2], "-y")) {
 		opts |= YM_OPT_WAVE_LOG;
 	}
-	
+
 	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;
 
-	ym2612_context y_context;
-	ym_init(&y_context, MCLKS_NTSC, MCLKS_PER_YM, opts);
-
-	psg_context p_context;
-	psg_init(&p_context, MCLKS_NTSC, MCLKS_PER_PSG);
-
-	VGMFILE * f = vgmopen(argv[1], "rb");
+	VGMFILE f = vgmopen(argv[1], "rb");
 	vgm_header header;
 	vgmread(&header, sizeof(header), 1, f);
 	if (header.version < 0x150 || !header.data_offset) {
 		header.data_offset = 0xC;
 	}
+	if (header.version <= 0x101) {
+		header.ym2612_clk = header.ym2413_clk;
+	}
+	uint32_t num_chips = 0;
+	rf5c164 *pcm68 = NULL;
+	if ((header.data_offset + 0x34) >= 0x44) {
+		uint32_t pcm68_clock = 0;
+		vgmseek(f, 0x40, SEEK_SET);
+		vgmread(&pcm68_clock, sizeof(pcm68_clock), 1, f);
+		if (pcm68_clock) {
+			pcm68 = calloc(sizeof(*pcm68), 1);
+			rf5c164_init(pcm68, pcm68_clock, 1);
+			chips[num_chips++] = (chip_info) {
+				.context = pcm68,
+				.run = (run_fun)rf5c164_run,
+				.adjust = pcm_adjust,
+				.clock = pcm68_clock,
+				.samples = 0
+			};
+		}
+	}
+	rf5c164 *pcm164 = NULL;
+	if ((header.data_offset + 0x34) >= 0x70) {
+		uint32_t pcm164_clock = 0;
+		vgmseek(f, 0x6C, SEEK_SET);
+		vgmread(&pcm164_clock, sizeof(pcm164_clock), 1, f);
+		if (pcm164_clock) {
+			pcm164 = calloc(sizeof(*pcm164), 1);
+			rf5c164_init(pcm164, pcm164_clock, 1);
+			chips[num_chips++] = (chip_info) {
+				.context = pcm164,
+				.run = (run_fun)rf5c164_run,
+				.adjust = pcm_adjust,
+				.clock = pcm164_clock,
+				.samples = 0
+			};
+		}
+	}
+
+	ym2612_context y_context;
+	if (header.ym2612_clk) {
+		ym_init(&y_context, header.ym2612_clk, 1, opts);
+		chips[num_chips++] = (chip_info) {
+			.context = &y_context,
+			.run = (run_fun)ym_run,
+			.adjust = ym_adjust,
+			.clock = header.ym2612_clk,
+			.samples = 0
+		};
+	}
+
+	psg_context p_context;
+	if (header.sn76489_clk) {
+		psg_init(&p_context, header.sn76489_clk, 1);
+		chips[num_chips++] = (chip_info) {
+			.context = &p_context,
+			.run = (run_fun)psg_run,
+			.adjust = psg_adjust,
+			.clock = header.sn76489_clk,
+			.samples = 0
+		};
+	}
+
 	vgmseek(f, header.data_offset + 0x34, SEEK_SET);
 	uint32_t data_size = header.eof_offset + 4 - (header.data_offset + 0x34);
 	uint8_t * data = malloc(data_size);
 	vgmread(data, 1, data_size, f);
 	vgmclose(f);
 
-	uint32_t mclks_sample = MCLKS_NTSC / 44100;
 	uint32_t loop_count = 2;
 
 	uint8_t * end = data + data_size;
 	uint8_t * cur = data;
-	uint32_t current_cycle = 0;
+	uint32_t completed_samples = 0;
 	while (cur < end) {
 		uint8_t cmd = *(cur++);
 		switch(cmd)
@@ -179,15 +308,14 @@
 		case CMD_WAIT: {
 			uint32_t wait_time = *(cur++);
 			wait_time |= *(cur++) << 8;
-			wait_time *= mclks_sample;
-			vgm_wait(&y_context, &p_context, &current_cycle, wait_time);
+			vgm_wait(chips, num_chips, &completed_samples, wait_time);
 			break;
 		}
 		case CMD_WAIT_60:
-			vgm_wait(&y_context, &p_context, &current_cycle, 735 * mclks_sample);
+			vgm_wait(chips, num_chips, &completed_samples, 735);
 			break;
 		case CMD_WAIT_50:
-			vgm_wait(&y_context, &p_context, &current_cycle, 882 * mclks_sample);
+			vgm_wait(chips, num_chips, &completed_samples, 882);
 			break;
 		case CMD_END:
 			if (header.loop_offset && --loop_count) {
@@ -197,6 +325,92 @@
 				return 0;
 			}
 			break;
+		case CMD_PCM_WRITE:
+			if (end - cur < 11) {
+				fatal_error("early end of stream at offset %X\n", data-cur);
+			}
+			cur++;
+			{
+				uint8_t chip_type = *(cur++);
+				uint32_t read_offset = *(cur++);
+				read_offset |= *(cur++) << 8;
+				read_offset |= *(cur++) << 16;
+				uint32_t write_offset = *(cur++);
+				write_offset |= *(cur++) << 8;
+				write_offset |= *(cur++) << 16;
+				uint32_t size = *(cur++);
+				size |= *(cur++) << 8;
+				size |= *(cur++) << 16;
+				if (chip_type == DATA_RF5C68) {
+					uint8_t *src = find_block(pcm68_blocks, read_offset, size);
+					if (!src) {
+						printf("Failed to find RF6C68 data offset %X with size %X\n", read_offset, size);
+					}
+					write_offset |= pcm68->ram_bank;
+					write_offset &= 0xFFFF;
+					if (size + write_offset > 0x10000) {
+						size = 0x10000 - write_offset;
+					}
+					memcpy(pcm68->ram + write_offset, src, size);
+					printf("rf5c68 PCM write read_offset %X, write_offset %X, size %X\n", read_offset, write_offset, size);
+				} else if (chip_type == DATA_RF5C164) {
+					uint8_t *src = find_block(pcm164_blocks, read_offset, size);
+					if (!src) {
+						printf("Failed to find RF6C68 data offset %X with size %X\n", read_offset, size);
+					}
+					write_offset |= pcm164->ram_bank;
+					write_offset &= 0xFFFF;
+					if (size + write_offset > 0x10000) {
+						size = 0x10000 - write_offset;
+					}
+					memcpy(pcm164->ram + write_offset, src, size);
+					printf("rf5c164 PCM write read_offset %X, write_offset %X, size %X\n", read_offset, write_offset, size);
+				} else {
+					printf("unknown PCM write read_offset %X, write_offset %X, size %X\n", read_offset, write_offset, size);
+				}
+			}
+			break;
+		case CMD_PCM68_REG:
+			if (pcm68) {
+				uint8_t reg = *(cur++);
+				uint8_t value = *(cur++);
+
+				rf5c164_write(pcm68, reg, value);
+			} else {
+				printf("CMD_PCM68_REG without rf5c68 clock\n");
+				cur += 2;
+			}
+			break;
+		case CMD_PCM164_REG:
+			if (pcm164) {
+				uint8_t reg = *(cur++);
+				uint8_t value = *(cur++);
+				rf5c164_write(pcm164, reg, value);
+			} else {
+				printf("CMD_PCM164_REG without rf5c164 clock\n");
+				cur += 2;
+			}
+			break;
+		case CMD_PCM68_RAM:
+			if (pcm68) {
+				uint16_t address = *(cur++);
+				address |= *(cur++) << 8;
+				address &= 0xFFF;
+				address |= 0x1000;
+				uint8_t value = *(cur++);
+				rf5c164_write(pcm68, address, value);
+			}
+			break;
+		case CMD_PCM164_RAM:
+			if (pcm164) {
+				uint16_t address = *(cur++);
+				address |= *(cur++) << 8;
+				address &= 0xFFF;
+				address |= 0x1000;
+				uint8_t value = *(cur++);
+				rf5c164_write(pcm164, address, value);
+			}
+			break;
 		case CMD_DATA: {
 			cur++; //skip compat command
 			uint8_t data_type = *(cur++);
@@ -204,8 +418,16 @@
 			data_size |= *(cur++) << 8;
 			data_size |= *(cur++) << 16;
 			data_size |= *(cur++) << 24;
+			data_block **curblock = NULL;
 			if (data_type == DATA_YM2612_PCM) {
-				data_block ** curblock = &blocks;
+				curblock = &blocks;
+			} else if (data_type == DATA_RF5C68) {
+				curblock = &pcm68_blocks;
+			} else if (data_type == DATA_RF5C164) {
+				curblock = &pcm164_blocks;
+			}
+
+			if (curblock) {
 				while(*curblock)
 				{
 					curblock = &((*curblock)->next);
@@ -245,8 +467,7 @@
 		default:
 			if (cmd >= CMD_WAIT_SHORT && cmd < (CMD_WAIT_SHORT + 0x10)) {
 				uint32_t wait_time = (cmd & 0xF) + 1;
-				wait_time *= mclks_sample;
-				vgm_wait(&y_context, &p_context, &current_cycle, wait_time);
+				vgm_wait(chips, num_chips, &completed_samples, wait_time);
 			} else if (cmd >= CMD_YM2612_DAC && cmd < CMD_DAC_STREAM_SETUP) {
 				if (seek_block) {
 					ym_address_write_part1(&y_context, 0x2A);
@@ -262,8 +483,7 @@
 				uint32_t wait_time = (cmd & 0xF);
 				if (wait_time)
 				{
-					wait_time *= mclks_sample;
-					vgm_wait(&y_context, &p_context, &current_cycle, wait_time);
+					vgm_wait(chips, num_chips, &completed_samples, wait_time);
 				}
 			} else {
 				fatal_error("unimplemented command: %X at offset %X\n", cmd, (unsigned int)(cur - data - 1));
--- a/ym2612.c	Wed Feb 02 01:10:07 2022 -0800
+++ b/ym2612.c	Thu Feb 03 23:15:42 2022 -0800
@@ -172,9 +172,9 @@
 	//TODO: Confirm these on hardware
 	context->timer_a = TIMER_A_MAX;
 	context->timer_b = TIMER_B_MAX;
-	
+
 	//TODO: Reset LFO state
-	
+
 	//some games seem to expect that the LR flags start out as 1
 	for (int i = 0; i < NUM_CHANNELS; i++) {
 		context->channels[i].lr = 0xC0;
@@ -199,11 +199,11 @@
 	memset(context, 0, sizeof(*context));
 	context->clock_inc = clock_div * 6;
 	context->busy_cycles = BUSY_CYCLES * context->clock_inc;
-	context->audio = render_audio_source(master_clock, context->clock_inc * NUM_OPERATORS, 2);
+	context->audio = render_audio_source("YM2612", master_clock, context->clock_inc * NUM_OPERATORS, 2);
 	//TODO: pick a randomish high initial value and lower it over time
 	context->invalid_status_decay = 225000 * context->clock_inc;
 	context->status_address_mask = (options & YM_OPT_3834) ? 0 : 3;
-	
+
 	//some games seem to expect that the LR flags start out as 1
 	for (int i = 0; i < NUM_CHANNELS; i++) {
 		if (options & YM_OPT_WAVE_LOG) {
@@ -480,7 +480,7 @@
 			operator->envelope += envelope_inc << 2;
 			//clamp to max attenuation value
 			if (
-				operator->envelope > MAX_ENVELOPE 
+				operator->envelope > MAX_ENVELOPE
 				|| (operator->env_phase == PHASE_RELEASE && operator->envelope >= SSG_CENTER)
 			) {
 				operator->envelope = MAX_ENVELOPE;
@@ -524,7 +524,7 @@
 					phase = operator->phase_counter = 0;
 				}
 				if (
-					(operator->env_phase == PHASE_DECAY || operator->env_phase == PHASE_SUSTAIN) 
+					(operator->env_phase == PHASE_DECAY || operator->env_phase == PHASE_SUSTAIN)
 					&& !(operator->ssg & SSG_HOLD)
 				) {
 					start_envelope(operator, chan);
@@ -665,7 +665,7 @@
 			context->current_op = 0;
 			ym_output_sample(context);
 		}
-		
+
 	}
 	//printf("Done running YM2612 at cycle %d\n", context->current_cycle, to_cycle);
 }
@@ -799,7 +799,7 @@
 		}
 		vgm_ym2612_part1_write(context->vgm, context->current_cycle, reg, context->part1_regs[reg - YM_PART1_START]);
 	}
-	
+
 	for (uint8_t reg = YM_PART2_START; reg < YM_REG_END; reg++) {
 		if ((reg & 3) == 3 || (reg >= REG_FNUM_LOW_CH3 && reg < REG_ALG_FEEDBACK)) {
 			//skip invalid registers
@@ -813,7 +813,7 @@
 {
 	context->write_cycle = context->current_cycle;
 	context->busy_start = context->current_cycle + context->clock_inc;
-	
+
 	if (context->selected_reg >= YM_REG_END) {
 		return;
 	}
@@ -912,7 +912,7 @@
 				if (channel > 2) {
 					channel--;
 				}
-				uint8_t changes = channel == 2 
+				uint8_t changes = channel == 2
 					? (value | context->csm_keyon) ^  (context->channels[channel].keyon | context->csm_keyon)
 					: value ^ context->channels[channel].keyon;
 				context->channels[channel].keyon = value & 0xF0;
@@ -1037,10 +1037,10 @@
 					//result from op2 when op3 starts executing
 					context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+2].output;
 					context->operators[channel*4+1].mod_src[1] = NULL;
-					
+
 					//operator 2 modulated by operator 1
 					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
-					
+
 					//operator 4 modulated by operator 3
 					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+1].output;
 					context->operators[channel*4+3].mod_src[1] = NULL;
@@ -1053,10 +1053,10 @@
 					//this uses a special op2 result reg on HW, but that reg will have the most recent
 					//result from op2 when op3 starts executing
 					context->operators[channel*4+1].mod_src[1] = &context->operators[channel*4+2].output;
-					
+
 					//operator 2 unmodulated
 					context->operators[channel*4+2].mod_src[0] = NULL;
-					
+
 					//operator 4 modulated by operator 3
 					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+1].output;
 					context->operators[channel*4+3].mod_src[1] = NULL;
@@ -1067,10 +1067,10 @@
 					//result from op2 when op3 starts executing
 					context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+2].output;
 					context->operators[channel*4+1].mod_src[1] = NULL;
-					
+
 					//operator 2 unmodulated
 					context->operators[channel*4+2].mod_src[0] = NULL;
-					
+
 					//operator 4 modulated by operator 1+3
 					//this uses a special op1 result reg on HW, but that reg will have the most recent
 					//result from op1 when op4 starts executing
@@ -1081,10 +1081,10 @@
 					//operator 3 unmodulated
 					context->operators[channel*4+1].mod_src[0] = NULL;
 					context->operators[channel*4+1].mod_src[1] = NULL;
-					
+
 					//operator 2 modulated by operator 1
 					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
-					
+
 					//operator 4 modulated by operator 2+3
 					//op2 starts executing before this, but due to pipeline length the most current result is
 					//not available and instead the previous result is used
@@ -1095,10 +1095,10 @@
 					//operator 3 unmodulated
 					context->operators[channel*4+1].mod_src[0] = NULL;
 					context->operators[channel*4+1].mod_src[1] = NULL;
-					
+
 					//operator 2 modulated by operator 1
 					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
-					
+
 					//operator 4 modulated by operator 3
 					context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+1].output;
 					context->operators[channel*4+3].mod_src[1] = NULL;
@@ -1109,10 +1109,10 @@
 					//not available and instead the previous result is used
 					context->operators[channel*4+1].mod_src[0] = &context->channels[channel].op1_old;
 					context->operators[channel*4+1].mod_src[1] = NULL;
-					
+
 					//operator 2 modulated by operator 1
 					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
-					
+
 					//operator 4 modulated by operator 1
 					//this uses a special op1 result reg on HW, but that reg will have the most recent
 					//result from op1 when op4 starts executing
@@ -1123,10 +1123,10 @@
 					//operator 3 unmodulated
 					context->operators[channel*4+1].mod_src[0] = NULL;
 					context->operators[channel*4+1].mod_src[1] = NULL;
-					
+
 					//operator 2 modulated by operator 1
 					context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output;
-					
+
 					//operator 4 unmodulated
 					context->operators[channel*4+3].mod_src[0] = NULL;
 					context->operators[channel*4+3].mod_src[1] = NULL;
@@ -1135,9 +1135,9 @@
 					//everything is an output so no modulation (except for op 1 feedback)
 					context->operators[channel*4+1].mod_src[0] = NULL;
 					context->operators[channel*4+1].mod_src[1] = NULL;
-					
+
 					context->operators[channel*4+2].mod_src[0] = NULL;
-					
+
 					context->operators[channel*4+3].mod_src[0] = NULL;
 					context->operators[channel*4+3].mod_src[1] = NULL;
 					break;
@@ -1182,7 +1182,7 @@
 		context->last_status_cycle = cycle;
 	}
 	return status;
-		
+
 }
 
 void ym_print_channel_info(ym2612_context *context, int channel)