diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d23f54d1..1aee7572c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ src/engine/platform/abstract.cpp src/engine/platform/genesis.cpp src/engine/platform/genesisext.cpp src/engine/platform/sms.cpp +src/engine/platform/gb.cpp src/engine/platform/dummy.cpp) #imgui/imgui.cpp diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 32b13f9e5..a32fa55c4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -5,6 +5,7 @@ #include "platform/genesis.h" #include "platform/genesisext.h" #include "platform/sms.h" +#include "platform/gb.h" #include "platform/dummy.h" #include #include @@ -693,6 +694,9 @@ bool DivEngine::init() { case DIV_SYSTEM_SMS: dispatch=new DivPlatformSMS; break; + case DIV_SYSTEM_GB: + dispatch=new DivPlatformGB; + break; default: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp new file mode 100644 index 000000000..78f186775 --- /dev/null +++ b/src/engine/platform/gb.cpp @@ -0,0 +1,171 @@ +#include "gb.h" +#include "../engine.h" +#include + +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; +} diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h new file mode 100644 index 000000000..8a2f8f904 --- /dev/null +++ b/src/engine/platform/gb.h @@ -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 diff --git a/src/engine/platform/sound/gb/apu.h b/src/engine/platform/sound/gb/apu.h index 06162034b..a9447ea0d 100644 --- a/src/engine/platform/sound/gb/apu.h +++ b/src/engine/platform/sound/gb/apu.h @@ -5,6 +5,9 @@ #include #include "gb_struct_def.h" +#ifdef __cplusplus +extern "C" { +#endif /* Speed = 1 / Length (in seconds) */ #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_borrow_sgb_border(GB_gameboy_t *gb); +#ifdef __cplusplus +} +#endif + #endif /* apu_h */ diff --git a/src/engine/platform/sound/gb/gb.h b/src/engine/platform/sound/gb/gb.h index 5de0fe6b6..eb29a02f8 100644 --- a/src/engine/platform/sound/gb/gb.h +++ b/src/engine/platform/sound/gb/gb.h @@ -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 bit platforms. */ +/* minimal GB_gameboy_s struct with just the APU */ 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*/ GB_model_t model; bool cgb_mode; bool cgb_double_speed; bool halted; 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]; - - // oops 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; - - - /* 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; - - /*** 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