bind the game boy

TODO: sound
This commit is contained in:
tildearrow 2021-05-26 03:17:12 -05:00
parent 30692985dc
commit c2b3d85f77
6 changed files with 223 additions and 201 deletions

View file

@ -37,6 +37,7 @@ src/engine/platform/abstract.cpp
src/engine/platform/genesis.cpp src/engine/platform/genesis.cpp
src/engine/platform/genesisext.cpp src/engine/platform/genesisext.cpp
src/engine/platform/sms.cpp src/engine/platform/sms.cpp
src/engine/platform/gb.cpp
src/engine/platform/dummy.cpp) src/engine/platform/dummy.cpp)
#imgui/imgui.cpp #imgui/imgui.cpp

View file

@ -5,6 +5,7 @@
#include "platform/genesis.h" #include "platform/genesis.h"
#include "platform/genesisext.h" #include "platform/genesisext.h"
#include "platform/sms.h" #include "platform/sms.h"
#include "platform/gb.h"
#include "platform/dummy.h" #include "platform/dummy.h"
#include <math.h> #include <math.h>
#include <zlib.h> #include <zlib.h>
@ -693,6 +694,9 @@ bool DivEngine::init() {
case DIV_SYSTEM_SMS: case DIV_SYSTEM_SMS:
dispatch=new DivPlatformSMS; dispatch=new DivPlatformSMS;
break; break;
case DIV_SYSTEM_GB:
dispatch=new DivPlatformGB;
break;
default: default:
dispatch=new DivPlatformDummy; dispatch=new DivPlatformDummy;
break; break;

171
src/engine/platform/gb.cpp Normal file
View file

@ -0,0 +1,171 @@
#include "gb.h"
#include "../engine.h"
#include <math.h>
void DivPlatformGB::acquire(int& l, int& r) {
GB_apu_run(gb);
l=gb->apu_output.summed_samples[0].left+
gb->apu_output.summed_samples[1].left+
gb->apu_output.summed_samples[2].left+
gb->apu_output.summed_samples[3].left;
r=gb->apu_output.summed_samples[0].right+
gb->apu_output.summed_samples[1].right+
gb->apu_output.summed_samples[2].right+
gb->apu_output.summed_samples[3].right;
}
void DivPlatformGB::tick() {
for (int i=0; i<4; i++) {
chan[i].std.next();
if (chan[i].std.hadVol) {
chan[i].outVol=(chan[i].vol*chan[i].std.vol)>>4;
//sn->write(0x90|(i<<5)|(15-(chan[i].outVol&15)));
}
if (chan[i].std.hadArp) {
if (chan[i].std.arpMode) {
chan[i].baseFreq=round(1712.0f/pow(2.0f,((float)(chan[i].std.arp)/12.0f)));
} else {
chan[i].baseFreq=round(1712.0f/pow(2.0f,((float)(chan[i].note+chan[i].std.arp-12)/12.0f)));
}
chan[i].freqChanged=true;
}
if (chan[i].std.hadDuty) {
snNoiseMode=(snNoiseMode&2)|(chan[i].std.duty&1);
if (chan[i].std.duty<2) {
chan[3].freqChanged=false;
}
updateSNMode=true;
}
}
for (int i=0; i<3; i++) {
if (chan[i].freqChanged) {
chan[i].freq=(chan[i].baseFreq*(ONE_SEMITONE-chan[i].pitch))/ONE_SEMITONE;
if (chan[i].note>0x5d) chan[i].freq=0x01;
//sn->write(0x80|i<<5|(chan[i].freq&15));
//sn->write(chan[i].freq>>4);
chan[i].freqChanged=false;
}
}
if (chan[3].freqChanged || updateSNMode) {
updateSNMode=false;
chan[3].freq=(chan[3].baseFreq*(ONE_SEMITONE-chan[3].pitch))/ONE_SEMITONE;
if (chan[3].note>0x5d) chan[3].freq=0x01;
chan[3].freqChanged=false;
if (snNoiseMode&2) { // take period from channel 3
if (snNoiseMode&1) {
//sn->write(0xe7);
} else {
//sn->write(0xe3);
}
//sn->write(0xdf);
//sn->write(0xc0|(chan[3].freq&15));
//sn->write(chan[3].freq>>4);
} else { // 3 fixed values
unsigned char value;
if (chan[3].std.hadArp) {
if (chan[3].std.arpMode) {
value=chan[3].std.arp%12;
} else {
value=(chan[3].note+chan[3].std.arp)%12;
}
} else {
value=chan[3].note%12;
}
if (value<3) {
value=2-value;
//sn->write(0xe0|value|((snNoiseMode&1)<<2));
}
}
}
}
int DivPlatformGB::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON:
chan[c.chan].baseFreq=round(1712.0f/pow(2.0f,((float)c.value/12.0f)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
chan[c.chan].active=true;
//sn->write(0x90|c.chan<<5|(15-(chan[c.chan].vol&15)));
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
break;
case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false;
//sn->write(0x9f|c.chan<<5);
chan[c.chan].std.init(NULL);
break;
case DIV_CMD_INSTRUMENT:
chan[c.chan].ins=c.value;
//chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.hasVol) {
chan[c.chan].outVol=c.value;
}
//sn->write(0x90|c.chan<<5|(15-(chan[c.chan].vol&15)));
}
break;
case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.hasVol) {
return chan[c.chan].vol;
}
return chan[c.chan].outVol;
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=round(1712.0f/pow(2.0f,((float)c.value2/12.0f)));
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) return 2;
break;
}
case DIV_CMD_STD_NOISE_MODE:
snNoiseMode=(c.value&1)|((c.value&16)>>3);
updateSNMode=true;
break;
case DIV_CMD_LEGATO:
chan[c.chan].baseFreq=round(1712.0f/pow(2.0f,((float)c.value/12.0f)));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
case DIV_CMD_PRE_PORTA:
chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
break;
case DIV_CMD_GET_VOLMAX:
return 15;
break;
default:
break;
}
return 1;
}
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate) {
parent=p;
rate=sugRate; // TODO: use blip_buf
gb=new GB_gameboy_t;
memset(gb,0,sizeof(GB_gameboy_t));
GB_apu_init(gb);
GB_set_sample_rate(gb,rate);
snNoiseMode=3;
updateSNMode=false;
return 4;
}

