changeset 2289:92449b47cce8

Integrate VGM player into main blastem binary
author Michael Pavone <pavone@retrodev.com>
date Sat, 04 Feb 2023 22:44:44 -0800
parents efc75ea79164
children c4980d89614b
files Makefile README blastem.c build_release mediaplayer.c mediaplayer.h system.c system.h vgm.h vgmplay.c
diffstat 10 files changed, 742 insertions(+), 530 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Jan 18 23:31:44 2023 -0800
+++ b/Makefile	Sat Feb 04 22:44:44 2023 -0800
@@ -215,12 +215,12 @@
 MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
 	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
 	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o \
-	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o
+	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o mediaplayer.o
 
 LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o xband.o realtec.o \
 	i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
 	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o gen_player.o $(LIBZOBJS) \
-	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o
+	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o mediaplayer.o
 
 ifdef NONUKLEAR
 CFLAGS+= -DDISABLE_NUKLEAR
@@ -263,7 +263,7 @@
 CFLAGS+= -DFONT_PATH='"'$(FONT_PATH)'"'
 endif
 
-ALL=dis$(EXE) zdis$(EXE) vgmplay$(EXE) blastem$(EXE)
+ALL=dis$(EXE) zdis$(EXE) blastem$(EXE)
 ifneq ($(OS),Windows)
 ALL+= termhelper
 endif
@@ -311,10 +311,6 @@
 ztestgen : ztestgen.o z80inst.o
 	$(CC) -ggdb -o ztestgen ztestgen.o z80inst.o
 
-vgmplay$(EXE) : vgmplay.o $(RENDEROBJS) serialize.o $(CONFIGOBJS) $(AUDIOOBJS)
-	$(CC) -o $@ $^ $(LDFLAGS)
-	$(FIXUP) ./$@
-
 blastcpm : blastcpm.o util.o serialize.o $(Z80OBJS) $(TRANSOBJS)
 	$(CC) -o $@ $^ $(OPT) $(PROFFLAGS)
 
--- a/README	Wed Jan 18 23:31:44 2023 -0800
+++ b/README	Sat Feb 04 22:44:44 2023 -0800
@@ -9,7 +9,7 @@
 NOTE: Prior to version 0.4.1, BlastEm was still using Unixy locations for config
 and save files. If you're upgrading from a previous version on Windows, you will
 need to move them manually. For config files, the relevant paths are in the
-previous paragraph. For save files, move all the directories found in 
+previous paragraph. For save files, move all the directories found in
 %userprofile%\.local\share\blastem to %localappdata%\blastem
 
 Usage
@@ -29,11 +29,11 @@
 list of supported command line options on Linux or OSX type:
 
     ./blastem -h
-    
+
 From within your BlastEm directory. On Windows type:
-    
+
     blastem.exe -h
-    
+
 Lock-On Support
 ---------------
 
@@ -46,7 +46,7 @@
 specify the ROM to be locked on using the -o option. As an example:
 
     ./blastem ~/romz/sonic_and_knuckles.bin -o ~/romz/sonic3.bin
-    
+
 Please note that Sonic 2 lock-on does not work at this time.
 
 Configuration
@@ -55,7 +55,7 @@
 Configuration is read from the file at $HOME/.config/blastem/blastem.cfg on
 Unix-like systems and %localappdata%\blastem\blastem.cfg if it exists.
 Othwerise it is read from default.cfg from the same directory as the BlastEm
-executable. Sections are denoted by a section name followed by an open curly 
+executable. Sections are denoted by a section name followed by an open curly
 bracket, the section's contents and a closing curly bracket. Individual
 configuration values are set by entering the value's name followed by a space
 or tab and followed by the desired value.
@@ -107,8 +107,8 @@
   back
 
 The pads subsection is used to map gamepads and joysticks. Gamepads that are
-recognized, can have their buttons and axes mapped with semantic names. 
-Xbox 360, PS4 and PS3 style names are supported. Unrecognized gamepads can be 
+recognized, can have their buttons and axes mapped with semantic names.
+Xbox 360, PS4 and PS3 style names are supported. Unrecognized gamepads can be
 mapped using numeric button and axis ids. The following button names are
 recognized by BlastEm:
 	a, cross
@@ -129,7 +129,7 @@
 	righty
 	lefttrigger, l2
 	righttrigger, r2
-	
+
 
 The mice subsection is used to map mice to emulated Mega/Sega mice. The default
 configuration maps both the first and second host mice to the first emulated
@@ -163,7 +163,7 @@
 ui.save_state                Saves a savestate to the quicksave slot
 ui.set_speed.N               Selects a specific machine speed specified by N
                              which should be a number between 0-9. Speeds are
-                             specified in the "clocks" section of the config				
+                             specified in the "clocks" section of the config
 ui.next_speed                Selects the next machine speed
 ui.prev_speed                Selects the previous machine speed
 ui.toggle_fullscreen         Toggles between fullscreen and windowed mode
@@ -176,7 +176,7 @@
                              mode
 ui.toggle_keyboard_captured  Toggles the capture state of the host keyboard
                              when an emulated keyboard is present
-		
+
 IO
 --
 
@@ -203,7 +203,7 @@
 "vertex_shader" and "fragment_shader" define the GLSL shader program that
 produces the final image for each frame. Shaders can be used to add various
 visual effects or enhancements. Currently BlastEm only ships with the default
