Skip to content

Commit

Permalink
Add APU log to emulator, and VGM output demo
Browse files Browse the repository at this point in the history
  • Loading branch information
binji committed Feb 7, 2022
1 parent ba152e9 commit eda1522
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docs/binjgb.js

Large diffs are not rendered by default.

Binary file modified docs/binjgb.wasm
Binary file not shown.
127 changes: 123 additions & 4 deletions docs/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const AUDIO_FRAMES = 4096;
const AUDIO_LATENCY_SEC = 0.1;
const MAX_UPDATE_SEC = 5 / 60;
const CPU_TICKS_PER_SECOND = 4194304;
const CPU_TICKS_PER_60HZ = Math.floor(CPU_TICKS_PER_SECOND / 60);
const CPU_TICK_FRACTION_PER_60HZ = CPU_TICKS_PER_SECOND - CPU_TICKS_PER_60HZ * 60;
const EVENT_NEW_FRAME = 1;
const EVENT_AUDIO_BUFFER_FULL = 2;
const EVENT_UNTIL_TICKS = 4;
Expand Down Expand Up @@ -76,6 +78,7 @@ let data = {
cgbColorCurve: 2, // Gambatte/Gameboy Online
colorOptions: false,
needsReload: false,
recordingVGM: false,
};

let vm = new Vue({
Expand Down Expand Up @@ -141,6 +144,9 @@ let vm = new Vue({
if (!this.selectedFileHasImage) return '';
return this.selectedFile.image;
},
vgmLabel: function() {
return this.recordingVGM ? 'stop recording VGM' : 'record VGM';
},
},
watch: {
paused: function(newPaused, oldPaused) {
Expand Down Expand Up @@ -234,9 +240,11 @@ let vm = new Vue({
downloadSave: async function(file) {
if (file.extRam) {
const el = $('#downloadEl');
el.href = URL.createObjectURL(file.extRam);
const url = URL.createObjectURL(file.extRam);
el.href = url;
el.download = file.name + '.sav';
el.click();
URL.revokeObjectURL(url);
}
},
uploadSaveClicked: function() {
Expand Down Expand Up @@ -309,6 +317,23 @@ let vm = new Vue({
};
return date.toLocaleDateString(undefined, options);
},
toggleRecordVGM: function() {
this.recordingVGM = !this.recordingVGM;
if (this.recordingVGM) {
emulator.vgmWriter.enabled = true;
} else {
emulator.vgmWriter.enabled = false;
const buffer = emulator.vgmWriter.write();
const blob = new Blob([buffer]);

const el = $('#downloadEl');
const url = URL.createObjectURL(blob);
el.href = url;
el.download = this.loadedFile.name + '.vgm';
el.click();
URL.revokeObjectURL(url);
}
},
}
});

Expand Down Expand Up @@ -347,6 +372,7 @@ class Emulator {
this.video = new Video(module, this.e, $('canvas'));
this.rewind = new Rewind(module, this.e);
this.rewindIntervalId = 0;
this.vgmWriter = new VGMWriter(module, this.e);

this.lastRafSec = 0;
this.leftoverTicks = 0;
Expand Down Expand Up @@ -476,9 +502,23 @@ class Emulator {
return this.module._emulator_get_ticks_f64(this.e);
}

runUntil(ticks) {
getNext60HzTicks(currentTicks) {
// Modulus to 1 second.
const mod1SecTicks = currentTicks -
Math.floor(currentTicks / CPU_TICKS_PER_SECOND) * CPU_TICKS_PER_SECOND;
// Round up to next 60Hz interval. Add 1 to ensure we go to the next 60Hz
// interval, in case we're on the boundary.
const next1SecTicks = Math.ceil(
Math.ceil((mod1SecTicks + 1) / CPU_TICKS_PER_60HZ) *
CPU_TICKS_PER_60HZ);
return currentTicks + (next1SecTicks - mod1SecTicks);
}

runUntil(untilTicks) {
let next60hzTicks = this.getNext60HzTicks(this.ticks);
while (true) {
const event = this.module._emulator_run_until_f64(this.e, ticks);
const event = this.module._emulator_run_until_f64(
this.e, Math.min(untilTicks, next60hzTicks));
if (event & EVENT_NEW_FRAME) {
this.rewind.pushBuffer();
this.video.uploadTexture();
Expand All @@ -487,7 +527,13 @@ class Emulator {
this.audio.pushBuffer();
}
if (event & EVENT_UNTIL_TICKS) {
break;
const currentTicks = this.ticks;
if (currentTicks >= next60hzTicks) { // Hit the 60Hz mark.
this.vgmWriter.onFrame();
next60hzTicks = this.getNext60HzTicks(currentTicks);
} else { // Ran for the requested time.
break;
}
}
}
if (this.module._emulator_was_ext_ram_updated(this.e)) {
Expand Down Expand Up @@ -1087,3 +1133,76 @@ class Rewind {
this.statePtr = 0;
}
}

class VGMWriter {
constructor(module, e) {
this.module = module;
this.e = e;
this.frames = [];
this.isEnabled = false;
}

get enabled() {
return this.isEnabled;
}

set enabled(set) {
this.isEnabled = set;
this.module._set_log_apu_writes(this.e, set);
}

onFrame() {
if (!this.isEnabled) return;
const buffer = makeWasmBuffer(
this.module, this.module._get_apu_log_data_ptr(this.e),
this.module._get_apu_log_data_size(this.e));
const frame = buffer.slice(); // Copy out data.
this.frames.push(frame);
this.module._reset_apu_log(this.e);
}

write() {
// The only commands used are:
// $62: Wait for 1/60th of a second
// $66: End of sound data
// $B3 aa dd: Write DMG register
const HeaderSize = 256;
let size = HeaderSize + 1; // $66 End of sound data
for (let frame of this.frames) {
// $B3 count
size += (frame.length >>> 1) * 3;
// $62 count
size += 1;
}
const buffer = new ArrayBuffer(size);
const data = new Uint8Array(buffer);
const dv = new DataView(buffer);
// Write magic
data[0] = 0x56;
data[1] = 0x67;
data[2] = 0x6d;
data[3] = 0x20;
// Write EOF offset
dv.setUint32(0x04, size - 4, true);
// Write version 1.61
dv.setUint32(0x08, 0x161, true);
// Write total # samples (1/60th of a second = 735 samples)
dv.setUint32(0x18, this.frames.length * 735, true);
// Write offset to data stream
dv.setUint32(0x34, HeaderSize - 0x34, true);
// Write DMG clock
dv.setUint32(0x80, 4194304, true);
// Write data stream
let offset = HeaderSize;
for (let frame of this.frames) {
for (let i = 0; i < frame.length; i += 2) {
data[offset++] = 0xb3; // DMG write command.
data[offset++] = frame[i];
data[offset++] = frame[i + 1];
}
data[offset++] = 0x62; // Frame wait command.
}
data[offset++] = 0x66; // End of data stream command.
return buffer;
}
}
5 changes: 4 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@
@click="downloadSave(selectedFile)">
download save
</button>
<a id="downloadEl" hidden download="game.sav"></a>
<button
v-bind:disabled="isFilesListEmpty"
@click="uploadSaveClicked">
Expand Down Expand Up @@ -252,6 +251,9 @@
<input id="colorOptions" v-if="loaded" type="checkbox"
v-model:checked="colorOptions"></input>
<label v-if="loaded" for="colorOptions">color options...</label>
<button
v-show="loaded"
@click="toggleRecordVGM">{{vgmLabel}}</button>
</div>
<div class="right">
<span>{{fps.toFixed(1)}}</span> FPS
Expand Down Expand Up @@ -287,6 +289,7 @@
</div>
</div>
</div>
<a id="downloadEl" hidden download="game.sav"></a>
</div>
<script src="sha1.js"></script>
<script src="binjgb.js"></script>
Expand Down
9 changes: 7 additions & 2 deletions src/emscripten/exported.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
"_emulator_new_simple",
"_emulator_read_ext_ram",
"_emulator_run_until_f64",
"_emulator_set_default_joypad_callback",
"_emulator_set_builtin_palette",
"_emulator_set_bw_palette_simple",
"_emulator_set_default_joypad_callback",
"_emulator_set_rewind_joypad_callback",
"_emulator_was_ext_ram_updated",
"_emulator_write_ext_ram",
"_ext_ram_file_data_new",
"_file_data_delete",
"_get_apu_log_data_ptr",
"_get_apu_log_data_size",
"_get_audio_buffer_capacity",
"_get_audio_buffer_ptr",
"_get_file_data_ptr",
Expand All @@ -22,6 +24,7 @@
"_get_sgb_frame_buffer_size",
"_joypad_delete",
"_joypad_new",
"_reset_apu_log",
"_rewind_append",
"_rewind_begin",
"_rewind_delete",
Expand All @@ -37,4 +40,6 @@
"_set_joyp_right",
"_set_joyp_select",
"_set_joyp_start",
"_set_joyp_up"]
"_set_joyp_up",
"_set_log_apu_writes"
]
18 changes: 18 additions & 0 deletions src/emscripten/wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef struct {

static Emulator* e;

static EmulatorConfig s_config;
static EmulatorInit s_init;
static JoypadButtons s_buttons;

Expand Down Expand Up @@ -188,3 +189,20 @@ void file_data_delete(FileData* file_data) {
xfree(file_data->data);
xfree(file_data);
}

void set_log_apu_writes(Emulator* e, Bool set) {
s_config.log_apu_writes = set;
emulator_set_config(e, &s_config);
}

size_t get_apu_log_data_size(Emulator* e) {
ApuLog* apu_log = emulator_get_apu_log(e);
return apu_log->write_count * sizeof(apu_log->writes[0]);
}

void* get_apu_log_data_ptr(Emulator* e) {
ApuLog* apu_log = emulator_get_apu_log(e);
return &apu_log->writes[0];
}

void reset_apu_log(Emulator* e) { return emulator_reset_apu_log(e); }
17 changes: 17 additions & 0 deletions src/emulator.c
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ struct Emulator {
PaletteRGBA pal[PALETTE_TYPE_COUNT];
PaletteRGBA sgb_pal[4];
CgbColorCurve cgb_color_curve;
ApuLog apu_log;
};


Expand Down Expand Up @@ -2999,6 +3000,14 @@ static void write_noise_period(Emulator* e) {
}

static void write_apu(Emulator* e, MaskedAddress addr, u8 value) {
if (e->config.log_apu_writes || !APU.initialized) {
if (e->apu_log.write_count < MAX_APU_LOG_FRAME_WRITES) {
ApuWrite* write = &e->apu_log.writes[e->apu_log.write_count++];
write->addr = addr;
write->value = value;
}
}

if (!APU.enabled) {
if (!IS_CGB && (addr == APU_NR11_ADDR || addr == APU_NR21_ADDR ||
addr == APU_NR31_ADDR || addr == APU_NR41_ADDR)) {
Expand Down Expand Up @@ -5014,3 +5023,11 @@ void emulator_set_builtin_palette(Emulator* e, u32 index) {
}
update_bw_palette_rgba(e, PALETTE_TYPE_BGP);
}

ApuLog* emulator_get_apu_log(Emulator* e) {
return &e->apu_log;
}

void emulator_reset_apu_log(Emulator* e) {
e->apu_log.write_count = 0;
}
16 changes: 16 additions & 0 deletions src/emulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ extern "C" {
#define RGBA_DARK_GRAY 0xff555555u
#define RGBA_BLACK 0xff000000u

#define MAX_APU_LOG_FRAME_WRITES 1024

typedef struct Emulator Emulator;

enum {
Expand Down Expand Up @@ -172,8 +174,19 @@ typedef struct EmulatorConfig {
Bool disable_window;
Bool disable_obj;
Bool allow_simulataneous_dpad_opposites;
Bool log_apu_writes;
} EmulatorConfig;

typedef struct {
u8 addr;
u8 value;
} ApuWrite;

typedef struct {
ApuWrite writes[MAX_APU_LOG_FRAME_WRITES];
size_t write_count;
} ApuLog;

typedef u32 EmulatorEvent;
enum {
EMULATOR_EVENT_NEW_FRAME = 0x1,
Expand Down Expand Up @@ -223,6 +236,9 @@ Result emulator_write_ext_ram_to_file(Emulator*, const char* filename);
EmulatorEvent emulator_step(Emulator*);
EmulatorEvent emulator_run_until(Emulator*, Ticks until_ticks);

ApuLog* emulator_get_apu_log(Emulator*);
void emulator_reset_apu_log(Emulator*);

#ifdef __cplusplus
}
#endif
Expand Down

0 comments on commit eda1522

Please sign in to comment.