39
src/engine/platform/gb.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef _GB_H
#define _GB_H
#include "../dispatch.h"
#include "../macroInt.h"
#include "sound/gb/gb.h"
class DivPlatformGB: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch;
unsigned char ins, note;
bool active, insChanged, freqChanged, keyOn, keyOff;
signed char vol, outVol;
DivMacroInt std;
Channel():
freq(0),
baseFreq(0),
pitch(0),
ins(-1),
note(0),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
vol(15) {}
};
Channel chan[4];
unsigned char snNoiseMode;
bool updateSNMode;
GB_gameboy_t* gb;
public:
void acquire(int& l, int& r);
int dispatch(DivCommand c);
void tick();
int init(DivEngine* parent, int channels, int sugRate);
};
#endif

View file

@ -5,6 +5,9 @@
#include <stddef.h> #include <stddef.h>
#include "gb_struct_def.h" #include "gb_struct_def.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Speed = 1 / Length (in seconds) */ /* Speed = 1 / Length (in seconds) */
#define DAC_DECAY_SPEED 20000 #define DAC_DECAY_SPEED 20000
@ -183,4 +186,8 @@ void GB_apu_run(GB_gameboy_t *gb);
void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb); void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb);
void GB_borrow_sgb_border(GB_gameboy_t *gb); void GB_borrow_sgb_border(GB_gameboy_t *gb);
#ifdef __cplusplus
}
#endif
#endif /* apu_h */ #endif /* apu_h */

View file

