changeset 2482:fb8f49b0aece

Impelement partial support for DAC stream commands in VGM player
author Michael Pavone <pavone@retrodev.com>
date Sat, 30 Mar 2024 21:10:20 -0700
parents f0645adddf0d
children 48ab1e3e5df5
files mediaplayer.c mediaplayer.h vgm.h
diffstat 3 files changed, 270 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/mediaplayer.c	Sat Mar 30 14:27:47 2024 -0700
+++ b/mediaplayer.c	Sat Mar 30 21:10:20 2024 -0700
@@ -21,6 +21,10 @@
 	MEDIA_UNKNOWN
 };
 
+#define STREAM_FLAG_REVERSE 0x01
+#define STREAM_FLAG_LOOP    0x10
+#define STREAM_FLAG_PLAY    0x80
+
 uint32_t cycles_to_samples(uint32_t clock_rate, uint32_t cycles)
 {
 	return ((uint64_t)cycles) * ((uint64_t)44100) / ((uint64_t)clock_rate);
@@ -52,6 +56,17 @@
 	ym->scope = NULL;
 }
 
+void ym_stream(chip_info *chip, uint8_t port, uint8_t command, uint16_t sample)
+{
+	ym2612_context *ym = chip->context;
+	if (port) {
+		ym_address_write_part2(ym, command);
+	} else {
+		ym_address_write_part1(ym, command);
+	}
+	ym_data_write(ym, sample);
+}
+
 void psg_adjust(chip_info *chip)
 {
 	psg_context *psg = chip->context;
@@ -125,6 +140,38 @@
 		vgm_wait(player, MAX_RUN_SAMPLES);
 		samples -= MAX_RUN_SAMPLES;
 	}
+	uint8_t keep_going;
+	do {
+		keep_going = 0;
+		for (uint32_t i = 0; i < 0xFF; i++)
+		{
+			if (!player->streams[i] || !(player->streams[i]->flags & STREAM_FLAG_PLAY)) {
+				continue;
+			}
+			vgm_stream *stream = player->streams[i];
+			uint32_t cycle = samples_to_cycles(stream->chip->clock, stream->chip->samples + samples);
+			if (cycle >= stream->next_sample_cycle) {
+				stream->chip->run(stream->chip->context, stream->next_sample_cycle);
+				if (stream->block_offset >= stream->cur_block->size) {
+					stream->cur_block = stream->cur_block->next;
+					if (!stream->cur_block) {
+						//TODO: looping support
+						player->streams[i]->flags &= ~STREAM_FLAG_PLAY;
+						continue;
+					}
+				}
+				//TODO: support for chips where the stream data unit is not a byte
+				uint16_t sample = stream->cur_block->data[stream->block_offset];
+				stream->block_offset += stream->step;
+				if (stream->chip->stream) {
+					stream->chip->stream(stream->chip, stream->port, stream->command, sample);
+				}
+				stream->next_sample_cycle += stream->cycles_per_sample;
+				//TODO: deal with cycle adjustments
+				keep_going = 1;
+			}
+		}
+	} while (keep_going);
 	for (uint32_t i = 0; i < num_chips; i++)
 	{
 		chips[i].samples += samples;
@@ -169,6 +216,17 @@
 	return NULL;
 }
 
+chip_info *find_chip_by_stream(media_player *player, uint8_t stream_type)
+{
+	for (uint32_t i = 0; i < player->num_chips; i++)
+	{
+		if (player->chips[i].stream_type == stream_type) {
+			return &player->chips[i];
+		}
+	}
+	return NULL;
+}
+
 static uint8_t read_byte(media_player *player)
 {
 	uint8_t *buffer = player->media->buffer;
@@ -402,6 +460,167 @@
 				player->current_offset += data_size;
 			}
 			break;
