diff vgmplay.c @ 2081:cfd53c94fffb

Initial stab at RF5C164 emulation
author Michael Pavone <pavone@retrodev.com>
date Thu, 03 Feb 2022 23:15:42 -0800
parents 460e14497120
children
line wrap: on
line diff
--- 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));