@ -346,219 +346,19 @@ typedef struct {
This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64 This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64
bit platforms. */ bit platforms. */
/* minimal GB_gameboy_s struct with just the APU */
struct GB_gameboy_s { struct GB_gameboy_s {
/* Registers */
uint16_t pc;
union {
uint16_t registers[GB_REGISTERS_16_BIT];
struct {
uint16_t af,
bc,
de,
hl,
sp;
};
struct {
#ifdef GB_BIG_ENDIAN
uint8_t a, f,
b, c,
d, e,
h, l;
#else
uint8_t f, a,
c, b,
e, d,
l, h;
#endif
};
};
uint8_t ime;
uint8_t interrupt_enable;
uint8_t cgb_ram_bank;
/* CPU and General Hardware Flags*/ /* CPU and General Hardware Flags*/
GB_model_t model; GB_model_t model;
bool cgb_mode; bool cgb_mode;
bool cgb_double_speed; bool cgb_double_speed;
bool halted; bool halted;
bool stopped; bool stopped;
bool boot_rom_finished;
bool ime_toggle; /* ei has delayed a effect.*/
bool halt_bug;
bool just_halted;
/* Misc state */
bool infrared_input;
uint8_t extra_oam[0xff00 - 0xfea0];
uint32_t ram_size; // Different between CGB and DMG
int32_t ir_sensor;
bool effective_ir_input;
/* DMA and HDMA */
bool hdma_on;
bool hdma_on_hblank;
uint8_t hdma_steps_left;
int16_t hdma_cycles; // in 8MHz units
uint16_t hdma_current_src, hdma_current_dest;
uint8_t dma_steps_left;
uint8_t dma_current_dest;
uint16_t dma_current_src;
int16_t dma_cycles;
bool is_dma_restarting;
uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */
bool hdma_starting;
/* MBC */
uint16_t mbc_rom_bank;
uint8_t mbc_ram_bank;
uint32_t mbc_ram_size;
bool mbc_ram_enable;
union {
struct {
uint8_t bank_low:5;
uint8_t bank_high:2;
uint8_t mode:1;
} mbc1;
struct {
uint8_t rom_bank:4;
} mbc2;
struct {
uint8_t rom_bank:8;
uint8_t ram_bank:3;
} mbc3;
struct {
uint8_t rom_bank_low;
uint8_t rom_bank_high:1;
uint8_t ram_bank:4;
} mbc5;
struct {
uint8_t bank_low:6;
uint8_t bank_high:3;
bool mode:1;
bool ir_mode:1;
} huc1;
struct {
uint8_t rom_bank:7;
uint8_t padding:1;
uint8_t ram_bank:4;
} huc3;
};
uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */
bool camera_registers_mapped;
uint8_t camera_registers[0x36];
uint8_t rumble_strength;
bool cart_ir;
// TODO: move to huc3/mbc3/tpp1 struct when breaking save compat
uint8_t huc3_mode;
uint8_t huc3_access_index;
uint16_t huc3_minutes, huc3_days;
uint16_t huc3_alarm_minutes, huc3_alarm_days;
bool huc3_alarm_enabled;
uint8_t huc3_read;
uint8_t huc3_access_flags;
bool mbc3_rtc_mapped;
uint16_t tpp1_rom_bank;
uint8_t tpp1_ram_bank;
uint8_t tpp1_mode;
/* HRAM and HW Registers */
uint8_t hram[0xFFFF - 0xFF80];
uint8_t io_registers[0x80]; uint8_t io_registers[0x80];
// oops
uint16_t div_counter; uint16_t div_counter;
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
uint16_t serial_cycles;
uint16_t serial_length;
uint8_t double_speed_alignment;
uint8_t serial_count;
GB_apu_t apu; GB_apu_t apu;
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
/* This data is reserved on reset and must come last in the struct */
/* ROM */
unsigned pending_cycles;
/* Various RAMs */
uint8_t *ram;
uint8_t *vram;
uint8_t *mbc_ram;
/* I/O */
uint32_t *screen;
uint32_t background_palettes_rgb[0x20];
uint32_t sprite_palettes_rgb[0x20];
const GB_palette_t *dmg_palette;
double light_temperature;
/* Timing */
uint64_t last_sync;
uint64_t cycles_since_last_sync; // In 8MHz units
GB_rtc_mode_t rtc_mode;
/* Audio */
GB_apu_output_t apu_output; GB_apu_output_t apu_output;
/*** Debugger ***/
volatile bool debug_stopped, debug_disable;
bool debug_fin_command, debug_next_command;
/* SLD (Todo: merge with backtrace) */
bool stack_leak_detection;
signed debug_call_depth;
uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */
uint16_t addr_for_call_depth[0x200];
/* Backtrace */
unsigned backtrace_size;
uint16_t backtrace_sps[0x200];
struct {
uint16_t bank;
uint16_t addr;
} backtrace_returns[0x200];
/* Ticks command */
uint64_t debugger_ticks;
/* Undo */
uint8_t *undo_state;
const char *undo_label;
/* Rewind */
#define GB_REWIND_FRAMES_PER_KEY 255
size_t rewind_buffer_length;
struct {
uint8_t *key_state;
uint8_t *compressed_states[GB_REWIND_FRAMES_PER_KEY];
unsigned pos;
} *rewind_sequences; // lasts about 4 seconds
size_t rewind_pos;
/* Misc */
bool turbo;
bool turbo_dont_skip;
bool disable_rendering;
uint8_t boot_rom[0x900];
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
double clock_multiplier;
/* Temporary state */
bool wx_just_changed;
bool tile_sel_glitch;
}; };
#ifndef __printflike #ifndef __printflike