-shader and a "subtle" crt shader. If you write your own shaders, place them in 
+shader and a "subtle" crt shader. If you write your own shaders, place them in
 $HOME/.config/blastem/shaders and then specify the basename of the new shader
 files in the "vertex_shader" and "fragment_shader" config options. Note that
 shaders are not available in the SDL fallback renderer.
@@ -334,7 +334,7 @@
 
 "remember_path" specifies whether BlastEm should remember the last path used in
 the file browser. When it is set to "on", the last path will be remembered and
-used instead of "initial_path" in subsequent runs. If it is set to "off", 
+used instead of "initial_path" in subsequent runs. If it is set to "off",
 "initial_path" will always be used.
 
 "screenshot_path" specifies the directory "internal" screenshots will be saved
@@ -364,7 +364,7 @@
 in the previous section.
 
 $HOME      The home directory of the current user. On most Unix variants, it
-           will be a subdirectory of /home. On Windows it will typically be a 
+           will be a subdirectory of /home. On Windows it will typically be a
            subdirectory of C:\Users
 $EXEDIR    The directory the BlastEm executable is located in
 $USERDATA  This is an OS-specific path used for storing application specific
@@ -453,7 +453,7 @@
 BlastEm will halt at the beginning of your program's entry point and return
 control to GDB. This will allow you to set breakpoints before your code runs.
 
-On Windows, the procedure is slightly different. First run 
+On Windows, the procedure is slightly different. First run
     blastem.exe ROM_FILE.bin -D
 This will cause BlastEm to wait for a socket connection on port 1234. It will
 appear to be frozen until gdb connects to it. Now open the ELF file in gdb
@@ -468,12 +468,10 @@
 
 BlastEm ships with a few small utilities that leverage portions of the emulator
 code.
-    
+
     dis       - 68K disassembler
     zdis      - Z80 disassembler
-    vgmplay   - Very basic VGM player
-    stateview - GST save state viewer
-    
+
 Sync Source and VSync
 -----
 
@@ -509,9 +507,9 @@
                      hardware document has also come in handy.
 
 Eke-Eke            - Eke-Eke wrote a great document on the use of I2C EEPROM in
-                     Genesis games and also left some useful very helpful 
+                     Genesis games and also left some useful very helpful
                      comments about problematic games in Genesis Plus GX
-					 
+
 Sauraen            - Sauraen has analyzed the YM2203 and YM2612 dies and written
                      a VHDL operator implementation. These have been useful in
                      improving the accuracy of my YM2612 core.
@@ -524,16 +522,16 @@
 Bart Trzynadlowski - His documents on the Genecyst save-state format and the
                      mapper used in Super Street Fighter 2 were definitely
                      appreciated.
-                     
+
 KanedaFR           - Kaneda's SpritesMind forum is a great resource for the
                      Sega development community.
-					 
+
 Titan              - Titan has created what are without a doubt the most
                      impressive demos on the Megadrive. Additionally, I am very
                      grateful for the documentation provided by Kabuto and the
                      assistance of Kabuto, Sik and Jorge in getting Overdrive 2
                      to run properly in BlastEm.
-					 
+
 flamewing          - flamewing created a very handy exhaustive test ROM for 68K
                      BCD instructions and documented the proper behavior for
                      certain BCD edge cases
