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/.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..f19e4e9 --- /dev/null +++ b/src/audio.cpp @@ -0,0 +1,753 @@ +#include "audio.h" +#include "types.h" + +APU::APU() +{ + SDL_zero(wanted); + SDL_zero(obtained); + audioDeviceID = 0; + + enabled = false; + frameSequencer = 0; + sampleCounter = 0; + frameSequencerCounter = 0; + soundPann = 0; + enableVINLeft = false; + enableVINRight = false; + volumeLeft = 0; + volumeRight = 0; + + mMap = nullptr; + + channel1 = new PulseChannel(CH1); + channel2 = new PulseChannel(CH2); + channel3 = new WaveChannel(); + 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; +} + +// 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"); +} + +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) +{ + 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); + } + + Byte val = 0; + switch (address) + { + case 0xFF24: + return (enableVINLeft ? 0x80 : 0) | (volumeLeft << 4) | (enableVINRight ? 0x08 : 0) | volumeRight; + + case 0xFF25: + 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; + printf("APU Read 0xFF26: %X\n", val); + return val; + + default: + break; + } + + return 0xFF; +} + +void APU::stepAPU(int cycles) +{ + 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() +{ + 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; + frameSequencer = 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 = 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; + 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); + set_NRx4(value); + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } + 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 && dacEnabled; +} + +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; + 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; // clock this + } + else + lengthTimer = maxLengthTimer; + } + } + else if (enable) + { + if (frameSequencer & 1) + { + if (lengthTimer > 0) + lengthTimer--; // clock this + else if (trigger_bit && lengthTimer == 0) + lengthTimer = maxLengthTimer - 1; // clock this + } + } + else + { + if (trigger_bit && lengthTimer == 0) + { + lengthTimer = maxLengthTimer; + } + } + + soundLengthEnable = enable; +} + +void PulseChannel::setFrameSequencer(int frameSequencer) +{ + this->frameSequencer = frameSequencer; +} + +void PulseChannel::trigger() +{ + enabled = dacEnabled; +} + +// WaveChannel + +WaveChannel::WaveChannel() +{ + dacEnabled = 0; + enabled = 0; + lengthTimer = 0; + maxLengthTimer = 256; + outputLevel = 0; + frequency = 0; + soundLengthEnable = 0; + frameSequencer = 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 = maxLengthTimer - 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); + set_NRx4(value); + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } + 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 && dacEnabled; +} + +void WaveChannel::powerOff() +{ + enabled = 0; + dacEnabled = 0; + lengthTimer = 0; + outputLevel = 0; + frequency = 0; + 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; + } + else + lengthTimer = maxLengthTimer; + } + } + else if (enable) + { + if (frameSequencer & 1) + { + if (lengthTimer > 0) + lengthTimer--; + else if (trigger_bit && lengthTimer == 0) + 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 = 64; + clockShift = 0; + LFSRWidthMode = 0; + clockDivider = 0; + LFSR = 0x7FFF; + soundLengthEnable = 0; + frameSequencer = 0; +} + +void NoiseChannel::writeByte(Word address, Byte value) +{ + switch (address) + { + case 0xFF20: + // NR41 + // Sound length + 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; + return; + case 0xFF22: + // NR43 + // Polynomial counter + clockShift = (value & 0xF0) >> 4; + LFSRWidthMode = (value & 0x08) >> 3; + clockDivider = value & 0x07; + return; + case 0xFF23: + // NR44 + // Counter/consecutive; initial + set_NRx4(value); + if (soundLengthEnable && lengthTimer == 0) + { + enabled = 0; + } + 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 && dacEnabled; +} + +void NoiseChannel::powerOff() +{ + enabled = 0; + lengthTimer = 0; + envelopeInitialVolume = 0; + envelopeIncrease = 0; + envelopePeriod = 0; + clockShift = 0; + LFSRWidthMode = 0; + clockDivider = 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; + } + else + lengthTimer = maxLengthTimer; + } + } + else if (enable) + { + if (frameSequencer & 1) + { + if (lengthTimer > 0) + lengthTimer--; + else if (trigger_bit && lengthTimer == 0) + 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; +} diff --git a/src/audio.h b/src/audio.h new file mode 100644 index 0000000..dd956e2 --- /dev/null +++ b/src/audio.h @@ -0,0 +1,177 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include "mmap.h" + +enum Channel +{ + CH1 = 0, + CH2 = 1, + CH3 = 2, + CH4 = 3 +}; + +class PulseChannel +{ +private: + Channel channel; + bool enabled; + bool dacEnabled; + int frameSequencer; + + Byte sweepPeriod; + bool sweepNegate; + Byte sweepShift; + + // NRx1 + Byte waveDuty; + int lengthTimer; + int maxLengthTimer = 64; + + 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(); + 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; + + 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(); + void set_NRx4(Byte value); + void run(); + void setFrameSequencer(int frameSequencer); +}; + +class NoiseChannel +{ +private: + bool enabled; + bool dacEnabled; + + int lengthTimer; + int maxLengthTimer = 64; + int frameSequencer; + + 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(); + void set_NRx4(Byte value); + void run(); + void setFrameSequencer(int frameSequencer); +}; + +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; + + bool enabled; + + // 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 }; + + Byte soundPann; + + bool enableVINLeft; + bool enableVINRight; + Byte volumeLeft; + Byte volumeRight; + + // Audio Channels + PulseChannel* channel1; + PulseChannel* channel2; + WaveChannel* channel3; + NoiseChannel* channel4; + + // Pointer to MemoryMap + MemoryMap* mMap; + +public: + APU(); + void setMemoryMap(MemoryMap* mMap); + void test(); + 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 cc7c7e9..0af8569 100644 --- a/src/gameBoy.cpp +++ b/src/gameBoy.cpp @@ -15,23 +15,33 @@ GBE::GBE() // Initialize the Graphics gbe_graphics = new PPU(); + // audio = new Audio(); + gbe_audio = new APU(); + // Unify the CPU and MemoryMap gbe_cpu->setMemory(gbe_mMap); // 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 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 +125,7 @@ void GBE::update() // update the DIV and TIMA timers gbe_cpu->updateTimers(s_Cycles); gbe_graphics->executePPU(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 25a4315..5b085e0 100644 --- a/src/mmap.cpp +++ b/src/mmap.cpp @@ -107,6 +107,21 @@ MemoryMap::MemoryMap() 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) @@ -172,8 +187,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) + { + audioWriteHandler(address, value); + } else ioPorts[address - 0xFF00] = value; } @@ -246,8 +264,14 @@ Byte MemoryMap::readMemory(Word address) } else if (address < 0xFF80) { + // Read from Audio Registers + if (address >= 0xFF10 && address <= 0xFF3F) + { + return audioReadHandler(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..ddcc4b3 100644 --- a/src/mmap.h +++ b/src/mmap.h @@ -1,6 +1,7 @@ #pragma once #include "types.h" #include +#include // The Memory Map for GBE // Pulled from https://gbdev.io/pandocs/Memory_Map.html @@ -137,9 +138,13 @@ 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: Byte* joyPadState; - // Constructor MemoryMap(); @@ -265,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