From f0c5df0e3e267fbca3ab9e773d0891e8ef49e13a Mon Sep 17 00:00:00 2001 From: demigod Date: Thu, 4 Apr 2024 07:52:09 +0530 Subject: [PATCH 1/4] Restructure APU It now passes Test ROM 1 --- .vscode/launch.json | 7 + .vscode/settings.json | 59 ++++++ .vscode/tasks.json | 28 +++ CMakeLists.txt | 3 + run.sh | 41 ++++ src/CMakeLists.txt | 2 + src/audio.cpp | 466 ++++++++++++++++++++++++++++++++++++++++++ src/audio.h | 155 ++++++++++++++ src/gameBoy.cpp | 10 +- src/mmap.cpp | 17 +- src/mmap.h | 7 +- 11 files changed, 790 insertions(+), 5 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100755 run.sh create mode 100644 src/audio.cpp create mode 100644 src/audio.h diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5c7247b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..31053f6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "files.associations": { + "chrono": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstddef": "cpp", + "cstdlib": "cpp", + "cwchar": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "functional": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "string_view": "cpp", + "type_traits": "cpp", + "algorithm": "cpp", + "numeric": "cpp", + "random": "cpp", + "iosfwd": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "typeinfo": "cpp", + "utility": "cpp", + "cctype": "cpp", + "cerrno": "cpp", + "cfloat": "cpp", + "climits": "cpp", + "clocale": "cpp", + "cstdarg": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwctype": "cpp", + "iterator": "cpp", + "initializer_list": "cpp", + "ios": "cpp", + "queue": "cpp", + "semaphore": "cpp", + "cinttypes": "cpp" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..08d9005 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f0ecf96..3ee4d19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "ON") +find_package(SDL2 REQUIRED) + if (DEBUG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG") endif() add_executable(${PROJECT_NAME} src/main.cpp) +target_link_libraries(${PROJECT_NAME} SDL2::SDL2 SDL2::SDL2main) add_subdirectory(src) diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..f6fd32c --- /dev/null +++ b/run.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +current_directory=$(pwd) +last_keyword=$(basename "$current_directory") + +if [[ $last_keyword == "build" ]]; then + # Execute commands for the specified directory + echo "Executing commands for build" + echo "removing build directory" + cd .. + rm -r build + cd .. + + # Add your commands here +elif [[ $last_keyword == "gbemu" ]]; then + # Execute commands for another directory + echo "Executing commands for gbemu" + + if [[ -d "$current_directory/build" ]]; then + rm -r build + echo "removing build directory" + echo "making new build directory" + mkdir build + cd build + cmake .. + cmake --build . -j8 + ./gbemu + else + echo "making new build directory" + mkdir build + cd build + cmake .. + cmake --build . -j8 + ./gbemu + fi + + # Add your commands here +else + # Default case if no match is found + echo "No matching directory found." +fi diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2177555..4a39bb9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES gameBoy.cpp mmap.cpp graphics.cpp + audio.cpp # ------- # Header Files cpu.h @@ -12,6 +13,7 @@ set(SOURCES mmap.h types.h graphics.h + audio.h ) target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/src/audio.cpp b/src/audio.cpp new file mode 100644 index 0000000..61cceb3 --- /dev/null +++ b/src/audio.cpp @@ -0,0 +1,466 @@ +#include "audio.h" +#include "types.h" + +APU::APU() +{ + printf("init APU\n"); + + enabled = false; + frameSequencer = 0; + soundPann = 0; + enableVINLeft = false; + enableVINRight = false; + volumeLeft = 0; + volumeRight = 0; + + channel1 = new PulseChannel(CH1); + channel2 = new PulseChannel(CH2); + channel3 = new WaveChannel(); + channel4 = new NoiseChannel(); +} + +void APU::test() +{ + printf("APU test\n"); +} + +void APU::writeByte(Word address, Byte value) +{ + printf("APU Address: %X, Value: %X\n", address, value); + if (address == 0xFF26) + { + bool enable = (value & 0x80) >> 7; + + if (enabled && !enable) + { + clearRegisters(); + } + else if (!enabled && enable) + { + frameSequencer = 0; + } + + enabled = enable; + return; + } + else if (address >= 0xFF30 && address <= 0xFF3F) + { + // Wave Pattern RAM + channel3->writeByte(address, value); + return; + } + + else if (!enabled) + { + return; + } + + if (address >= 0xFF10 && address <= 0xFF14) + { + channel1->writeByte(address, value); + return; + } + else if (address >= 0xFF16 && address <= 0xFF19) + { + channel2->writeByte(address, value); + return; + } + else if (address >= 0xFF1A && address <= 0xFF1E) + { + channel3->writeByte(address, value); + return; + } + else if (address >= 0xFF20 && address <= 0xFF23) + { + channel4->writeByte(address, value); + return; + } + + switch (address) + { + case 0xFF24: + enableVINLeft = (value & 0x80) >> 7; + enableVINRight = (value & 0x08) >> 3; + volumeLeft = (value & 0x70) >> 4; + volumeRight = (value & 0x07); + return; + case 0xFF25: + soundPann = value; + return; + default: + return; + } +} + +Byte APU::readByte(Word address) +{ + printf("APU Address: %X\n", address); + if (address >= 0xFF10 && address <= 0xFF14) + { + return channel1->readByte(address); + } + else if (address >= 0xFF16 && address <= 0xFF19) + { + return channel2->readByte(address); + } + else if (address >= 0xFF1A && address <= 0xFF1E) + { + return channel3->readByte(address); + } + else if (address >= 0xFF20 && address <= 0xFF23) + { + return channel4->readByte(address); + } + else if (address >= 0xFF30 && address <= 0xFF3F) + { + // Wave Pattern RAM + return channel3->readByte(address); + } + switch (address) + { + case 0xFF24: + return (enableVINLeft ? 0x80 : 0) | (volumeLeft << 4) | (enableVINRight ? 0x08 : 0) | volumeRight; + + case 0xFF25: + return soundPann; + + case 0xFF26: + return (enabled ? 0x80 : 0) | (channel1->isEnabled() ? 0x01 : 0) | (channel2->isEnabled() ? 0x02 : 0) | (channel3->isEnabled() ? 0x04 : 0) | (channel4->isEnabled() ? 0x08 : 0) | 0x70; + + default: + break; + } + + return 0xFF; +} + +void APU::stepAPU(int cycles) +{ + printf("APU step\n"); +} + +void APU::clearRegisters() +{ + printf("APU clear registers\n"); + enableVINLeft = 0; + enableVINRight = 0; + volumeLeft = 0; + volumeRight = 0; + enabled = 0; + soundPann = 0; + channel1->powerOff(); + channel2->powerOff(); + channel3->powerOff(); + channel4->powerOff(); +} + +// PulseChannel + +PulseChannel::PulseChannel(Channel channel) +{ + this->channel = channel; + enabled = 0; + sweepPeriod = 0; + sweepNegate = 0; + sweepShift = 0; + waveDuty = 0; + lengthTimer = 0; + envelopeInitialVolume = 0; + envelopeIncrease = 0; + envelopePeriod = 0; + frequency = 0; + soundLengthEnable = 0; +} + +void PulseChannel::writeByte(Word address, Byte value) +{ + switch (address) + { + case 0xFF10: + // NR10 + // Sweep + if (channel == CH1) + { + sweepPeriod = (value & 0x70) >> 4; + sweepNegate = (value & 0x08) >> 3; + sweepShift = value & 0x07; + } + return; + case 0xFF11: + case 0xFF16: + // NR11 + // Sound length/Wave pattern duty + waveDuty = (value & 0xC0) >> 6; + lengthTimer = value & 0x3F; + return; + case 0xFF12: + case 0xFF17: + // NR12 + // Volume Envelope + envelopeInitialVolume = (value & 0xF0) >> 4; + envelopeIncrease = (value & 0x08) >> 3; + envelopePeriod = value & 0x07; + return; + case 0xFF13: + case 0xFF18: + // NR13 + // Frequency lo + frequency = (frequency & 0x0700) | value; + return; + case 0xFF14: + case 0xFF19: + // NR14 + // Frequency hi + frequency = (frequency & 0x00FF) | ((value & 0x07) << 8); + soundLengthEnable = (value & 0x40) >> 6; + if (value & 0x80) + { + // trigger(); + } + return; + default: + return; + } +} + +Byte PulseChannel::readByte(Word address) +{ + switch (address) + { + case 0xFF10: + // NR10 + return (sweepPeriod << 4) | (sweepNegate ? 0x08 : 0) | sweepShift | 0x80; + case 0xFF11: + case 0xFF16: + // NR11 NR21 + return (waveDuty << 6) | 0x3F; + case 0xFF12: + case 0xFF17: + // NR12 NR22 + return (envelopeInitialVolume << 4) | (envelopeIncrease ? 0x08 : 0) | envelopePeriod; + case 0xFF13: + case 0xFF18: + // NR13 NR23 + return 0xFF; + case 0xFF14: + case 0xFF19: + // NR14 NR24 + return (soundLengthEnable ? 0x40 : 0) | 0xBF; + default: + return 0xFF; + } +} + +bool PulseChannel::isEnabled() +{ + return enabled; +} + +void PulseChannel::powerOff() +{ + enabled = 0; + sweepPeriod = 0; + sweepNegate = 0; + sweepShift = 0; + waveDuty = 0; + lengthTimer = 0; + envelopeInitialVolume = 0; + envelopeIncrease = 0; + envelopePeriod = 0; + frequency = 0; + soundLengthEnable = 0; +} + +// WaveChannel + +WaveChannel::WaveChannel() +{ + dacEnabled = 0; + enabled = 0; + lengthTimer = 0; + maxLengthTimer = 0; + outputLevel = 0; + frequency = 0; + soundLengthEnable = 0; +} + +void WaveChannel::writeByte(Word address, Byte value) +{ + if (address >= 0xFF30 && address <= 0xFF3F) + { + // Wave Pattern RAM + waveRAM[address - 0xFF30] = value; + return; + } + switch (address) + { + case 0xFF1A: + // NR30 + // Sound on/off + dacEnabled = (value & 0x80) >> 7; + enabled = dacEnabled; + return; + case 0xFF1B: + // NR31 + // Sound length + lengthTimer = value; + return; + case 0xFF1C: + // NR32 + // Select output level + outputLevel = (value & 0x60) >> 5; + return; + case 0xFF1D: + // NR33 + // Frequency lo + frequency = (frequency & 0x0700) | value; + return; + case 0xFF1E: + // NR34 + // Frequency hi + frequency = (frequency & 0x00FF) | ((value & 0x07) << 8); + soundLengthEnable = (value & 0x40) >> 6; + if (value & 0x80) + { + // trigger(); + } + return; + default: + return; + } +} + +Byte WaveChannel::readByte(Word address) +{ + if (address >= 0xFF30 && address <= 0xFF3F) + { + // Wave Pattern RAM + return waveRAM[address - 0xFF30]; + } + switch (address) + { + case 0xFF1A: + // NR30 + return (dacEnabled ? 0x80 : 0) | 0x7F; + case 0xFF1B: + // NR31 + return 0xFF; + case 0xFF1C: + // NR32 + return (outputLevel << 5) | 0x9F; + case 0xFF1D: + // NR33 + return 0xFF; + case 0xFF1E: + // NR34 + return (soundLengthEnable ? 0x40 : 0) | 0xBF; + default: + return 0xFF; + } +} + +bool WaveChannel::isEnabled() +{ + return enabled; +} + +void WaveChannel::powerOff() +{ + enabled = 0; + dacEnabled = 0; + lengthTimer = 0; + outputLevel = 0; + frequency = 0; + soundLengthEnable = 0; +} + +// Noise Channel + +NoiseChannel::NoiseChannel() +{ + enabled = 0; + lengthTimer = 0; + maxLengthTimer = 0; + clockShift = 0; + LFSRWidthMode = 0; + clockDivider = 0; + LFSR = 0; + soundLengthEnable = 0; +} + +void NoiseChannel::writeByte(Word address, Byte value) +{ + switch (address) + { + case 0xFF20: + // NR41 + // Sound length + lengthTimer = value & 0x3F; + return; + case 0xFF21: + // NR42 + // Volume Envelope + envelopeInitialVolume = (value & 0xF0) >> 4; + envelopeIncrease = (value & 0x08) >> 3; + envelopePeriod = value & 0x07; + return; + case 0xFF22: + // NR43 + // Polynomial counter + clockShift = (value & 0xF0) >> 4; + LFSRWidthMode = (value & 0x08) >> 3; + clockDivider = value & 0x07; + return; + case 0xFF23: + // NR44 + // Counter/consecutive; initial + soundLengthEnable = (value & 0x40) >> 6; + if (value & 0x80) + { + // trigger(); + } + return; + default: + return; + } +} + +Byte NoiseChannel::readByte(Word address) +{ + switch (address) + { + case 0xFF20: + // NR41 + return 0xFF; + case 0xFF21: + // NR42 + return (envelopeInitialVolume << 4) | (envelopeIncrease ? 0x08 : 0) | envelopePeriod; + case 0xFF22: + // NR43 + return (clockShift << 4) | (LFSRWidthMode ? 0x08 : 0) | clockDivider; + case 0xFF23: + // NR44 + return (soundLengthEnable ? 0x40 : 0) | 0xBF; + default: + return 0xFF; + } +} + +bool NoiseChannel::isEnabled() +{ + return enabled; +} + +void NoiseChannel::powerOff() +{ + enabled = 0; + lengthTimer = 0; + envelopeInitialVolume = 0; + envelopeIncrease = 0; + envelopePeriod = 0; + clockShift = 0; + LFSRWidthMode = 0; + clockDivider = 0; + LFSR = 0; + soundLengthEnable = 0; +} \ No newline at end of file diff --git a/src/audio.h b/src/audio.h new file mode 100644 index 0000000..e97ce80 --- /dev/null +++ b/src/audio.h @@ -0,0 +1,155 @@ +#pragma once +#include "types.h" +#include +#include + +enum Channel +{ + CH1 = 0, + CH2 = 1, + CH3 = 2, + CH4 = 3 +}; + +class PulseChannel +{ +private: + Channel channel; + bool enabled; + bool dacEnabled; + + Byte sweepPeriod; + bool sweepNegate; + Byte sweepShift; + + // NRx1 + Byte waveDuty; + int lengthTimer; + + Byte envelopeInitialVolume; + bool envelopeIncrease; + Byte envelopePeriod; + + int frequency; + + bool soundLengthEnable; + +public: + PulseChannel(Channel channel); + void test(); + void writeByte(Word address, Byte value); + Byte readByte(Word address); + bool isEnabled(); + void powerOff(); +}; + +class WaveChannel +{ +private: + + Byte waveRAM[16]; + bool dacEnabled; + bool enabled; + + int lengthTimer; + int maxLengthTimer; + + Byte outputLevel; + + int frequency; + + bool soundLengthEnable; + +public: + WaveChannel(); + void test(); + void writeByte(Word address, Byte value); + Byte readByte(Word address); + void trigger(); + bool isEnabled(); + void powerOff(); +}; + +class NoiseChannel +{ +private: + bool enabled; + bool dacEnabled; + int lengthTimer; + int maxLengthTimer; + + Byte envelopeInitialVolume; + bool envelopeIncrease; + Byte envelopePeriod; + + // NRx3 + Byte clockShift; + bool LFSRWidthMode; + Byte clockDivider; + Word LFSR; + + Byte dividerTable[8] = { 8, 16, 32, 48, 64, 80, 96, 112 }; + + // NRx4 + // bool trigger; + bool soundLengthEnable; + +public: + NoiseChannel(); + void test(); + void writeByte(Word address, Byte value); + Byte readByte(Word address); + void trigger(); + bool isEnabled(); + void powerOff(); +}; + +class APU +{ +private: + // SDL Audio + // https://documentation.help/SDL/guideaudioexamples.html + SDL_AudioSpec wanted, obtained; + SDL_AudioDeviceID audioDeviceID; + + static Uint8* audio_chunk; + static Uint32 audio_len; + static Uint8* audio_pos; + + // Gets an audio sample every 95 clock cycles. + // clockSpeed/sampleRate ~ 95 + int sampleCounter; + + // This updates the frame sequencer at 512Hz. + // Must be reset after every 8192 clock cycles. + int frameSequencerCounter; + int frameSequencer; + + // Buffer + unsigned int bufferSize = 4096; + unsigned int bufferIndex = 0; + float buffer[4096] = { 0 }; + + bool enabled; + + Byte soundPann; + + bool enableVINLeft; + bool enableVINRight; + Byte volumeLeft; + Byte volumeRight; + + // Audio Channels + PulseChannel* channel1; + PulseChannel* channel2; + WaveChannel* channel3; + NoiseChannel* channel4; + +public: + APU(); + void test(); + void writeByte(Word address, Byte value); + Byte readByte(Word address); + void stepAPU(int cycles); + void clearRegisters(); +}; \ No newline at end of file diff --git a/src/gameBoy.cpp b/src/gameBoy.cpp index cc7c7e9..7ac19a2 100644 --- a/src/gameBoy.cpp +++ b/src/gameBoy.cpp @@ -15,6 +15,9 @@ GBE::GBE() // Initialize the Graphics gbe_graphics = new PPU(); + // audio = new Audio(); + // APU = new APU(); + // Unify the CPU and MemoryMap gbe_cpu->setMemory(gbe_mMap); @@ -30,8 +33,12 @@ GBE::GBE() if ((bootROM = fopen("../src/dmg_boot.gb", "rb")) == NULL) printf("boot rom file not opened"); + // // 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/halt_bug.gb", "rb")) == NULL) + if ((gameROM = fopen("../tests/dmg_sound/rom_singles/02-len ctr.gb", "rb")) == NULL) printf("game rom file not opened"); // Set the Boot ROM @@ -115,6 +122,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); s_Cycles = 0; s_Cycles += gbe_cpu->performInterrupt(); gbe_graphics->pollEvents(); diff --git a/src/mmap.cpp b/src/mmap.cpp index 25a4315..6d71288 100644 --- a/src/mmap.cpp +++ b/src/mmap.cpp @@ -104,6 +104,8 @@ MemoryMap::MemoryMap() bootRomFile = nullptr; romFile = nullptr; + audio = new APU(); + mbcMode = 0x0; } @@ -172,8 +174,11 @@ bool MemoryMap::writeMemory(Word address, Byte value) { readInput(value); } - //if (value != 0xFF) - //printf("0x%02x\n", ioPorts[0]);} + // Write to Audio Registers + else if (address >= 0xFF10 && address <= 0xFF3F) + { + audio->writeByte(address, value); + } else ioPorts[address - 0xFF00] = value; } @@ -246,8 +251,14 @@ 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 - return ioPorts[address - 0xFF00]; + else + return ioPorts[address - 0xFF00]; } else if (address < 0xFFFF) { diff --git a/src/mmap.h b/src/mmap.h index 09c29e2..7756c80 100644 --- a/src/mmap.h +++ b/src/mmap.h @@ -1,6 +1,7 @@ #pragma once #include "types.h" #include +#include "audio.h" // The Memory Map for GBE // Pulled from https://gbdev.io/pandocs/Memory_Map.html @@ -138,8 +139,12 @@ class MemoryMap Byte* reg_WX; 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(); From e45dea9e8a86e8426b9827f4a46f8f07ca87bdb2 Mon Sep 17 00:00:00 2001 From: demigod Date: Thu, 4 Apr 2024 17:01:18 +0530 Subject: [PATCH 2/4] Add Length Timer The passes test ROM "02-len ctr.gb" --- src/audio.cpp | 289 ++++++++++++++++++++++++++++++++++++++++++++---- src/audio.h | 24 +++- src/gameBoy.cpp | 2 +- 3 files changed, 290 insertions(+), 25 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index 61cceb3..8500c8e 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -3,10 +3,14 @@ APU::APU() { - printf("init APU\n"); + SDL_zero(wanted); + SDL_zero(obtained); + audioDeviceID = 0; enabled = false; frameSequencer = 0; + sampleCounter = 0; + frameSequencerCounter = 0; soundPann = 0; enableVINLeft = false; enableVINRight = false; @@ -19,6 +23,33 @@ APU::APU() channel4 = new NoiseChannel(); } +bool APU::init() +{ + // Initializing SDL Audio + wanted.freq = 44100; + wanted.format = AUDIO_F32SYS; + wanted.channels = 2; /* 1 = mono, 2 = stereo */ + wanted.samples = bufferSize; + wanted.callback = NULL; + wanted.userdata = NULL; + + audioDeviceID = SDL_OpenAudioDevice(NULL, 0, &wanted, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); + if (audioDeviceID == 0) + { + printf("SDL Audio not initialize! SDL_Error: %s\n", SDL_GetError()); + SDL_Quit(); + return false; + } + SDL_PauseAudioDevice(audioDeviceID, 0); + SDL_Delay(3); + + channel1->setFrameSequencer(frameSequencer); + channel2->setFrameSequencer(frameSequencer); + channel3->setFrameSequencer(frameSequencer); + channel4->setFrameSequencer(frameSequencer); + return true; +} + void APU::test() { printf("APU test\n"); @@ -26,7 +57,7 @@ void APU::test() void APU::writeByte(Word address, Byte value) { - printf("APU Address: %X, Value: %X\n", address, value); + // printf("APU Address: %X, Value: %X\n", address, value); if (address == 0xFF26) { bool enable = (value & 0x80) >> 7; @@ -94,7 +125,6 @@ void APU::writeByte(Word address, Byte value) Byte APU::readByte(Word address) { - printf("APU Address: %X\n", address); if (address >= 0xFF10 && address <= 0xFF14) { return channel1->readByte(address); @@ -116,6 +146,8 @@ Byte APU::readByte(Word address) // Wave Pattern RAM return channel3->readByte(address); } + + Byte val = 0; switch (address) { case 0xFF24: @@ -125,7 +157,8 @@ Byte APU::readByte(Word address) return soundPann; case 0xFF26: - return (enabled ? 0x80 : 0) | (channel1->isEnabled() ? 0x01 : 0) | (channel2->isEnabled() ? 0x02 : 0) | (channel3->isEnabled() ? 0x04 : 0) | (channel4->isEnabled() ? 0x08 : 0) | 0x70; + val = (enabled ? 0x80 : 0) | (channel1->isEnabled() ? 0x01 : 0) | (channel2->isEnabled() ? 0x02 : 0) | (channel3->isEnabled() ? 0x04 : 0) | (channel4->isEnabled() ? 0x08 : 0) | 0x70; + return val; default: break; @@ -136,7 +169,26 @@ Byte APU::readByte(Word address) void APU::stepAPU(int cycles) { - printf("APU step\n"); + sampleCounter += cycles; + frameSequencerCounter += cycles; + + if (frameSequencerCounter >= 8192) + { + // update envelope clocks and length timers + + channel1->run(); + channel2->run(); + channel3->run(); + channel4->run(); + + frameSequencerCounter -= 8192; + frameSequencer = (frameSequencer + 1) % 8; + + channel1->setFrameSequencer(frameSequencer); + channel2->setFrameSequencer(frameSequencer); + channel3->setFrameSequencer(frameSequencer); + channel4->setFrameSequencer(frameSequencer); + } } void APU::clearRegisters() @@ -147,7 +199,7 @@ void APU::clearRegisters() volumeLeft = 0; volumeRight = 0; enabled = 0; - soundPann = 0; + soundPann = 0; channel1->powerOff(); channel2->powerOff(); channel3->powerOff(); @@ -170,6 +222,7 @@ PulseChannel::PulseChannel(Channel channel) envelopePeriod = 0; frequency = 0; soundLengthEnable = 0; + frameSequencer = 0; } void PulseChannel::writeByte(Word address, Byte value) @@ -191,12 +244,15 @@ void PulseChannel::writeByte(Word address, Byte value) // NR11 // Sound length/Wave pattern duty waveDuty = (value & 0xC0) >> 6; - lengthTimer = value & 0x3F; + lengthTimer = maxLengthTimer - (value & 0x3F); return; case 0xFF12: case 0xFF17: // NR12 // Volume Envelope + dacEnabled = (value & 0xF8) != 0; + enabled &= dacEnabled; + envelopeInitialVolume = (value & 0xF0) >> 4; envelopeIncrease = (value & 0x08) >> 3; envelopePeriod = value & 0x07; @@ -212,10 +268,10 @@ void PulseChannel::writeByte(Word address, Byte value) // NR14 // Frequency hi frequency = (frequency & 0x00FF) | ((value & 0x07) << 8); - soundLengthEnable = (value & 0x40) >> 6; + set_NRx4(value); if (value & 0x80) { - // trigger(); + trigger(); } return; default: @@ -269,6 +325,70 @@ void PulseChannel::powerOff() envelopePeriod = 0; frequency = 0; soundLengthEnable = 0; + frameSequencer = 0; +} + +void PulseChannel::run() +{ + // length timer + if (frameSequencer % 2 == 0) + { + if (soundLengthEnable && lengthTimer) + { + lengthTimer--; + } + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } + } +} + +void PulseChannel::set_NRx4(Byte value) +{ + bool enable = (value & 0x40) >> 6; + bool trigger_bit = (value & 0x80) >> 7; + + if (soundLengthEnable) + { + if (trigger_bit && lengthTimer == 0) + { + if (enable && frameSequencer & 1) + { + lengthTimer = maxLengthTimer - 1; + } + lengthTimer = maxLengthTimer; + } + } + else if (enable) + { + if (frameSequencer & 1) + { + if (lengthTimer > 0) + lengthTimer--; + else + lengthTimer = maxLengthTimer - 1; + } + } + else + { + if (trigger_bit && lengthTimer == 0) + { + lengthTimer = maxLengthTimer; + } + } + + soundLengthEnable = enable; +} + +void PulseChannel::setFrameSequencer(int frameSequencer) +{ + this->frameSequencer = frameSequencer; +} + +void PulseChannel::trigger() +{ + enabled = dacEnabled; } // WaveChannel @@ -278,10 +398,11 @@ WaveChannel::WaveChannel() dacEnabled = 0; enabled = 0; lengthTimer = 0; - maxLengthTimer = 0; + maxLengthTimer = 256; outputLevel = 0; frequency = 0; soundLengthEnable = 0; + frameSequencer = 0; } void WaveChannel::writeByte(Word address, Byte value) @@ -298,12 +419,12 @@ void WaveChannel::writeByte(Word address, Byte value) // NR30 // Sound on/off dacEnabled = (value & 0x80) >> 7; - enabled = dacEnabled; + enabled &= dacEnabled; return; case 0xFF1B: // NR31 // Sound length - lengthTimer = value; + lengthTimer = maxLengthTimer - value; return; case 0xFF1C: // NR32 @@ -319,10 +440,10 @@ void WaveChannel::writeByte(Word address, Byte value) // NR34 // Frequency hi frequency = (frequency & 0x00FF) | ((value & 0x07) << 8); - soundLengthEnable = (value & 0x40) >> 6; + set_NRx4(value); if (value & 0x80) { - // trigger(); + trigger(); } return; default: @@ -374,18 +495,80 @@ void WaveChannel::powerOff() soundLengthEnable = 0; } +void WaveChannel::set_NRx4(Byte value) +{ + bool enable = (value & 0x40) >> 6; + bool trigger_bit = (value & 0x80) >> 7; + + if (soundLengthEnable) + { + if (trigger_bit && lengthTimer == 0) + { + if (enable && frameSequencer & 1) + { + lengthTimer = maxLengthTimer - 1; + } + lengthTimer = maxLengthTimer; + } + } + else if (enable) + { + if (frameSequencer & 1) + { + if (lengthTimer > 0) + lengthTimer--; + else + lengthTimer = maxLengthTimer - 1; + } + } + else + { + if (trigger_bit && lengthTimer == 0) + { + lengthTimer = maxLengthTimer; + } + } + + soundLengthEnable = enable; +} + +void WaveChannel::run() +{ + if (frameSequencer % 2 == 0) + { + if (soundLengthEnable && lengthTimer) + { + lengthTimer--; + } + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } + } +} + +void WaveChannel::setFrameSequencer(int frameSequencer) +{ + this->frameSequencer = frameSequencer; +} + +void WaveChannel::trigger() +{ + enabled = dacEnabled; +} // Noise Channel NoiseChannel::NoiseChannel() { enabled = 0; lengthTimer = 0; - maxLengthTimer = 0; + maxLengthTimer = 64; clockShift = 0; LFSRWidthMode = 0; clockDivider = 0; - LFSR = 0; + LFSR = 0x7FFF; soundLengthEnable = 0; + frameSequencer = 0; } void NoiseChannel::writeByte(Word address, Byte value) @@ -395,11 +578,14 @@ void NoiseChannel::writeByte(Word address, Byte value) case 0xFF20: // NR41 // Sound length - lengthTimer = value & 0x3F; + lengthTimer = maxLengthTimer - (value & 0x3F); return; case 0xFF21: // NR42 // Volume Envelope + dacEnabled = (value & 0xF8) != 0; + enabled &= dacEnabled; + envelopeInitialVolume = (value & 0xF0) >> 4; envelopeIncrease = (value & 0x08) >> 3; envelopePeriod = value & 0x07; @@ -414,10 +600,10 @@ void NoiseChannel::writeByte(Word address, Byte value) case 0xFF23: // NR44 // Counter/consecutive; initial - soundLengthEnable = (value & 0x40) >> 6; + set_NRx4(value); if (value & 0x80) { - // trigger(); + trigger(); } return; default: @@ -461,6 +647,69 @@ void NoiseChannel::powerOff() clockShift = 0; LFSRWidthMode = 0; clockDivider = 0; - LFSR = 0; + LFSR = 0x7FFF; soundLengthEnable = 0; +} + +void NoiseChannel::set_NRx4(Byte value) +{ + bool enable = (value & 0x40) >> 6; + bool trigger_bit = (value & 0x80) >> 7; + + if (soundLengthEnable) + { + if (trigger_bit && lengthTimer == 0) + { + if (enable && frameSequencer & 1) + { + lengthTimer = maxLengthTimer - 1; + } + lengthTimer = maxLengthTimer; + } + } + else if (enable) + { + if (frameSequencer & 1) + { + if (lengthTimer > 0) + lengthTimer--; + else + lengthTimer = maxLengthTimer - 1; + } + } + else + { + if (trigger_bit && lengthTimer == 0) + { + lengthTimer = maxLengthTimer; + } + } + + soundLengthEnable = enable; +} + +void NoiseChannel::run() +{ + if (frameSequencer % 2 == 0) + { + if (soundLengthEnable && lengthTimer) + { + lengthTimer--; + } + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } + } +} + +void NoiseChannel::setFrameSequencer(int frameSequencer) +{ + this->frameSequencer = frameSequencer; +} + +void NoiseChannel::trigger() +{ + LFSR = 0x7FFF; + enabled = dacEnabled; } \ No newline at end of file diff --git a/src/audio.h b/src/audio.h index e97ce80..1255f41 100644 --- a/src/audio.h +++ b/src/audio.h @@ -17,6 +17,7 @@ class PulseChannel Channel channel; bool enabled; bool dacEnabled; + int frameSequencer; Byte sweepPeriod; bool sweepNegate; @@ -25,6 +26,7 @@ class PulseChannel // NRx1 Byte waveDuty; int lengthTimer; + int maxLengthTimer = 64; Byte envelopeInitialVolume; bool envelopeIncrease; @@ -41,6 +43,10 @@ class PulseChannel Byte readByte(Word address); bool isEnabled(); void powerOff(); + void run(); + void set_NRx4(Byte value); + void setFrameSequencer(int frameSequencer); + void trigger(); }; class WaveChannel @@ -52,7 +58,8 @@ class WaveChannel bool enabled; int lengthTimer; - int maxLengthTimer; + int maxLengthTimer = 256; + int frameSequencer; Byte outputLevel; @@ -68,6 +75,9 @@ class WaveChannel void trigger(); bool isEnabled(); void powerOff(); + void set_NRx4(Byte value); + void run(); + void setFrameSequencer(int frameSequencer); }; class NoiseChannel @@ -75,8 +85,10 @@ class NoiseChannel private: bool enabled; bool dacEnabled; + int lengthTimer; - int maxLengthTimer; + int maxLengthTimer = 64; + int frameSequencer; Byte envelopeInitialVolume; bool envelopeIncrease; @@ -102,6 +114,9 @@ class NoiseChannel void trigger(); bool isEnabled(); void powerOff(); + void set_NRx4(Byte value); + void run(); + void setFrameSequencer(int frameSequencer); }; class APU @@ -116,6 +131,8 @@ class APU static Uint32 audio_len; static Uint8* audio_pos; + bool enabled; + // Gets an audio sample every 95 clock cycles. // clockSpeed/sampleRate ~ 95 int sampleCounter; @@ -130,8 +147,6 @@ class APU unsigned int bufferIndex = 0; float buffer[4096] = { 0 }; - bool enabled; - Byte soundPann; bool enableVINLeft; @@ -148,6 +163,7 @@ class APU public: APU(); void test(); + bool init(); void writeByte(Word address, Byte value); Byte readByte(Word address); void stepAPU(int cycles); diff --git a/src/gameBoy.cpp b/src/gameBoy.cpp index 7ac19a2..359697c 100644 --- a/src/gameBoy.cpp +++ b/src/gameBoy.cpp @@ -122,7 +122,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_mMap->audio->stepAPU(s_Cycles); s_Cycles = 0; s_Cycles += gbe_cpu->performInterrupt(); gbe_graphics->pollEvents(); From 4e4bdd5cb0a3c173cd77bbf38e418a86bee29ceb Mon Sep 17 00:00:00 2001 From: demigod Date: Thu, 4 Apr 2024 20:33:38 +0530 Subject: [PATCH 3/4] chore: length control This passes test #11 of 03-trigger.gb The rest would pass once Frequency sweep is implemented. --- src/audio.cpp | 52 ++++++++++++++++++++++++++++++++----------------- src/gameBoy.cpp | 2 +- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index 8500c8e..9a95636 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -57,7 +57,7 @@ void APU::test() void APU::writeByte(Word address, Byte value) { - // printf("APU Address: %X, Value: %X\n", address, value); + printf("APU Address: %X, Value: %X\n", address, value); if (address == 0xFF26) { bool enable = (value & 0x80) >> 7; @@ -147,7 +147,7 @@ Byte APU::readByte(Word address) return channel3->readByte(address); } - Byte val = 0; + Byte val = 0; switch (address) { case 0xFF24: @@ -157,8 +157,9 @@ Byte APU::readByte(Word address) return soundPann; case 0xFF26: - val = (enabled ? 0x80 : 0) | (channel1->isEnabled() ? 0x01 : 0) | (channel2->isEnabled() ? 0x02 : 0) | (channel3->isEnabled() ? 0x04 : 0) | (channel4->isEnabled() ? 0x08 : 0) | 0x70; - return val; + 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: break; @@ -251,7 +252,7 @@ void PulseChannel::writeByte(Word address, Byte value) // NR12 // Volume Envelope dacEnabled = (value & 0xF8) != 0; - enabled &= dacEnabled; + enabled &= dacEnabled; envelopeInitialVolume = (value & 0xF0) >> 4; envelopeIncrease = (value & 0x08) >> 3; @@ -269,6 +270,10 @@ void PulseChannel::writeByte(Word address, Byte value) // Frequency hi frequency = (frequency & 0x00FF) | ((value & 0x07) << 8); set_NRx4(value); + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } if (value & 0x80) { trigger(); @@ -309,7 +314,7 @@ Byte PulseChannel::readByte(Word address) bool PulseChannel::isEnabled() { - return enabled; + return enabled && dacEnabled; } void PulseChannel::powerOff() @@ -355,9 +360,10 @@ void PulseChannel::set_NRx4(Byte value) { if (enable && frameSequencer & 1) { - lengthTimer = maxLengthTimer - 1; + lengthTimer = maxLengthTimer - 1; // clock this } - lengthTimer = maxLengthTimer; + else + lengthTimer = maxLengthTimer; } } else if (enable) @@ -365,9 +371,9 @@ void PulseChannel::set_NRx4(Byte value) if (frameSequencer & 1) { if (lengthTimer > 0) - lengthTimer--; - else - lengthTimer = maxLengthTimer - 1; + lengthTimer--; // clock this + else if (trigger_bit && lengthTimer == 0) + lengthTimer = maxLengthTimer - 1; // clock this } } else @@ -419,7 +425,7 @@ void WaveChannel::writeByte(Word address, Byte value) // NR30 // Sound on/off dacEnabled = (value & 0x80) >> 7; - enabled &= dacEnabled; + enabled &= dacEnabled; return; case 0xFF1B: // NR31 @@ -441,6 +447,10 @@ void WaveChannel::writeByte(Word address, Byte value) // Frequency hi frequency = (frequency & 0x00FF) | ((value & 0x07) << 8); set_NRx4(value); + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } if (value & 0x80) { trigger(); @@ -482,7 +492,7 @@ Byte WaveChannel::readByte(Word address) bool WaveChannel::isEnabled() { - return enabled; + return enabled && dacEnabled; } void WaveChannel::powerOff() @@ -508,7 +518,8 @@ void WaveChannel::set_NRx4(Byte value) { lengthTimer = maxLengthTimer - 1; } - lengthTimer = maxLengthTimer; + else + lengthTimer = maxLengthTimer; } } else if (enable) @@ -517,7 +528,7 @@ void WaveChannel::set_NRx4(Byte value) { if (lengthTimer > 0) lengthTimer--; - else + else if (trigger_bit && lengthTimer == 0) lengthTimer = maxLengthTimer - 1; } } @@ -601,6 +612,10 @@ void NoiseChannel::writeByte(Word address, Byte value) // NR44 // Counter/consecutive; initial set_NRx4(value); + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } if (value & 0x80) { trigger(); @@ -634,7 +649,7 @@ Byte NoiseChannel::readByte(Word address) bool NoiseChannel::isEnabled() { - return enabled; + return enabled && dacEnabled; } void NoiseChannel::powerOff() @@ -664,7 +679,8 @@ void NoiseChannel::set_NRx4(Byte value) { lengthTimer = maxLengthTimer - 1; } - lengthTimer = maxLengthTimer; + else + lengthTimer = maxLengthTimer; } } else if (enable) @@ -673,7 +689,7 @@ void NoiseChannel::set_NRx4(Byte value) { if (lengthTimer > 0) lengthTimer--; - else + else if (trigger_bit && lengthTimer == 0) lengthTimer = maxLengthTimer - 1; } } diff --git a/src/gameBoy.cpp b/src/gameBoy.cpp index 359697c..9f25b3e 100644 --- a/src/gameBoy.cpp +++ b/src/gameBoy.cpp @@ -38,7 +38,7 @@ GBE::GBE() // printf("game rom file not opened"); // Open the Game ROM - if ((gameROM = fopen("../tests/dmg_sound/rom_singles/02-len ctr.gb", "rb")) == NULL) + if ((gameROM = fopen("../tests/dmg_sound/rom_singles/03-trigger.gb", "rb")) == NULL) printf("game rom file not opened"); // Set the Boot ROM From 4e837a765c3bd94790290f772e6ac74534a3597f Mon Sep 17 00:00:00 2001 From: vibhatsu <152148244+v1bh475u@users.noreply.github.com> Date: Sun, 1 Sep 2024 17:22:25 +0530 Subject: [PATCH 4/4] Separate APU from Memory (#34) Add signal handlers for APU writes/reads --- .gitignore | 3 ++- src/audio.cpp | 24 +++++++++++++++++++- src/audio.h | 58 +++++++++++++++++++++++++++---------------------- src/gameBoy.cpp | 11 ++++++---- src/gameBoy.h | 4 ++++ src/mmap.cpp | 23 +++++++++++++++----- src/mmap.h | 18 ++++++++++----- 7 files changed, 98 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 478cd55..4d02245 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ build/ cmake-build-debug/ .idea/ src/.vscode/ -src/dmg_boot.gb \ No newline at end of file +src/dmg_boot.gb +tests/* \ No newline at end of file diff --git a/src/audio.cpp b/src/audio.cpp index 9a95636..f19e4e9 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -17,6 +17,8 @@ APU::APU() volumeLeft = 0; volumeRight = 0; + mMap = nullptr; + channel1 = new PulseChannel(CH1); channel2 = new PulseChannel(CH2); channel3 = new WaveChannel(); @@ -50,6 +52,26 @@ bool APU::init() return true; } +// Set MemoryMap pointer +void APU::setMemoryMap(MemoryMap* mMap) +{ + this->mMap = mMap; + // initialize Handlers + initializeReadWriteHandlers(); +} + +// Initializes the read-write handlers of MemoryMap +void APU::initializeReadWriteHandlers() +{ + if (!mMap) + { + throw std::runtime_error("MemoryMap not set in APU"); + return; + } + + mMap->setAudioReadHandler([this](Word address) { return this->readByte(address); }); + mMap->setAudioWriteHandler([this](Word address, Byte value) { this->writeByte(address, value); }); +} void APU::test() { printf("APU test\n"); @@ -728,4 +750,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..dd956e2 100644 --- a/src/audio.h +++ b/src/audio.h @@ -2,6 +2,8 @@ #include "types.h" #include #include +#include +#include "mmap.h" enum Channel { @@ -15,9 +17,9 @@ class PulseChannel { private: Channel channel; - bool enabled; - bool dacEnabled; - int frameSequencer; + bool enabled; + bool dacEnabled; + int frameSequencer; Byte sweepPeriod; bool sweepNegate; @@ -26,7 +28,7 @@ class PulseChannel // NRx1 Byte waveDuty; int lengthTimer; - int maxLengthTimer = 64; + int maxLengthTimer = 64; Byte envelopeInitialVolume; bool envelopeIncrease; @@ -41,25 +43,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 +74,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 +113,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 +161,17 @@ class APU WaveChannel* channel3; NoiseChannel* channel4; + // Pointer to MemoryMap + MemoryMap* mMap; + public: APU(); + void setMemoryMap(MemoryMap* mMap); void test(); - bool init(); + bool init(); void writeByte(Word address, Byte value); Byte readByte(Word address); void stepAPU(int cycles); void clearRegisters(); + void initializeReadWriteHandlers(); }; \ No newline at end of file diff --git a/src/gameBoy.cpp b/src/gameBoy.cpp index 9f25b3e..0af8569 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,9 +24,12 @@ 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); + // Unify the APU and MemoryMap + gbe_audio->setMemoryMap(gbe_mMap); + gbe_graphics->init(); // Open the Boot ROM @@ -38,7 +41,7 @@ GBE::GBE() // printf("game rom file not opened"); // Open the Game ROM - if ((gameROM = fopen("../tests/dmg_sound/rom_singles/03-trigger.gb", "rb")) == NULL) + if ((gameROM = fopen("../tests/dmg_sound/rom_singles/02-len ctr.gb", "rb")) == NULL) printf("game rom file not opened"); // Set the Boot ROM @@ -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..20ce470 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 @@ -34,6 +35,9 @@ class GBE // File pointer for game ROM FILE* gameROM; + // Pointer to Audio + APU* gbe_audio; + // Update function of the GBE // Will be called every frame // GB has 59.73 frames per second diff --git a/src/mmap.cpp b/src/mmap.cpp index 6d71288..5b085e0 100644 --- a/src/mmap.cpp +++ b/src/mmap.cpp @@ -104,11 +104,24 @@ MemoryMap::MemoryMap() bootRomFile = nullptr; romFile = nullptr; - audio = new APU(); - 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) @@ -176,8 +189,8 @@ bool MemoryMap::writeMemory(Word address, Byte value) } // Write to Audio Registers else if (address >= 0xFF10 && address <= 0xFF3F) - { - audio->writeByte(address, value); + { + audioWriteHandler(address, value); } else ioPorts[address - 0xFF00] = value; @@ -254,7 +267,7 @@ Byte MemoryMap::readMemory(Word address) // Read from Audio Registers if (address >= 0xFF10 && address <= 0xFF3F) { - return audio->readByte(address); + return audioReadHandler(address); } // Read from I/O Ports else diff --git a/src/mmap.h b/src/mmap.h index 7756c80..ddcc4b3 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,12 @@ class MemoryMap // Stays in the I/O Ports at 0xFF4B Byte* reg_WX; + // Write to audio are being handled by this without passing the APU pointer to MemoryMap + std::function audioWriteHandler; + // Read from audio handled by this + std::function audioReadHandler; + 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(); @@ -270,4 +270,10 @@ class MemoryMap // sets the ROM file void setRomFile(FILE* file) { romFile = file; } + + // set audioWriteHandler + void setAudioWriteHandler(const std::function& function) { audioWriteHandler = function; } + + // set audioReadHandler + void setAudioReadHandler(const std::function& function) { audioReadHandler = function; } }; \ No newline at end of file