--- a/blastem.c	Wed Jan 18 23:31:44 2023 -0800
+++ b/blastem.c	Sat Feb 04 22:44:44 2023 -0800
@@ -294,7 +294,7 @@
 system_header *game_system;
 void persist_save()
 {
-	if (!game_system) {
+	if (!game_system || !game_system->persist_save) {
 		return;
 	}
 	game_system->persist_save(game_system);
@@ -343,6 +343,10 @@
 
 void setup_saves(system_media *media, system_header *context)
 {
+	if (!context->load_save) {
+		// system doesn't support saves
+		return;
+	}
 	static uint8_t persist_save_registered;
 	rom_info *info = &context->info;
 	char *save_dir = get_save_dir(info->is_save_lock_on ? media->chain : media);
@@ -448,7 +452,9 @@
 void init_system_with_media(const char *path, system_type force_stype)
 {
 	if (game_system) {
-		game_system->persist_save(game_system);
+		if (game_system->persist_save) {
+			game_system->persist_save(game_system);
+		}
 		//swap to game context arena and mark all allocated pages in it free
 		if (current_system == menu_system) {
 			current_system->arena = set_current_arena(game_system->arena);
@@ -596,6 +602,8 @@
 					stype = force_stype = SYSTEM_GENESIS;
 				} else if (!strcmp("jag", argv[i])) {
 					stype = force_stype = SYSTEM_JAGUAR;
+				} else if (!strcmp("media", argv[i])) {
+					stype = force_stype = SYSTEM_MEDIA_PLAYER;
 				} else {
 					fatal_error("Unrecognized machine type %s\n", argv[i]);
 				}
@@ -633,7 +641,7 @@
 					"	-m MACHINE  Force emulated machine type to MACHINE. Valid values are:\n"
 					"                   sms - Sega Master System/Mark III\n"
 					"                   gen - Sega Genesis/Megadrive\n"
-					"                   jag - Atari Jaguar\n"
+					"                   media - Media Player\n"
 					"	-f          Toggles fullscreen mode\n"
 					"	-g          Disable OpenGL rendering\n"
 					"	-s FILE     Load a GST format savestate from FILE\n"
--- a/build_release	Wed Jan 18 23:31:44 2023 -0800
+++ b/build_release	Sat Feb 04 22:44:44 2023 -0800
@@ -34,11 +34,11 @@
 fi
 make menu.bin tmss.md
 if [ $OS = "Windows" -o $OS = "Win64" ]; then
-	binaries="dis.exe zdis.exe vgmplay.exe blastem.exe $SDLDLLPATH/SDL2.dll"
+	binaries="dis.exe zdis.exe blastem.exe $SDLDLLPATH/SDL2.dll"
 	verstr=`sed -E -n 's/^[^B]+BLASTEM_VERSION "([^"]+)"/blastem \1/p' blastem.c`
 	txt=".txt"
 else
-	binaries="dis zdis vgmplay blastem termhelper"
+	binaries="dis zdis blastem termhelper"
 	if [ $OS = "Darwin" ]; then
 		binaries="$binaries Frameworks"
 	else
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mediaplayer.c	Sat Feb 04 22:44:44 2023 -0800
@@ -0,0 +1,629 @@
+#include <stdlib.h>
+#include <stddef.h>
+#include <limits.h>
+#include <string.h>
+#include "mediaplayer.h"
+#include "io.h"
+#include "ym2612.h"
+#include "psg.h"
+#include "rf5c164.h"
+#include "util.h"
+#include "render.h"
+
+#define ADJUST_BUFFER (12500000)
+#define MAX_NO_ADJUST (UINT_MAX-ADJUST_BUFFER)
+#define MAX_RUN_SAMPLES 128
+
+enum {
+	AUDIO_VGM,
+	AUDIO_WAVE,
+	AUDIO_FLAC,
+	MEDIA_UNKNOWN
+};
+
+enum {
+	STATE_PLAY,
+	STATE_PAUSED
+};
+
+uint32_t cycles_to_samples(uint32_t clock_rate, uint32_t 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;
+	}
+}
+
+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;
+	}
+}
+
+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;
+	}
+}
+
+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;
+}
+
+void vgm_wait(media_player *player, uint32_t samples)
+{
+	chip_info *chips = player->chips;
+	uint32_t num_chips = player->num_chips;
+	while (samples > MAX_RUN_SAMPLES)
+	{
+		vgm_wait(player, MAX_RUN_SAMPLES);
+		samples -= MAX_RUN_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);
+	}
+}
+
+void vgm_stop(media_player *player)
+{
+	player->state = STATE_PAUSED;
+	player->playback_time = 0;
+	player->current_offset = player->vgm->data_offset + offsetof(vgm_header, data_offset);
+}
+
+chip_info *find_chip(media_player *player, uint8_t cmd)
+{
+	for (uint32_t i = 0; i < player->num_chips; i++)
+	{
+		if (player->chips[i].cmd == cmd) {
+			return player->chips + i;
+		}
+	}
+	return NULL;
+}
+
+void *find_chip_context(media_player *player, uint8_t cmd)
+{
+	chip_info *chip = find_chip(player, cmd);
+	return chip ? chip->context : NULL;
+}
+
+chip_info *find_chip_by_data(media_player *player, uint8_t data_type)
+{
+	for (uint32_t i = 0; i < player->num_chips; i++)
+	{
+		if (player->chips[i].data_type == data_type) {
+			return &player->chips[i];
+		}
+	}
+	return NULL;
+}
+
+static uint8_t read_byte(media_player *player)
+{
+	uint8_t *buffer = player->media->buffer;
+	return buffer[player->current_offset++];
+}
+
+static uint16_t read_word_le(media_player *player)
+{
+	uint8_t *buffer = player->media->buffer;
+	uint16_t value =  buffer[player->current_offset++];
+	value |= buffer[player->current_offset++] << 8;
+	return value;
+}
+
+static uint32_t read_24_le(media_player *player)
+{
+	uint8_t *buffer = player->media->buffer;
+	uint32_t value =  buffer[player->current_offset++];
+	value |= buffer[player->current_offset++] << 8;
+	value |= buffer[player->current_offset++] << 16;
+	return value;
+}
+
+static uint32_t read_long_le(media_player *player)
+{
+	uint8_t *buffer = player->media->buffer;
+	uint32_t value =  buffer[player->current_offset++];
+	value |= buffer[player->current_offset++] << 8;
+	value |= buffer[player->current_offset++] << 16;
+	value |= buffer[player->current_offset++] << 24;
+	return value;
+}
+
+void vgm_frame(media_player *player)
+{
+	for (uint32_t remaining_samples = 44100 / 60; remaining_samples > 0;)
+	{
+		if (player->wait_samples) {
+			uint32_t to_wait = player->wait_samples;
+			if (to_wait > remaining_samples) {
+				to_wait = remaining_samples;
+			}
+			vgm_wait(player, to_wait);
+			player->wait_samples -= to_wait;
+			remaining_samples -= to_wait;
+			if (player->wait_samples) {
+				return;
+			}
+		}
+		if (player->current_offset >= player->media->size) {
+			vgm_stop(player);
+			return;
+		}
+		uint8_t cmd = read_byte(player);
+		psg_context *psg;
+		ym2612_context *ym;
+		rf5c164 *pcm;
+		switch (cmd)
+		{
+		case CMD_PSG_STEREO:
+			psg = find_chip_context(player, CMD_PSG);
+			if (!psg || player->current_offset > player->media->size - 1) {
+				vgm_stop(player);
+				return;
+			}
+			psg->pan = read_byte(player);
+			break;
+		case CMD_PSG:
+			psg = find_chip_context(player, CMD_PSG);
+			if (!psg || player->current_offset > player->media->size - 1) {
+				vgm_stop(player);
+				return;
+			}
+			psg_write(psg, read_byte(player));
+			break;
+		case CMD_YM2612_0:
+			ym = find_chip_context(player, CMD_YM2612_0);
+			if (!ym || player->current_offset > player->media->size - 2) {
+				vgm_stop(player);
+				return;
+			}
+			ym_address_write_part1(ym, read_byte(player));
+			ym_data_write(ym, read_byte(player));
+			break;
+		case CMD_YM2612_1:
+			ym = find_chip_context(player, CMD_YM2612_0);
+			if (!ym || player->current_offset > player->media->size - 2) {
+				vgm_stop(player);
+				return;
+			}
+			ym_address_write_part2(ym, read_byte(player));
+			ym_data_write(ym, read_byte(player));
+			break;
+		case CMD_WAIT: {
+			if (player->current_offset > player->media->size - 2) {
+				vgm_stop(player);
+				return;
+			}
+			player->wait_samples += read_word_le(player);
+			break;
+		}
+		case CMD_WAIT_60:
+			player->wait_samples += 735;
+			break;
+		case CMD_WAIT_50:
+			player->wait_samples += 882;
+			break;
+		case CMD_END:
+			//TODO: loops
+			vgm_stop(player);
+			return;
+		case CMD_PCM_WRITE: {
+			if (player->current_offset > player->media->size - 11) {
+				vgm_stop(player);
+				return;
+			}
+			player->current_offset++; //skip compatibility command
+			uint8_t data_type = read_byte(player);
+			uint32_t read_offset = read_24_le(player);
+			uint32_t write_offset = read_24_le(player);
+			uint16_t size = read_24_le(player);
+			chip_info *chip = find_chip_by_data(player, data_type);
+			if (!chip || !chip->blocks) {
+				warning("Failed to find data block list for type %d\n", data_type);
+				break;
+			}
+			uint8_t *src = find_block(chip->blocks, read_offset, size);
+			if (!src) {
+				warning("Failed to find data offset %X with size %X for chip type %d\n", read_offset, size, data_type);
+				break;
+			}
+			switch (data_type)
+			{
+			case DATA_RF5C68:
+			case DATA_RF5C164:
+				pcm = chip->context;
+				write_offset |= pcm->ram_bank;
+				write_offset &= 0xFFFF;
+				if (size + write_offset > 0x10000) {
+					size = 0x10000 - write_offset;
+				}
+				memcpy(pcm->ram + write_offset, src, size);
+				break;
+			default:
+				warning("Unknown PCM write read_offset %X, write_offset %X, size %X\n", read_offset, write_offset, size);
+			}
+			break;
+		}
+		case CMD_PCM68_REG:
+			pcm = find_chip_context(player, CMD_PCM68_REG);
+			if (!pcm || player->current_offset > player->media->size - 2) {
+				vgm_stop(player);
+				return;
+			} else {
+				uint8_t reg = read_byte(player);
+				uint8_t value = read_byte(player);
+				rf5c164_write(pcm, reg, value);
+			}
+			break;
+		case CMD_PCM164_REG:
+			pcm = find_chip_context(player, CMD_PCM164_REG);
+			if (!pcm || player->current_offset > player->media->size - 2) {
+				vgm_stop(player);
+				return;
+			} else {
+				uint8_t reg = read_byte(player);
+				uint8_t value = read_byte(player);
+				rf5c164_write(pcm, reg, value);
+			}
+			break;
+		case CMD_PCM68_RAM:
+			pcm = find_chip_context(player, CMD_PCM68_REG);
+			if (!pcm || player->current_offset > player->media->size - 3) {
+				vgm_stop(player);
+				return;
+			} else {
+				uint16_t address = read_word_le(player);
+				address &= 0xFFF;
+				address |= 0x1000;
+				rf5c164_write(pcm, address, read_byte(player));
+			}
+			break;
+		case CMD_PCM164_RAM:
+			pcm = find_chip_context(player, CMD_PCM164_REG);
+			if (!pcm || player->current_offset > player->media->size - 3) {
+				vgm_stop(player);
+				return;
+			} else {
+				uint16_t address = read_word_le(player);
+				address &= 0xFFF;
+				address |= 0x1000;
+				rf5c164_write(pcm, address, read_byte(player));
+			}
+			break;
+		case CMD_DATA:
+			if (player->current_offset > player->media->size - 6) {
+				vgm_stop(player);
+				return;
+			} else {
+				player->current_offset++; //skip compat command
+				uint8_t data_type = read_byte(player);
+				uint32_t data_size = read_long_le(player);
+				if (data_size > player->media->size || player->current_offset > player->media->size - data_size) {
+					vgm_stop(player);
+					return;
+				}
+				chip_info *chip = find_chip_by_data(player, data_type);
+				if (chip) {
+					data_block **cur = &(chip->blocks);
+					while (*cur)
+					{
+						cur = &((*cur)->next);
+					}
+					*cur = calloc(1, sizeof(data_block));
+					(*cur)->size = data_size;
+					(*cur)->type = data_type;
+					(*cur)->data = ((uint8_t *)player->media->buffer) + player->current_offset;
+				} else {
+					fprintf(stderr, "Skipping data block with unrecognized type %X\n", data_type);
+				}
+				player->current_offset += data_size;
+			}
+			break;
+		case CMD_DATA_SEEK:
+			if (player->current_offset > player->media->size - 4) {
+				vgm_stop(player);
+				return;
+			} else {
+				uint32_t new_offset = read_long_le(player);
+				if (!player->ym_seek_block || new_offset < player->ym_seek_offset) {
+					chip_info *chip = find_chip(player, CMD_YM2612_0);
+					if (!chip) {
+						break;
+					}
+					player->ym_seek_block = chip->blocks;
+					player->ym_seek_offset = 0;
+					player->ym_block_offset = 0;
+				}
+				while (player->ym_seek_block && (player->ym_seek_offset - player->ym_block_offset + player->ym_seek_block->size) < new_offset)
+				{
+					player->ym_seek_offset += player->ym_seek_block->size - player->ym_block_offset;
+					player->ym_seek_block = player->ym_seek_block->next;
+					player->ym_block_offset = 0;
+				}
+				player->ym_block_offset += new_offset - player->ym_seek_offset;
+				player->ym_seek_offset = new_offset;
+			}
+			break;
+		default:
+			if (cmd >= CMD_WAIT_SHORT && cmd < (CMD_WAIT_SHORT + 0x10)) {
+				uint32_t wait_time = (cmd & 0xF) + 1;
+				player->wait_samples += wait_time;
+			} else if (cmd >= CMD_YM2612_DAC && cmd < CMD_DAC_STREAM_SETUP) {
+				if (player->ym_seek_block) {
+					ym = find_chip_context(player, CMD_YM2612_0);
+					ym_address_write_part1(ym, 0x2A);
+					ym_data_write(ym, player->ym_seek_block->data[player->ym_block_offset++]);
+					player->ym_seek_offset++;
+					if (player->ym_block_offset > player->ym_seek_block->size) {
+						player->ym_seek_block = player->ym_seek_block->next;
+						player->ym_block_offset = 0;
+					}
+				} else {
+					fputs("Encountered DAC write command but data seek pointer is invalid!\n", stderr);
+				}
+				player->wait_samples += cmd & 0xF;
+			} else {
+				warning("unimplemented command: %X at offset %X\n", cmd, player->current_offset);
+				vgm_stop(player);
+				return;
+			}
+		}
+	}
+}
+
+void wave_frame(media_player *player)
+{
+	render_sleep_ms(15);
+}
+
+void flac_frame(media_player *player)
+{
+	render_sleep_ms(15);
+}
+
+void vgm_init(media_player *player, uint32_t opts)
+{
+	player->vgm = calloc(1, sizeof(vgm_header));
+	player->vgm_ext = NULL;
+	memcpy(player->vgm, player->media->buffer, sizeof(vgm_header));
+	if (player->vgm->version < 0x150 || !player->vgm->data_offset) {
+		player->vgm->data_offset = 0xC;
+	}
+	if (player->vgm->data_offset + offsetof(vgm_header, data_offset) > player->media->size) {
+		player->vgm->data_offset = player->media->size - offsetof(vgm_header, data_offset);
+	}
+	if (player->vgm->version <= 0x101 && player->vgm->ym2413_clk > 4000000) {
+		player->vgm->ym2612_clk = player->vgm->ym2413_clk;
+		player->vgm->ym2413_clk = 0;
+	}
+	if (player->vgm->data_offset > 0xC) {
+		player->vgm_ext = calloc(1, sizeof(vgm_extended_header));
+		size_t additional_header = player->vgm->data_offset + offsetof(vgm_header, data_offset) - sizeof(vgm_header);
+		if (additional_header > sizeof(vgm_extended_header)) {
+			additional_header = sizeof(vgm_extended_header);
+		}
+		memcpy(player->vgm_ext, ((uint8_t *)player->media->buffer) + sizeof(vgm_header),  additional_header);
+	}
+	player->num_chips = 0;
+	if (player->vgm->sn76489_clk) {
+		player->num_chips++;
+	}
+	if (player->vgm->ym2612_clk) {
+		player->num_chips++;
+	}
+	if (player->vgm_ext && player->vgm_ext->rf5c68_clk) {
+		player->num_chips++;
+	}
+	if (player->vgm_ext && player->vgm_ext->rf5c164_clk) {
+		player->num_chips++;
+	}
+	player->chips = calloc(player->num_chips, sizeof(chip_info));
+	uint32_t chip = 0;
+	if (player->vgm->sn76489_clk) {
+		psg_context *psg = calloc(1, sizeof(psg_context));
+		psg_init(psg, player->vgm->sn76489_clk, 1);
+		player->chips[chip++] = (chip_info) {
+			.context = psg,
+			.run = (chip_run_fun)psg_run,
+			.adjust = psg_adjust,
+			.clock = player->vgm->sn76489_clk,
+			.samples = 0,
+			.cmd = CMD_PSG,
+			.data_type = 0xFF
+		};
+	}
+	if (player->vgm->ym2612_clk) {
+		ym2612_context *ym = calloc(1, sizeof(ym2612_context));
+		ym_init(ym, player->vgm->ym2612_clk, 1, opts);
+		player->chips[chip++] = (chip_info) {
+			.context = ym,
+			.run = (chip_run_fun)ym_run,
+			.adjust = ym_adjust,
+			.clock = player->vgm->ym2612_clk,
+			.samples = 0,
+			.cmd = CMD_YM2612_0,
+			.data_type = DATA_YM2612_PCM
+		};
+	}
+	if (player->vgm_ext && player->vgm_ext->rf5c68_clk) {
+		rf5c164 *pcm = calloc(1, sizeof(rf5c164));
+		rf5c164_init(pcm, player->vgm_ext->rf5c68_clk, 1);
+		player->chips[chip++] = (chip_info) {
+			.context = pcm,
+			.run = (chip_run_fun)rf5c164_run,
+			.adjust = pcm_adjust,
+			.clock = player->vgm_ext->rf5c68_clk,
+			.samples = 0,
+			.cmd = CMD_PCM68_REG,
+			.data_type = DATA_RF5C68
+		};
+	}
+	if (player->vgm_ext && player->vgm_ext->rf5c164_clk) {
+		rf5c164 *pcm = calloc(1, sizeof(rf5c164));
+		rf5c164_init(pcm, player->vgm_ext->rf5c164_clk, 1);
+		player->chips[chip++] = (chip_info) {
+			.context = pcm,
+			.run = (chip_run_fun)rf5c164_run,
+			.adjust = pcm_adjust,
+			.clock = player->vgm_ext->rf5c164_clk,
+			.samples = 0,
+			.cmd = CMD_PCM164_REG,
+			.data_type = DATA_RF5C164
+		};
+	}
+	player->current_offset = player->vgm->data_offset + offsetof(vgm_header, data_offset);
+}
+
+static void resume_player(system_header *system)
+{
+	media_player *player = (media_player *)system;
+	player->should_return = 0;
+	while (!player->header.should_exit && !player->should_return)
+	{
+		switch (player->state)
+		{
+		case STATE_PLAY:
+			switch(player->media_type)
+			{
+			case AUDIO_VGM:
+				vgm_frame(player);
+				break;
+			case AUDIO_WAVE:
+				wave_frame(player);
+				break;
+			case AUDIO_FLAC:
+				flac_frame(player);
+				break;
+			}
+			break;
+		case STATE_PAUSED:
+			render_sleep_ms(15);
+			break;
+		}
+		render_update_display();
+	}
+}
+
+static void gamepad_down(system_header *system, uint8_t pad, uint8_t button)
+{
+	if (button >= BUTTON_A && button <= BUTTON_C) {
+		media_player *player = (media_player *)system;
+		if (player->state == STATE_PAUSED) {
+			player->state = STATE_PLAY;
+			puts("Now playing");
+		} else {
+			player->state = STATE_PAUSED;
+			puts("Now paused");
+		}
+	}
+}
+
+static void gamepad_up(system_header *system, uint8_t pad, uint8_t button)
+{
+}
+
+static void start_player(system_header *system, char *statefile)
+{
+	resume_player(system);
+}
+
+static void free_player(system_header *system)
+{
+	media_player *player = (media_player *)system;
+	for (uint32_t i = 0; i < player->num_chips; i++)
+	{
+		//TODO properly free chips
+		free(player->chips[i].context);
+	}
+	free(player->chips);
+	free(player->vgm);
+	free(player);
+}
+
+uint8_t detect_media_type(system_media *media)
+{
+	if (media->size < 4) {
+		return MEDIA_UNKNOWN;
+	}
+	if (!memcmp(media->buffer, "Vgm ", 4)) {
+		if (media->size < sizeof(vgm_header)) {
+			return MEDIA_UNKNOWN;
+		}
+		return AUDIO_VGM;
+	}
+	if (!memcmp(media->buffer, "RIFF", 4)) {
+		return AUDIO_WAVE;
+	}
+	if (!memcmp(media->buffer, "fLaC", 4)) {
+		return AUDIO_FLAC;
+	}
+	return MEDIA_UNKNOWN;
+}
+
+static void request_exit(system_header *system)
+{
+	media_player *player = (media_player *)system;
+	player->should_return = 1;
+}
+
+media_player *alloc_media_player(system_media *media, uint32_t opts)
+{
+	media_player *player = calloc(1, sizeof(media_player));
+	player->header.start_context = start_player;
+	player->header.resume_context = resume_player;
+	player->header.request_exit = request_exit;
+	player->header.free_context = free_player;
+	player->header.gamepad_down = gamepad_down;
+	player->header.gamepad_up = gamepad_down;
+	player->header.type = SYSTEM_MEDIA_PLAYER;
+	player->header.info.name = strdup(media->name);
+
+	player->media = media;
+	player->media_type = detect_media_type(media);
+	player->state = STATE_PLAY;
+	switch (player->media_type)
+	{
+	case AUDIO_VGM:
+		vgm_init(player, opts);
+		break;
+	}
+
+	return player;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mediaplayer.h	Sat Feb 04 22:44:44 2023 -0800
@@ -0,0 +1,43 @@
+#ifndef MEDIAPLAYER_H_
+#define MEDIAPLAYER_H_
+
+#include <stdint.h>
+#include "system.h"
+#include "vgm.h"
+
+typedef struct chip_info chip_info;
+typedef void (*chip_run_fun)(void *context, uint32_t cycle);
+typedef void (*chip_adjust_fun)(chip_info *chip);
+struct chip_info {
+	void            *context;
+	chip_run_fun    run;
+	chip_adjust_fun adjust;
+	data_block      *blocks;
+	uint32_t        clock;
+	uint32_t        samples;
+	uint8_t         cmd;
+	uint8_t         data_type;
+};
+
+typedef struct {
+	system_header       header;
+	system_media        *media;
+	vgm_header          *vgm;
+	vgm_extended_header *vgm_ext;
+	data_block          *ym_seek_block;
+	chip_info           *chips;
+	uint32_t            num_chips;
+	uint32_t            current_offset;
+	uint32_t            playback_time;
+	uint32_t            wait_samples;
+	uint32_t            ym_seek_offset;
+	uint32_t            ym_block_offset;
+	uint8_t             state;
+	uint8_t             media_type;
+	uint8_t             should_return;
+} media_player;
+
+media_player *alloc_media_player(system_media *media, uint32_t opts);
+
+
+#endif //MEDIAPLAYER_H_
--- a/system.c	Wed Jan 18 23:31:44 2023 -0800
+++ b/system.c	Sat Feb 04 22:44:44 2023 -0800
@@ -3,6 +3,7 @@
 #include "genesis.h"
 #include "gen_player.h"
 #include "sms.h"
+#include "mediaplayer.h"
 
 uint8_t safe_cmp(char *str, long offset, uint8_t *buffer, long filesize)
 {
@@ -32,6 +33,12 @@
 			return buffer[8] + 1;
 		}
 	}