+		case CMD_DAC_STREAM_SETUP:
+			if (player->current_offset > player->media->size - 4) {
+				vgm_stop(player);
+				goto frame_end;
+			} else {
+				uint8_t stream_id = read_byte(player);
+				if (!player->streams[stream_id]) {
+					player->streams[stream_id] = calloc(1, sizeof(vgm_stream));
+				}
+				vgm_stream *stream = player->streams[stream_id];
+				uint8_t chip_type = read_byte(player);
+				stream->chip = find_chip_by_stream(player, chip_type);
+				if (!stream->chip) {
+					fprintf(stderr, "Failed to find chip with stream type %d for stream %d\n", chip_type, stream_id);
+				}
+				stream->port = read_byte(player);
+				stream->command = read_byte(player);
+			}
+			break;
+		case CMD_DAC_STREAM_DATA:
+			if (player->current_offset > player->media->size - 4) {
+				vgm_stop(player);
+				goto frame_end;
+			} else {
+				uint8_t stream_id = read_byte(player);
+				if (!player->streams[stream_id]) {
+					player->streams[stream_id] = calloc(1, sizeof(vgm_stream));
+				}
+				vgm_stream *stream = player->streams[stream_id];
+				stream->data_type = read_byte(player);
+				stream->step = read_byte(player);
+				stream->start_offset = read_byte(player);
+			}
+			break;
+		case CMD_DAC_STREAM_FREQ:
+			if (player->current_offset > player->media->size - 4) {
+				vgm_stop(player);
+				goto frame_end;
+			} else {
+				uint8_t stream_id = read_byte(player);
+				if (!player->streams[stream_id]) {
+					player->streams[stream_id] = calloc(1, sizeof(vgm_stream));
+				}
+				vgm_stream *stream = player->streams[stream_id];
+				stream->sample_rate = read_long_le(player);
+				if (stream->chip) {
+					stream->cycles_per_sample = stream->chip->clock / stream->sample_rate;
+				}
+			}
+			break;
+		case CMD_DAC_STREAM_START:
+			if (player->current_offset > player->media->size - 10) {
+				vgm_stop(player);
+				goto frame_end;
+			} else {
+				uint8_t stream_id = read_byte(player);
+				if (!player->streams[stream_id]) {
+					player->streams[stream_id] = calloc(1, sizeof(vgm_stream));
+				}
+				vgm_stream *stream = player->streams[stream_id];
+				uint32_t data_start_offset = read_long_le(player);
+				uint8_t length_mode = read_byte(player);
+				uint32_t length = read_long_le(player);
+				stream->flags = length_mode & STREAM_FLAG_REVERSE;
+				if (length_mode & 0x80) {
+					stream->flags |= STREAM_FLAG_LOOP;
+				}
+				length_mode &= 0x3;
+				if (length_mode) {
+					stream->length_mode = length_mode;
+					if (length_mode == 2) {
+						//length is in msec
+						if (stream->chip) {
+							stream->remaining = (((uint64_t)length) * (uint64_t)stream->sample_rate) / 1000;
+						}
+					} else if (length_mode == 3) {
+						//play until end of data
+						stream->remaining = 0xFFFFFFFF;
+					} else {
+						//length is in commands?
+						stream->remaining = length;
+					}
+				}
+				if (stream->chip && data_start_offset != 0xFFFFFFFF) {
+					data_block * cur_block = stream->chip->blocks;
+					data_start_offset += stream->start_offset;
+					while (cur_block)
+					{
+						if (cur_block->type == stream->data_type) {
+							if (data_start_offset >= cur_block->size) {
+								data_start_offset -= cur_block->size;
+								cur_block = cur_block->next;
+							} else {
+								stream->block_offset = data_start_offset;
+								stream->cur_block = cur_block;
+								break;
+							}
+						} else {
+							cur_block = cur_block->next;
+						}
+					}
+				}
+				if (stream->chip && stream->cur_block) {
+					stream->flags |= STREAM_FLAG_PLAY;
+				}
+			}
+			break;
+		case CMD_DAC_STREAM_STOP:
+			if (player->current_offset < player->media->size) {
+				vgm_stop(player);
+				goto frame_end;
+			} else {
+				uint8_t stream_id = read_byte(player);
+				if (stream_id == 0xFF) {
+					for (uint32_t i = 0; i < 0xFF; i++) {
+						if (player->streams[i]) {
+							player->streams[i]->flags &= ~STREAM_FLAG_PLAY;
+						}
+					}
+				} else {
+					if (!player->streams[stream_id]) {
+						player->streams[stream_id] = calloc(1, sizeof(vgm_stream));
+					}
+					player->streams[stream_id]->flags &= ~STREAM_FLAG_PLAY;
+				}
+			}
+			break;
+		case CMD_DAC_STREAM_STARTFAST:
+			if (player->current_offset > player->media->size - 4) {
+				vgm_stop(player);
+				goto frame_end;
+			} else {
+				uint8_t stream_id = read_byte(player);
+				if (!player->streams[stream_id]) {
+					player->streams[stream_id] = calloc(1, sizeof(vgm_stream));
+				}
+				vgm_stream *stream = player->streams[stream_id];
+				uint16_t block_id = read_word_le(player);
+				stream->flags = read_byte(player) & (STREAM_FLAG_LOOP|STREAM_FLAG_REVERSE);
+				if (stream->chip) {
+					uint16_t cur_block_id = 0;
+					data_block *cur_block = stream->chip->blocks;
+					while (cur_block)
+					{
+						if (cur_block_id == block_id) {
+							stream->cur_block = cur_block;
+							stream->block_offset = stream->start_offset;
+							break;
+						}
+						if (cur_block->type == stream->data_type) {
+							cur_block_id++;
+						}
+						cur_block = cur_block->next;
+					}
+					if (stream->cur_block) {
+						stream->flags |= STREAM_FLAG_PLAY;
+						stream->next_sample_cycle = samples_to_cycles(stream->chip->clock, stream->chip->samples);
+					}
+				}
+			}
+			break;
 		case CMD_DATA_SEEK:
 			if (player->current_offset > player->media->size - 4) {
 				vgm_stop(player);
@@ -558,10 +777,12 @@
 			.scope = ym_scope,
 			.no_scope = ym_no_scope,
 			.free = (chip_noarg_fun)ym_free,
+			.stream = ym_stream,
 			.clock = player->vgm->ym2612_clk,
 			.samples = 0,
 			.cmd = CMD_YM2612_0,
-			.data_type = DATA_YM2612_PCM
+			.data_type = DATA_YM2612_PCM,
+			.stream_type = STREAM_CHIP_YM2612
 		};
 	}
 	if (player->vgm->sn76489_clk) {
@@ -577,7 +798,8 @@
 			.clock = player->vgm->sn76489_clk,
 			.samples = 0,
 			.cmd = CMD_PSG,
-			.data_type = 0xFF
+			.data_type = 0xFF,
+			.stream_type = STREAM_CHIP_PSG
 		};
 	}
 	if (player->vgm_ext && player->vgm_ext->rf5c68_clk) {
@@ -593,7 +815,8 @@
 			.clock = player->vgm_ext->rf5c68_clk,
 			.samples = 0,
 			.cmd = CMD_PCM68_REG,
-			.data_type = DATA_RF5C68
+			.data_type = DATA_RF5C68,
+			.stream_type = STREAM_CHIP_RF5C68
 		};
 	}
 	if (player->vgm_ext && player->vgm_ext->rf5c164_clk) {
@@ -609,7 +832,8 @@
 			.clock = player->vgm_ext->rf5c164_clk,
 			.samples = 0,
 			.cmd = CMD_PCM164_REG,
-			.data_type = DATA_RF5C164
+			.data_type = DATA_RF5C164,
+			.stream_type = STREAM_CHIP_RF5C164
 		};
 	}
 	player->current_offset = player->vgm->data_offset + offsetof(vgm_header, data_offset);
