Mercurial > repos > blastem
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, ¤t_cycle, wait_time); + vgm_wait(chips, num_chips, &completed_samples, wait_time); break; } case CMD_WAIT_60: - vgm_wait(&y_context, &p_context, ¤t_cycle, 735 * mclks_sample); + vgm_wait(chips, num_chips, &completed_samples, 735); break; case CMD_WAIT_50: - vgm_wait(&y_context, &p_context, ¤t_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, ¤t_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, ¤t_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));