+	if (
+		safe_cmp("Vgm ", 0, media->buffer, media->size)
+		|| safe_cmp("RIFF", 0, media->buffer, media->size)
+		|| safe_cmp("fLaC", 0, media->buffer, media->size)) {
+		return SYSTEM_MEDIA_PLAYER;
+	}
 
 
 	//TODO: Detect Jaguar ROMs here
@@ -81,6 +88,8 @@
 	case SYSTEM_SMS:
 		return &(alloc_configure_sms(media, opts, force_region))->header;
 #endif
+	case SYSTEM_MEDIA_PLAYER:
+		return &(alloc_media_player(media, opts))->header;
 	default:
 		return NULL;
 	}
--- a/system.h	Wed Jan 18 23:31:44 2023 -0800
+++ b/system.h	Sat Feb 04 22:44:44 2023 -0800
@@ -15,6 +15,7 @@
 	SYSTEM_SMS,
 	SYSTEM_SMS_PLAYER,
 	SYSTEM_JAGUAR,
+	SYSTEM_MEDIA_PLAYER
 } system_type;
 
 typedef enum {
--- a/vgm.h	Wed Jan 18 23:31:44 2023 -0800
+++ b/vgm.h	Sat Feb 04 22:44:44 2023 -0800
@@ -26,6 +26,28 @@
 	uint32_t sega_pcm_reg;
 } vgm_header;
 
