diff --git a/.gitignore b/.gitignore index 478cd55..a66f954 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,8 @@ build/ cmake-build-debug/ .idea/ src/.vscode/ -src/dmg_boot.gb \ No newline at end of file +src/dmg_boot.gb +tests/* +.vscode/* +run.sh +output.txt \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 31053f6..3300a6c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -54,6 +54,35 @@ "ios": "cpp", "queue": "cpp", "semaphore": "cpp", - "cinttypes": "cpp" + "cinttypes": "cpp", + "any": "cpp", + "hash_map": "cpp", + "strstream": "cpp", + "charconv": "cpp", + "codecvt": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "unordered_set": "cpp", + "optional": "cpp", + "source_location": "cpp", + "format": "cpp", + "fstream": "cpp", + "future": "cpp", + "iomanip": "cpp", + "iostream": "cpp", + "istream": "cpp", + "mutex": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdfloat": "cpp", + "text_encoding": "cpp", + "cfenv": "cpp", + "typeindex": "cpp", + "valarray": "cpp", + "variant": "cpp" } } \ No newline at end of file diff --git a/run.sh b/run.sh index f6fd32c..6034860 100755 --- a/run.sh +++ b/run.sh @@ -22,14 +22,14 @@ elif [[ $last_keyword == "gbemu" ]]; then echo "making new build directory" mkdir build cd build - cmake .. + cmake .. cmake --build . -j8 ./gbemu else echo "making new build directory" mkdir build cd build - cmake .. + cmake -DDEBUG=on .. cmake --build . -j8 ./gbemu fi diff --git a/src/audio.cpp b/src/audio.cpp index 9a95636..4f4eeb1 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -1,5 +1,4 @@ #include "audio.h" -#include "types.h" APU::APU() { @@ -16,7 +15,7 @@ APU::APU() enableVINRight = false; volumeLeft = 0; volumeRight = 0; - + mMap = nullptr; channel1 = new PulseChannel(CH1); channel2 = new PulseChannel(CH2); channel3 = new WaveChannel(); @@ -47,6 +46,7 @@ bool APU::init() channel2->setFrameSequencer(frameSequencer); channel3->setFrameSequencer(frameSequencer); channel4->setFrameSequencer(frameSequencer); + return true; } @@ -55,9 +55,16 @@ void APU::test() printf("APU test\n"); } +// initialize the writehandler of memorymap +void APU::initializeWriteHandler() +{ + if (mMap) + mMap->setAudioWriteHandler([this](Word address) { this->onMemoryWrite(address); }); +} + void APU::writeByte(Word address, Byte value) { - printf("APU Address: %X, Value: %X\n", address, value); + printf("APU Address: %04X, Value: %02X\n", address, value); if (address == 0xFF26) { bool enable = (value & 0x80) >> 7; @@ -158,7 +165,6 @@ Byte APU::readByte(Word address) case 0xFF26: val = (enabled ? 0x80 : 0) | (channel1->isEnabled() ? 0x01 : 0) | (channel2->isEnabled() ? 0x02 : 0) | (channel3->isEnabled() ? 0x04 : 0) | (channel4->isEnabled() ? 0x08 : 0) | 0x70; - printf("APU Read 0xFF26: %X\n", val); return val; default: @@ -176,7 +182,6 @@ void APU::stepAPU(int cycles) if (frameSequencerCounter >= 8192) { // update envelope clocks and length timers - channel1->run(); channel2->run(); channel3->run(); @@ -189,6 +194,13 @@ void APU::stepAPU(int cycles) channel2->setFrameSequencer(frameSequencer); channel3->setFrameSequencer(frameSequencer); channel4->setFrameSequencer(frameSequencer); + + // Read and write back after updating + Word address[] = { 0xFF19, 0xFF1E, 0xFF23, 0xFF26 }; + for (auto addr : address) + { + audioRegisterUpdate(addr, AudioWrite); + } } } @@ -205,6 +217,33 @@ void APU::clearRegisters() channel2->powerOff(); channel3->powerOff(); channel4->powerOff(); + // Could be done by simply writing 0s but for checking's sake done as such + for (int address = 0xFF10; address <= 0xFF3F; address++) + { + audioRegisterUpdate(address, AudioWrite); + } +} + +// Updates APU registers on write in MemoryMap +void APU::onMemoryWrite(Word address) +{ + // address where write has occurred + audioRegisterUpdate(address, AudioMemoryWrite); + // Update the audio channel controller register + audioRegisterUpdate(AUDIO_MASTER_CONTROL_REGISTER, AudioWrite); +} + +// Write Update +void APU::audioRegisterUpdate(Word address, audioWriteFlag flag) +{ + Byte value = 0xFF; + if (flag == AudioMemoryWrite) + { + value = mMap->readMemory(address); + writeByte(address, value); + } + value = readByte(address); + mMap->MemoryWriteBack(address, value); } // PulseChannel @@ -728,4 +767,4 @@ void NoiseChannel::trigger() { LFSR = 0x7FFF; enabled = dacEnabled; -} \ No newline at end of file +} diff --git a/src/audio.h b/src/audio.h index 1255f41..464f06f 100644 --- a/src/audio.h +++ b/src/audio.h @@ -1,7 +1,10 @@ #pragma once #include "types.h" +#include "mmap.h" #include -#include +#include // SDL Audio + +#define AUDIO_MASTER_CONTROL_REGISTER 0xFF26 enum Channel { @@ -11,13 +14,21 @@ enum Channel CH4 = 3 }; +// For checking whether Write is due to memory or APU itself +enum audioWriteFlag +{ + AudioWrite = 0, + AudioMemoryWrite = 1 + +}; + class PulseChannel { private: Channel channel; - bool enabled; - bool dacEnabled; - int frameSequencer; + bool enabled; + bool dacEnabled; + int frameSequencer; Byte sweepPeriod; bool sweepNegate; @@ -26,7 +37,7 @@ class PulseChannel // NRx1 Byte waveDuty; int lengthTimer; - int maxLengthTimer = 64; + int maxLengthTimer = 64; Byte envelopeInitialVolume; bool envelopeIncrease; @@ -41,25 +52,24 @@ class PulseChannel void test(); void writeByte(Word address, Byte value); Byte readByte(Word address); - bool isEnabled(); - void powerOff(); - void run(); - void set_NRx4(Byte value); - void setFrameSequencer(int frameSequencer); - void trigger(); + bool isEnabled(); + void powerOff(); + void run(); + void set_NRx4(Byte value); + void setFrameSequencer(int frameSequencer); + void trigger(); }; class WaveChannel { private: - Byte waveRAM[16]; bool dacEnabled; bool enabled; int lengthTimer; int maxLengthTimer = 256; - int frameSequencer; + int frameSequencer; Byte outputLevel; @@ -73,22 +83,22 @@ class WaveChannel void writeByte(Word address, Byte value); Byte readByte(Word address); void trigger(); - bool isEnabled(); - void powerOff(); - void set_NRx4(Byte value); - void run(); - void setFrameSequencer(int frameSequencer); + bool isEnabled(); + void powerOff(); + void set_NRx4(Byte value); + void run(); + void setFrameSequencer(int frameSequencer); }; class NoiseChannel { private: - bool enabled; - bool dacEnabled; + bool enabled; + bool dacEnabled; int lengthTimer; int maxLengthTimer = 64; - int frameSequencer; + int frameSequencer; Byte envelopeInitialVolume; bool envelopeIncrease; @@ -112,11 +122,11 @@ class NoiseChannel void writeByte(Word address, Byte value); Byte readByte(Word address); void trigger(); - bool isEnabled(); - void powerOff(); - void set_NRx4(Byte value); - void run(); - void setFrameSequencer(int frameSequencer); + bool isEnabled(); + void powerOff(); + void set_NRx4(Byte value); + void run(); + void setFrameSequencer(int frameSequencer); }; class APU @@ -160,12 +170,22 @@ class APU WaveChannel* channel3; NoiseChannel* channel4; + // Memory Map + MemoryMap* mMap; + public: APU(); void test(); - bool init(); + bool init(); void writeByte(Word address, Byte value); Byte readByte(Word address); void stepAPU(int cycles); void clearRegisters(); + void setMemoryMap(MemoryMap* map) { mMap = map; } + // Writes back on Memory Write + void onMemoryWrite(Word address); + // Write update + void audioRegisterUpdate(Word address, audioWriteFlag flag); + // initializes the audioWriteHandler of MemoryMap + void initializeWriteHandler(); }; \ No newline at end of file diff --git a/src/gameBoy.cpp b/src/gameBoy.cpp index 9f25b3e..047ae3c 100644 --- a/src/gameBoy.cpp +++ b/src/gameBoy.cpp @@ -16,7 +16,7 @@ GBE::GBE() gbe_graphics = new PPU(); // audio = new Audio(); - // APU = new APU(); + gbe_audio = new APU(); // Unify the CPU and MemoryMap gbe_cpu->setMemory(gbe_mMap); @@ -24,22 +24,26 @@ GBE::GBE() // Unify the CPU and PPU gbe_cpu->setPPU(gbe_graphics); - // Unify the PPU and MmeoryMap + // Unify the PPU and MemoryMap gbe_graphics->setMemoryMap(gbe_mMap); - gbe_graphics->init(); + // Unify the APU and MemoryMap + gbe_audio->setMemoryMap(gbe_mMap); + // initialize the write handler + gbe_audio->initializeWriteHandler(); + // Open the Boot ROM if ((bootROM = fopen("../src/dmg_boot.gb", "rb")) == NULL) - printf("boot rom file not opened"); + printf("boot rom file not opened\n"); // // Open the Game ROM // if ((gameROM = fopen("../tests/tetris.gb", "rb")) == NULL) // printf("game rom file not opened"); // Open the Game ROM - if ((gameROM = fopen("../tests/dmg_sound/rom_singles/03-trigger.gb", "rb")) == NULL) - printf("game rom file not opened"); + if ((gameROM = fopen("../tests/dmg_sound/rom_singles/02-len ctr.gb", "rb")) == NULL) + printf("game rom file not opened\n"); // Set the Boot ROM gbe_mMap->setBootRomFile(bootROM); @@ -105,7 +109,6 @@ GBE::GBE() gbe_mMap->debugWriteMemory(0x133, 0x3E); executeBootROM(); - update(); } @@ -122,7 +125,7 @@ void GBE::update() // update the DIV and TIMA timers gbe_cpu->updateTimers(s_Cycles); gbe_graphics->executePPU(s_Cycles); - gbe_mMap->audio->stepAPU(s_Cycles); + gbe_audio->stepAPU(s_Cycles); s_Cycles = 0; s_Cycles += gbe_cpu->performInterrupt(); gbe_graphics->pollEvents(); diff --git a/src/gameBoy.h b/src/gameBoy.h index 35c22f8..f7ac04f 100644 --- a/src/gameBoy.h +++ b/src/gameBoy.h @@ -3,6 +3,7 @@ #include "cpu.h" #include "mmap.h" #include "graphics.h" +#include "audio.h" // GBE stands for GameBoyEmulator @@ -28,6 +29,9 @@ class GBE // Pointer to the Graphics PPU* gbe_graphics; + // Pointer to the Audio + APU* gbe_audio; + // File pointer for Boot ROM FILE* bootROM; diff --git a/src/mmap.cpp b/src/mmap.cpp index 6d71288..a7f19cd 100644 --- a/src/mmap.cpp +++ b/src/mmap.cpp @@ -103,19 +103,33 @@ MemoryMap::MemoryMap() bootRomFile = nullptr; romFile = nullptr; - - audio = new APU(); + audioWriteHandler = nullptr; mbcMode = 0x0; } +// MemoryMap Destructor +MemoryMap::~MemoryMap() +{ + delete romBank0; + delete romBank1; + delete videoRam; + delete externalRam; + delete workRam; + delete oamTable; + delete ioPorts; + delete highRam; + delete interruptEnableRegister; + delete joyPadState; +} + // Write to memory // TODO: Make emulation memory secure bool MemoryMap::writeMemory(Word address, Byte value) { if (address < 0x8000) { - printf("Writing to ROM is not allowed! Write attempted at %04X", address); + printf("Writing to ROM is not allowed! Write attempted at %04X\n", address); return false; } else if (address < 0xA000) @@ -174,13 +188,15 @@ bool MemoryMap::writeMemory(Word address, Byte value) { readInput(value); } - // Write to Audio Registers - else if (address >= 0xFF10 && address <= 0xFF3F) - { - audio->writeByte(address, value); - } else + { ioPorts[address - 0xFF00] = value; + // Checks for write in aduio registers and calls audioWriteHandler + if (address >= 0xFF10 && address <= 0xFF3F) + { + audioWriteHandler(address); + } + } } else if (address < 0xFFFF) { @@ -194,7 +210,7 @@ bool MemoryMap::writeMemory(Word address, Byte value) } else { - printf("Invalid address"); + printf("Invalid address\n"); return false; } @@ -206,6 +222,16 @@ void MemoryMap::debugWriteMemory(Word address, Byte value) romBank0[address] = value; } +bool MemoryMap::MemoryWriteBack(Word address, Byte value) +{ + if (address >= 0xFF10 && address <= 0xFF3F) + { + ioPorts[address - 0xFF00] = value; + return true; + } + return false; +} + Byte MemoryMap::readMemory(Word address) { if (address < 0x4000) @@ -251,14 +277,8 @@ Byte MemoryMap::readMemory(Word address) } else if (address < 0xFF80) { - // Read from Audio Registers - if (address >= 0xFF10 && address <= 0xFF3F) - { - return audio->readByte(address); - } // Read from I/O Ports - else - return ioPorts[address - 0xFF00]; + return ioPorts[address - 0xFF00]; } else if (address < 0xFFFF) { @@ -331,4 +351,4 @@ void MemoryMap::unloadBootRom() { fseek(romFile, 0x00, SEEK_SET); fread(romBank0, 1, 256, romFile); -} \ No newline at end of file +} diff --git a/src/mmap.h b/src/mmap.h index 7756c80..cfcb77b 100644 --- a/src/mmap.h +++ b/src/mmap.h @@ -1,7 +1,7 @@ #pragma once #include "types.h" #include -#include "audio.h" +#include // The Memory Map for GBE // Pulled from https://gbdev.io/pandocs/Memory_Map.html @@ -138,12 +138,11 @@ class MemoryMap // Stays in the I/O Ports at 0xFF4B Byte* reg_WX; + // Audio Write Listener + // updates and writes back after audio write + std::function audioWriteHandler; + public: - // Audio Unit - // I know this is not the best way to do it - // But I am not sure how to do it better - APU* audio; - Byte* joyPadState; // Constructor MemoryMap(); @@ -187,6 +186,9 @@ class MemoryMap bool writeMemory(Word address, Byte value); void debugWriteMemory(Word address, Byte value); + // Write Back + bool MemoryWriteBack(Word address, Byte value); + // Reads a byte from the memory address Byte readMemory(Word address); @@ -270,4 +272,7 @@ class MemoryMap // sets the ROM file void setRomFile(FILE* file) { romFile = file; } + + // sets audiowritehandler function + void setAudioWriteHandler(const std::function& function) { audioWriteHandler = function; } }; \ No newline at end of file diff --git a/src/types.h b/src/types.h index edd42d2..ee950ee 100644 --- a/src/types.h +++ b/src/types.h @@ -7,4 +7,4 @@ typedef unsigned char Byte; typedef char SByte; typedef unsigned short Word; typedef signed short SWord; -typedef unsigned int color; \ No newline at end of file +typedef unsigned int color;