summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--bindings.c77
-rw-r--r--default.cfg5
-rw-r--r--genesis.c29
-rw-r--r--psg.c27
-rw-r--r--psg.h3
-rw-r--r--system.h3
-rw-r--r--vgm.c117
-rw-r--r--vgm.h19
-rw-r--r--ym2612.c19
-rw-r--r--ym2612.h3
11 files changed, 274 insertions, 30 deletions
diff --git a/Makefile b/Makefile
index b38b31b..2a21e79 100644
--- a/Makefile
+++ b/Makefile
@@ -194,7 +194,7 @@ TRANSOBJS+= gen_x86.o backend_x86.o
endif
endif
endif
-AUDIOOBJS=ym2612.o psg.o wave.o
+AUDIOOBJS=ym2612.o psg.o wave.o vgm.o
CONFIGOBJS=config.o tern.o util.o paths.o
NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o
RENDEROBJS=ppm.o controller_info.o render_audio.o
diff --git a/bindings.c b/bindings.c
index 357a859..bec8e16 100644
--- a/bindings.c
+++ b/bindings.c
@@ -36,6 +36,7 @@ typedef enum {
UI_RELOAD,
UI_SMS_PAUSE,
UI_SCREENSHOT,
+ UI_VGM_LOG,
UI_EXIT,
UI_PLANE_DEBUG,
UI_VRAM_DEBUG,
@@ -258,6 +259,39 @@ static uint8_t mouse_captured;
#define localtime_r(a,b) localtime(a)
#endif
+char *get_content_config_path(char *config_path, char *config_template, char *default_name)
+{
+ char *base = tern_find_path(config, config_path, TVAL_PTR).ptrval;
+ if (!base) {
+ base = "$HOME";
+ }
+ const system_media *media = current_media();
+ tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
+ vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
+ vars = tern_insert_ptr(vars, "USERDATA", (char *)get_userdata_dir());
+ vars = tern_insert_ptr(vars, "ROMNAME", media->name);
+ vars = tern_insert_ptr(vars, "ROMDIR", media->dir);
+ base = replace_vars(base, vars, 1);
+ tern_free(vars);
+ ensure_dir_exists(base);
+ time_t now = time(NULL);
+ struct tm local_store;
+ char fname_part[256];
+ char *template = tern_find_path(config, config_template, TVAL_PTR).ptrval;
+ if (template) {
+ vars = tern_insert_ptr(NULL, "ROMNAME", media->name);
+ template = replace_vars(template, vars, 0);
+ } else {
+ template = strdup(default_name);
+ }
+ strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
+ char const *parts[] = {base, PATH_SEP, fname_part};
+ char *path = alloc_concat_m(3, parts);
+ free(base);
+ free(template);
+ return path;
+}
+
void handle_binding_up(keybinding * binding)
{
uint8_t allow_content_binds = content_binds_enabled && current_system;
@@ -352,40 +386,23 @@ void handle_binding_up(keybinding * binding)
current_system->gamepad_down(current_system, GAMEPAD_MAIN_UNIT, MAIN_UNIT_PAUSE);
}
break;
- case UI_SCREENSHOT: {
+ case UI_SCREENSHOT:
if (allow_content_binds) {
- char *screenshot_base = tern_find_path(config, "ui\0screenshot_path\0", TVAL_PTR).ptrval;
- if (!screenshot_base) {
- screenshot_base = "$HOME";
- }
- const system_media *media = current_media();
- tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir());
- vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir());
- vars = tern_insert_ptr(vars, "USERDATA", (char *)get_userdata_dir());
- vars = tern_insert_ptr(vars, "ROMNAME", media->name);
- vars = tern_insert_ptr(vars, "ROMDIR", media->dir);
- screenshot_base = replace_vars(screenshot_base, vars, 1);
- tern_free(vars);
- ensure_dir_exists(screenshot_base);
- time_t now = time(NULL);
- struct tm local_store;
- char fname_part[256];
- char *template = tern_find_path(config, "ui\0screenshot_template\0", TVAL_PTR).ptrval;
- if (template) {
- vars = tern_insert_ptr(NULL, "ROMNAME", media->name);
- template = replace_vars(template, vars, 0);
+ char *path = get_content_config_path("ui\0screenshot_path\0", "ui\0screenshot_template\0", "blastem_%c.ppm");
+ render_save_screenshot(path);
+ }
+ break;
+ case UI_VGM_LOG:
+ if (allow_content_binds && current_system->start_vgm_log) {
+ if (current_system->vgm_logging) {
+ current_system->stop_vgm_log(current_system);
} else {
- template = strdup("blastem_%c.ppm");
+ char *path = get_content_config_path("ui\0vgm_path\0", "ui\0vgm_template\0", "blastem_%c.vgm");
+ current_system->start_vgm_log(current_system, path);
+ free(path);
}
- strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store));
- char const *parts[] = {screenshot_base, PATH_SEP, fname_part};
- char *path = alloc_concat_m(3, parts);
- free(screenshot_base);
- free(template);
- render_save_screenshot(path);
}
break;
- }
case UI_EXIT:
#ifndef DISABLE_NUKLEAR
if (is_nuklear_active()) {
@@ -634,6 +651,8 @@ int parse_binding_target(int device_num, char * target, tern_node * padbuttons,
*subtype_a = UI_SMS_PAUSE;
} else if (!strcmp(target + 3, "screenshot")) {
*subtype_a = UI_SCREENSHOT;
+ } else if (!strcmp(target + 3, "vgm_log")) {
+ *subtype_a = UI_VGM_LOG;
} else if(!strcmp(target + 3, "exit")) {
*subtype_a = UI_EXIT;
} else if (!strcmp(target + 3, "plane_debug")) {
diff --git a/default.cfg b/default.cfg
index 47a9abc..8d76e6d 100644
--- a/default.cfg
+++ b/default.cfg
@@ -22,6 +22,7 @@ bindings {
v ui.vram_debug
c ui.cram_debug
n ui.compositing_debug
+ m ui.vgm_log
esc ui.exit
` ui.save_state
0 ui.set_speed.0
@@ -364,6 +365,10 @@ ui {
screenshot_path $HOME
#see strftime for the format specifiers valid in screenshot_template
screenshot_template blastem_%Y%m%d_%H%M%S.png
+ #path for storing VGM recordings, accepts the same variables as initial_path
+ vgm_path $HOME
+ #see strftime for the format specifiers valid in vgm_template
+ vgm_template blastem_%Y%m%d_%H%M%S.vgm
#path template for saving SRAM, EEPROM and savestates
#accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME
save_path $USERDATA/blastem/$ROMNAME
diff --git a/genesis.c b/genesis.c
index 14dcf0a..6b6ba13 100644
--- a/genesis.c
+++ b/genesis.c
@@ -404,6 +404,9 @@ m68k_context * sync_components(m68k_context * context, uint32_t address)
context->current_cycle -= deduction;
z80_adjust_cycles(z_context, deduction);
ym_adjust_cycles(gen->ym, deduction);
+ if (gen->ym->vgm) {
+ vgm_adjust_cycles(gen->ym->vgm, deduction);
+ }
gen->psg->cycles -= deduction;
if (gen->reset_cycle != CYCLE_NEVER) {
gen->reset_cycle -= deduction;
@@ -1452,6 +1455,30 @@ static void config_updated(system_header *system)
set_audio_config(gen);
}
+static void start_vgm_log(system_header *system, char *filename)
+{
+ genesis_context *gen = (genesis_context *)system;
+ vgm_writer *vgm = vgm_write_open(filename, gen->version_reg & HZ50 ? 50 : 60, gen->master_clock, gen->m68k->current_cycle);
+ if (vgm) {
+ printf("Started logging VGM to %s\n", filename);
+ sync_sound(gen, vgm->last_cycle);
+ ym_vgm_log(gen->ym, gen->master_clock, vgm);
+ psg_vgm_log(gen->psg, gen->master_clock, vgm);
+ gen->header.vgm_logging = 1;
+ } else {
+ printf("Failed to start logging to %s\n", filename);
+ }
+}
+
+static void stop_vgm_log(system_header *system)
+{
+ puts("Stopped VGM log");
+ genesis_context *gen = (genesis_context *)system;
+ vgm_close(gen->ym->vgm);
+ gen->ym->vgm = gen->psg->vgm = NULL;
+ gen->header.vgm_logging = 0;
+}
+
genesis_context *alloc_init_genesis(rom_info *rom, void *main_rom, void *lock_on, uint32_t system_opts, uint8_t force_region)
{
static memmap_chunk z80_map[] = {
@@ -1484,6 +1511,8 @@ genesis_context *alloc_init_genesis(rom_info *rom, void *main_rom, void *lock_on
gen->header.config_updated = config_updated;
gen->header.serialize = serialize;
gen->header.deserialize = deserialize;
+ gen->header.start_vgm_log = start_vgm_log;
+ gen->header.stop_vgm_log = stop_vgm_log;
gen->header.type = SYSTEM_GENESIS;
gen->header.info = *rom;
set_region(gen, rom, force_region);
diff --git a/psg.c b/psg.c
index ccf0779..363114c 100644
--- a/psg.c
+++ b/psg.c
@@ -32,6 +32,9 @@ void psg_adjust_master_clock(psg_context * context, uint32_t master_clock)
void psg_write(psg_context * context, uint8_t value)
{
+ if (context->vgm) {
+ vgm_sn76489_write(context->vgm, context->cycles, value);
+ }
if (value & 0x80) {
context->latch = value & 0x70;
uint8_t channel = value >> 5 & 0x3;
@@ -122,6 +125,30 @@ void psg_run(psg_context * context, uint32_t cycles)
}
}
+void psg_vgm_log(psg_context *context, uint32_t master_clock, vgm_writer *vgm)
+{
+ vgm_sn76489_init(vgm, master_clock / context->clock_inc, 9, 16, 0);
+ context->vgm = vgm;
+ for (int chan = 0; chan < 4; chan++)
+ {
+ uint8_t base = chan << 5 | 0x80;
+ vgm_sn76489_write(context->vgm, context->cycles, context->volume[chan] | base | 0x10);
+ if (chan == 3) {
+ if (context->noise_use_tone) {
+ vgm_sn76489_write(context->vgm, context->cycles, 3 | base);
+ } else {
+ //0x10 = 0
+ //0x20 = 1
+ //0x40 = 2
+ vgm_sn76489_write(context->vgm, context->cycles, context->counter_load[chan] >> 5 | base);
+ }
+ } else {
+ vgm_sn76489_write(context->vgm, context->cycles, (context->counter_load[chan] & 0xF) | base);
+ vgm_sn76489_write(context->vgm, context->cycles, context->counter_load[chan] >> 4 & 0x3F);
+ }
+ }
+}
+
void psg_serialize(psg_context *context, serialize_buffer *buf)
{
save_int16(buf, context->lsfr);
diff --git a/psg.h b/psg.h
index cad590d..9460207 100644
--- a/psg.h
+++ b/psg.h
@@ -9,9 +9,11 @@
#include <stdint.h>
#include "serialize.h"
#include "render_audio.h"
+#include "vgm.h"
typedef struct {
audio_source *audio;
+ vgm_writer *vgm;
uint32_t clock_inc;
uint32_t cycles;
uint16_t lsfr;
@@ -31,6 +33,7 @@ void psg_free(psg_context *context);
void psg_adjust_master_clock(psg_context * context, uint32_t master_clock);
void psg_write(psg_context * context, uint8_t value);
void psg_run(psg_context * context, uint32_t cycles);
+void psg_vgm_log(psg_context *context, uint32_t master_clock, vgm_writer *vgm);
void psg_serialize(psg_context *context, serialize_buffer *buf);
void psg_deserialize(deserialize_buffer *buf, void *vcontext);
diff --git a/system.h b/system.h
index 1e574df..508e29a 100644
--- a/system.h
+++ b/system.h
@@ -58,6 +58,8 @@ struct system_header {
system_fun config_updated;
system_ptrszt_fun_rptr8 serialize;
system_ptr8_sizet_fun deserialize;
+ system_str_fun start_vgm_log;
+ system_fun stop_vgm_log;
rom_info info;
arena *arena;
char *next_rom;
@@ -67,6 +69,7 @@ struct system_header {
uint8_t save_state;
uint8_t delayed_load_slot;
uint8_t has_keyboard;
+ uint8_t vgm_logging;
debugger_type debugger_type;
system_type type;
};
diff --git a/vgm.c b/vgm.c
new file mode 100644
index 0000000..4e117d6
--- /dev/null
+++ b/vgm.c
@@ -0,0 +1,117 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include "vgm.h"
+
+vgm_writer *vgm_write_open(char *filename, uint32_t rate, uint32_t clock, uint32_t cycle)
+{
+ FILE *f = fopen(filename, "wb");
+ if (!f) {
+ return NULL;
+ }
+ vgm_writer *writer = calloc(sizeof(vgm_writer), 1);
+ memcpy(writer->header.ident, "Vgm ", 4);
+ writer->header.version = 0x150;
+ writer->header.data_offset = sizeof(writer->header) - offsetof(vgm_header, data_offset);
+ writer->header.rate = rate;
+ writer->f = f;
+ if (1 != fwrite(&writer->header, sizeof(writer->header), 1, f)) {
+ free(writer);
+ fclose(f);
+ return NULL;
+ }
+ writer->master_clock = clock;
+ writer->last_cycle = cycle;
+
+ return writer;
+}
+
+void vgm_sn76489_init(vgm_writer *writer, uint32_t clock, uint16_t feedback, uint8_t shift_reg_size, uint8_t flags)
+{
+ if (flags && writer->header.version < 0x151) {
+ writer->header.version = 0x151;
+ }
+ writer->header.sn76489_clk = clock,
+ writer->header.sn76489_fb = feedback;
+ writer->header.sn76489_shift = shift_reg_size;
+ writer->header.sn76489_flags = flags;
+}
+
+static void wait_commands(vgm_writer *writer, uint32_t delta)
+{
+ if (!delta) {
+ return;
+ }
+ if (delta <= 0x10) {
+ fputc(CMD_WAIT_SHORT + (delta - 1), writer->f);
+ } else if (delta >= 735 && delta <= (735 + 0x10)) {
+ fputc(CMD_WAIT_60, writer->f);
+ wait_commands(writer, delta - 735);
+ } else if (delta >= 882 && delta <= (882 + 0x10)) {
+ fputc(CMD_WAIT_50, writer->f);
+ wait_commands(writer, delta - 882);
+ } else if (delta > 0xFFFF) {
+ uint8_t cmd[3] = {CMD_WAIT, 0xFF, 0xFF};
+ fwrite(cmd, 1, sizeof(cmd), writer->f);
+ wait_commands(writer, delta - 0xFFFF);
+ } else {
+ uint8_t cmd[3] = {CMD_WAIT, delta, delta >> 8};
+ fwrite(cmd, 1, sizeof(cmd), writer->f);
+ }
+}
+
+static void add_wait(vgm_writer *writer, uint32_t cycle)
+{
+ uint64_t delta = cycle - writer->last_cycle;
+ delta *= (uint64_t)44100;
+ delta /= (uint64_t)writer->master_clock;
+
+ uint32_t mclks_per_sample = writer->master_clock / 44100;
+ writer->last_cycle += delta * mclks_per_sample;
+ writer->header.num_samples += delta;
+ wait_commands(writer, delta);
+}
+
+void vgm_sn76489_write(vgm_writer *writer, uint32_t cycle, uint8_t value)
+{
+ add_wait(writer, cycle);
+ uint8_t cmd[2] = {CMD_PSG, value};
+ fwrite(cmd, 1, sizeof(cmd), writer->f);
+}
+
+void vgm_ym2612_init(vgm_writer *writer, uint32_t clock)
+{
+ writer->header.ym2612_clk = clock;
+}
+
+void vgm_ym2612_part1_write(vgm_writer *writer, uint32_t cycle, uint8_t reg, uint8_t value)
+{
+ add_wait(writer, cycle);
+ uint8_t cmd[3] = {CMD_YM2612_0, reg, value};
+ fwrite(cmd, 1, sizeof(cmd), writer->f);
+}
+
+void vgm_ym2612_part2_write(vgm_writer *writer, uint32_t cycle, uint8_t reg, uint8_t value)
+{
+ add_wait(writer, cycle);
+ uint8_t cmd[3] = {CMD_YM2612_1, reg, value};
+ fwrite(cmd, 1, sizeof(cmd), writer->f);
+}
+
+void vgm_adjust_cycles(vgm_writer *writer, uint32_t deduction)
+{
+ if (deduction > writer->last_cycle) {
+ writer->last_cycle = 0;
+ } else {
+ writer->last_cycle -= deduction;
+ }
+}
+
+void vgm_close(vgm_writer *writer)
+{
+ writer->header.eof_offset = ftell(writer->f) - offsetof(vgm_header, eof_offset);
+ fseek(writer->f, SEEK_SET, 0);
+ fwrite(&writer->header, sizeof(writer->header), 1, writer->f);
+ fclose(writer->f);
+ free(writer);
+} \ No newline at end of file
diff --git a/vgm.h b/vgm.h
index b6ca836..ce196d5 100644
--- a/vgm.h
+++ b/vgm.h
@@ -1,6 +1,9 @@
#ifndef VGM_H_
#define VGM_H_
+#include <stdint.h>
+#include <stdio.h>
+
#pragma pack(push, 1)
typedef struct {
char ident[4];
@@ -71,4 +74,20 @@ typedef struct {
uint8_t type;
} data_block;
+typedef struct {
+ vgm_header header;
+ FILE *f;
+ uint32_t master_clock;
+ uint32_t last_cycle;
+} vgm_writer;
+
+vgm_writer *vgm_write_open(char *filename, uint32_t rate, uint32_t clock, uint32_t cycle);
+void vgm_sn76489_init(vgm_writer *writer, uint32_t clock, uint16_t feedback, uint8_t shift_reg_size, uint8_t flags);
+void vgm_sn76489_write(vgm_writer *writer, uint32_t cycle, uint8_t value);
+void vgm_ym2612_init(vgm_writer *writer, uint32_t clock);
+void vgm_ym2612_part1_write(vgm_writer *writer, uint32_t cycle, uint8_t reg, uint8_t value);
+void vgm_ym2612_part2_write(vgm_writer *writer, uint32_t cycle, uint8_t reg, uint8_t value);
+void vgm_adjust_cycles(vgm_writer *writer, uint32_t deduction);
+void vgm_close(vgm_writer *writer);
+
#endif //VGM_H_
diff --git a/ym2612.c b/ym2612.c
index ec527a6..bde4ee7 100644
--- a/ym2612.c
+++ b/ym2612.c
@@ -779,6 +779,19 @@ static uint32_t ym_calc_phase_inc(ym2612_context * context, ym_operator * operat
return inc;
}
+void ym_vgm_log(ym2612_context *context, uint32_t master_clock, vgm_writer *vgm)
+{
+ vgm_ym2612_init(vgm, 6 * master_clock / context->clock_inc);
+ context->vgm = vgm;
+ for (uint8_t reg = YM_PART1_START; reg < YM_REG_END; reg++) {
+ vgm_ym2612_part1_write(context->vgm, context->current_cycle, reg, context->part1_regs[reg - YM_PART1_START]);
+ }
+
+ for (uint8_t reg = YM_PART2_START; reg < YM_REG_END; reg++) {
+ vgm_ym2612_part2_write(context->vgm, context->current_cycle, reg, context->part2_regs[reg - YM_PART2_START]);
+ }
+}
+
void ym_data_write(ym2612_context * context, uint8_t value)
{
context->write_cycle = context->current_cycle;
@@ -791,11 +804,17 @@ void ym_data_write(ym2612_context * context, uint8_t value)
if (context->selected_reg < YM_PART2_START) {
return;
}
+ if (context->vgm) {
+ vgm_ym2612_part2_write(context->vgm, context->current_cycle, context->selected_reg, value);
+ }
context->part2_regs[context->selected_reg - YM_PART2_START] = value;
} else {
if (context->selected_reg < YM_PART1_START) {
return;
}
+ if (context->vgm) {
+ vgm_ym2612_part1_write(context->vgm, context->current_cycle, context->selected_reg, value);
+ }
context->part1_regs[context->selected_reg - YM_PART1_START] = value;
}
dfprintf(debug_file, "write of %X to reg %X in part %d\n", value, context->selected_reg, context->selected_part+1);
diff --git a/ym2612.h b/ym2612.h
index 130c729..32ef035 100644
--- a/ym2612.h
+++ b/ym2612.h
@@ -10,6 +10,7 @@
#include <stdio.h>
#include "serialize.h"
#include "render_audio.h"
+#include "vgm.h"
#define NUM_PART_REGS (0xB7-0x30)
#define NUM_CHANNELS 6
@@ -68,6 +69,7 @@ typedef struct {
typedef struct {
audio_source *audio;
+ vgm_writer *vgm;
uint32_t clock_inc;
uint32_t current_cycle;
uint32_t write_cycle;
@@ -144,6 +146,7 @@ void ym_run(ym2612_context * context, uint32_t to_cycle);
void ym_address_write_part1(ym2612_context * context, uint8_t address);
void ym_address_write_part2(ym2612_context * context, uint8_t address);
void ym_data_write(ym2612_context * context, uint8_t value);
+void ym_vgm_log(ym2612_context *context, uint32_t master_clock, vgm_writer *vgm);
uint8_t ym_read_status(ym2612_context * context, uint32_t cycle, uint32_t port);
uint8_t ym_load_gst(ym2612_context * context, FILE * gstfile);
uint8_t ym_save_gst(ym2612_context * context, FILE * gstfile);