+typedef struct {
+	uint32_t rf5c68_clk;
+	uint32_t ym2203_clk;
+	uint32_t ym2608_clk;
+	uint32_t ym2610_clk;
+	uint32_t ym3812_clk;
+	uint32_t ym3526_clk;
+	uint32_t y8950_clk;
+	uint32_t ymf262_clk;
+	uint32_t ymf278b_clk;
+	uint32_t ymf271_clk;
+	uint32_t ymz280b_clk;
+	uint32_t rf5c164_clk;
+	uint32_t pwm_clk;
+	uint32_t ay8910_clk;
+	uint8_t  ay8910_type;
+	uint8_t  ay8910_flags;
+	uint8_t  ym2203_ay_flags;
+	uint8_t  ym2608_ay_flags;
+	//TODO: additional header extension fields
+} vgm_extended_header;
+
 enum {
 	CMD_PSG_STEREO = 0x4F,
 	CMD_PSG,
--- a/vgmplay.c	Wed Jan 18 23:31:44 2023 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,494 +0,0 @@
-/*
- Copyright 2013 Michael Pavone
- This file is part of BlastEm.
- 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 "render.h"
-#include "ym2612.h"
-#include "psg.h"
-#include "config.h"
-#include "util.h"
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include "vgm.h"
-#include "system.h"
-#include "rf5c164.h"
-
-#define MCLKS_NTSC 53693175
-#define MCLKS_PAL  53203395
-
-#define MCLKS_PER_68K 7
-#define MCLKS_PER_YM  MCLKS_PER_68K
-#define MCLKS_PER_Z80 15
-#define MCLKS_PER_PSG (MCLKS_PER_Z80*16)
-
-
-#ifdef DISABLE_ZLIB
-#define VGMFILE FILE*
-#define vgmopen fopen
-#define vgmread fread
-#define vgmseek fseek
-#define vgmgetc fgetc
-#define vgmclose fclose
-#else
-#include "zlib/zlib.h"
-#define VGMFILE gzFile
-#define vgmopen gzopen
-#define vgmread gzfread
-#define vgmseek gzseek
-#define vgmgetc gzgetc
-#define vgmclose gzclose
-#endif
-
-
-system_header *current_system;
-
-void system_request_exit(system_header *system, uint8_t force_release)
-{
-}
-
-void handle_keydown(int keycode)
-{
-}
-
-void handle_keyup(int keycode)
-{
-}
-
-void handle_joydown(int joystick, int button)
-{
-}
-
-void handle_joyup(int joystick, int button)
-{
-}
-
-void handle_joy_dpad(int joystick, int dpadnum, uint8_t value)
-{
-}
-
-void handle_joy_axis(int joystick, int axis, int16_t value)
-{
-}
-
-void handle_joy_added(int joystick)
-{
-}
-
-void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay)
-{
-}
-
-void handle_mousedown(int mouse, int button)
-{
-}
-
-void handle_mouseup(int mouse, int button)
-{
-}
-
-int headless = 0;
-
-#include <limits.h>
-#define ADJUST_BUFFER (12500000)
-#define MAX_NO_ADJUST (UINT_MAX-ADJUST_BUFFER)
-#define MAX_RUN_SAMPLES 128
-tern_node * config;
-
-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)
-{
-	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;
-	}
-}
-
-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;
-	}
-}
-
-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;
-
-	uint32_t fps = 60;
-	config = load_config(argv[0]);
-	render_init(320, 240, "vgm play", 0);
-
-	uint32_t opts = 0;
-	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;
-
-	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 loop_count = 2;
-
-	uint8_t * end = data + data_size;
-	uint8_t * cur = data;
-	uint32_t completed_samples = 0;
-	while (cur < end) {
-		uint8_t cmd = *(cur++);
-		switch(cmd)
-		{
-		case CMD_PSG_STEREO:
-			//ignore for now
-			cur++;
-			break;
-		case CMD_PSG:
-			psg_write(&p_context, *(cur++));
-			break;
-		case CMD_YM2612_0:
-			ym_address_write_part1(&y_context, *(cur++));
-			ym_data_write(&y_context, *(cur++));
-			break;
-		case CMD_YM2612_1:
-			ym_address_write_part2(&y_context, *(cur++));
-			ym_data_write(&y_context, *(cur++));
-			break;
-		case CMD_WAIT: {
-			uint32_t wait_time = *(cur++);
-			wait_time |= *(cur++) << 8;
-			vgm_wait(chips, num_chips, &completed_samples, wait_time);
-			break;
-		}
-		case CMD_WAIT_60:
-			vgm_wait(chips, num_chips, &completed_samples, 735);
-			break;
-		case CMD_WAIT_50:
-			vgm_wait(chips, num_chips, &completed_samples, 882);
-			break;
-		case CMD_END:
-			if (header.loop_offset && --loop_count) {
-				cur = data + header.loop_offset + 0x1C - (header.data_offset + 0x34);
-			} else {
-				//TODO: fade out
-				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++);
-			uint32_t data_size = *(cur++);
-			data_size |= *(cur++) << 8;
-			data_size |= *(cur++) << 16;
-			data_size |= *(cur++) << 24;
-			data_block **curblock = NULL;
-			if (data_type == DATA_YM2612_PCM) {
-				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);
-				}
-				*curblock = malloc(sizeof(data_block));
-				(*curblock)->size = data_size;
-				(*curblock)->type = data_type;
-				(*curblock)->data = cur;
-				(*curblock)->next = NULL;
-			} else {
-				fprintf(stderr, "Skipping data block with unrecognized type %X\n", data_type);
-			}
-			cur += data_size;
-			break;
-		}
-		case CMD_DATA_SEEK: {
-			uint32_t new_offset = *(cur++);
-			new_offset |= *(cur++) << 8;
-			new_offset |= *(cur++) << 16;
-			new_offset |= *(cur++) << 24;
-			if (!seek_block || new_offset < seek_offset) {
-				seek_block = blocks;
-				seek_offset = 0;
-				block_offset = 0;
-			}
-			while (seek_block && (seek_offset - block_offset + seek_block->size) < new_offset)
-			{
-				seek_offset += seek_block->size - block_offset;
-				seek_block = seek_block->next;
-				block_offset = 0;
-			}
-			block_offset += new_offset-seek_offset;
-			seek_offset = new_offset;
-			break;
-		}
-
-		default:
-			if (cmd >= CMD_WAIT_SHORT && cmd < (CMD_WAIT_SHORT + 0x10)) {
-				uint32_t wait_time = (cmd & 0xF) + 1;
-				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);
-					ym_data_write(&y_context, seek_block->data[block_offset++]);
-					seek_offset++;
-					if (block_offset > seek_block->size) {
-						seek_block = seek_block->next;
-						block_offset = 0;
-					}
-				} else {
-					fputs("Encountered DAC write command but data seek pointer is invalid!\n", stderr);
-				}
-				uint32_t wait_time = (cmd & 0xF);
-				if (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));
-			}
-		}
-	}
-	return 0;
-}