--- a/mediaplayer.h	Sat Mar 30 14:27:47 2024 -0700
+++ b/mediaplayer.h	Sat Mar 30 21:10:20 2024 -0700
@@ -15,6 +15,7 @@
 typedef void (*chip_scope_fun)(chip_info *chip, oscilloscope *scope);
 typedef void (*chip_noarg_fun)(void *context);
 typedef void (*chip_adjust_fun)(chip_info *chip);
+typedef void (*chip_stream_fun)(chip_info *chip, uint8_t port, uint8_t command, uint16_t sample);
 struct chip_info {
 	void            *context;
 	chip_run_fun    run;
@@ -22,13 +23,32 @@
 	chip_scope_fun  scope;
 	chip_noarg_fun  no_scope;
 	chip_noarg_fun  free;
+	chip_stream_fun stream;
 	data_block      *blocks;
 	uint32_t        clock;
 	uint32_t        samples;
 	uint8_t         cmd;
 	uint8_t         data_type;
+	uint8_t         stream_type;
 };
 
+typedef struct {
+	chip_info  *chip;
+	data_block *cur_block;
+	uint32_t   step;
+	uint32_t   block_offset;
+	uint32_t   start_offset;
+	uint32_t   cycles_per_sample;
+	uint32_t   sample_rate;
+	uint32_t   next_sample_cycle;
+	uint32_t   remaining; // sample count or command count
+	uint8_t    data_type;
+	uint8_t    port;
+	uint8_t    command;
+	uint8_t    flags;
+	uint8_t    length_mode;
+} vgm_stream;
+
 enum {
 	STATE_PLAY,
 	STATE_PAUSED
@@ -45,6 +65,7 @@
 	audio_source        *audio;
 	chip_info           *chips;
 	oscilloscope        *scope;
+	vgm_stream          *streams[256];
 	uint32_t            num_chips;
 	uint32_t            current_offset;
 	uint32_t            playback_time;
--- a/vgm.h	Sat Mar 30 14:27:47 2024 -0700
+++ b/vgm.h	Sat Mar 30 21:10:20 2024 -0700
@@ -91,6 +91,27 @@
 	DATA_YM2612_PCM = 0,
 	DATA_RF5C68,
 	DATA_RF5C164,
+	DATA_PWM
+};
+
+enum {
+	STREAM_CHIP_PSG,
+	STREAM_CHIP_YM2413,
+	STREAM_CHIP_YM2612,
+	STREAM_CHIP_YM2151,
+	STREAM_CHIP_RF5C68,
+	STREAM_CHIP_YM2203,
+	STREAM_CHIP_YM2608,
+	STREAM_CHIP_YM2610,
+	STREAM_CHIP_YM3812,
+	STREAM_CHIP_YM3526,
+	STREAM_CHIP_Y8950,
+	STREAM_CHIP_YMF262,
+	STREAM_CHIP_YMF278B,
+	STREAM_CHIP_YMF271,
+	STREAM_CHIP_YMZ280B,
+	STREAM_CHIP_RF5C164,
+	STREAM_CHIP_PWM
 };
 
 #pragma pack(pop)