# HG changeset patch # User Michael Pavone # Date 1675579484 28800 # Node ID 92449b47cce897e99e1c2f4245e9335040a695cc # Parent efc75ea791640696112a02bb0d01f9b76e4cb2cb Integrate VGM player into main blastem binary diff -r efc75ea79164 -r 92449b47cce8 Makefile --- 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) diff -r efc75ea79164 -r 92449b47cce8 README --- 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 diff -r efc75ea79164 -r 92449b47cce8 blastem.c --- 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" diff -r efc75ea79164 -r 92449b47cce8 build_release --- 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 diff -r efc75ea79164 -r 92449b47cce8 mediaplayer.c --- /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 +#include +#include +#include +#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; +} diff -r efc75ea79164 -r 92449b47cce8 mediaplayer.h --- /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 +#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_ diff -r efc75ea79164 -r 92449b47cce8 system.c --- 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; } diff -r efc75ea79164 -r 92449b47cce8 system.h --- 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 { diff -r efc75ea79164 -r 92449b47cce8 vgm.h --- 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, diff -r efc75ea79164 -r 92449b47cce8 vgmplay.c --- 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 -#include -#include -#include -#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 -#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; -}