From 3d793c452d0ab781ba5c9fbea6126b0fbd9ab8f6 Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 26 Aug 2023 01:59:57 -0400 Subject: [PATCH] Reorganize emu folder, update S-SMP system infra --- src/app/emu/apu.cc | 75 ------ src/app/emu/apu.h | 79 ------ src/app/emu/audio/apu.cc | 125 ++++++++++ src/app/emu/audio/apu.h | 192 +++++++++++++++ src/app/emu/audio/dsp.cc | 281 +++++++++++++++++++++ src/app/emu/audio/dsp.h | 315 ++++++++++++++++++++++++ src/app/emu/{ => audio}/spc700.cc | 4 +- src/app/emu/{ => audio}/spc700.h | 58 +---- src/app/emu/clock.h | 10 +- src/app/emu/cpu.cc | 12 +- src/app/emu/cpu.h | 5 +- src/app/emu/{dbg.h => debug/debugger.h} | 10 +- src/app/emu/snes.cc | 25 +- src/app/emu/snes.h | 12 +- src/app/emu/{ => video}/ppu.cc | 28 ++- src/app/emu/{ => video}/ppu.h | 10 +- test/CMakeLists.txt | 5 + test/cpu_test.cc | 20 +- test/spc700_test.cc | 26 ++ 19 files changed, 1054 insertions(+), 238 deletions(-) delete mode 100644 src/app/emu/apu.cc delete mode 100644 src/app/emu/apu.h create mode 100644 src/app/emu/audio/apu.cc create mode 100644 src/app/emu/audio/apu.h create mode 100644 src/app/emu/audio/dsp.cc create mode 100644 src/app/emu/audio/dsp.h rename src/app/emu/{ => audio}/spc700.cc (99%) rename src/app/emu/{ => audio}/spc700.h (86%) rename src/app/emu/{dbg.h => debug/debugger.h} (84%) rename src/app/emu/{ => video}/ppu.cc (87%) rename src/app/emu/{ => video}/ppu.h (98%) create mode 100644 test/spc700_test.cc diff --git a/src/app/emu/apu.cc b/src/app/emu/apu.cc deleted file mode 100644 index 154eec9e..00000000 --- a/src/app/emu/apu.cc +++ /dev/null @@ -1,75 +0,0 @@ -#include "app/emu/apu.h" - -#include -#include -#include - -#include "app/emu/mem.h" - -namespace yaze { -namespace app { -namespace emu { - -void APU::Init() { - // Set the clock frequency - clock_.SetFrequency(kApuClockSpeed); - - // Initialize registers - // ... -} - -void APU::Reset() { - // Reset the clock - clock_.ResetAccumulatedTime(); - - // Reset the SPC700 - // ... -} - -void APU::Update() { - auto cycles_to_run = clock_.GetCycleCount(); - - for (auto i = 0; i < cycles_to_run; ++i) { - // Update the APU - // ... - - // Update the SPC700 - // ... - } -} - -uint8_t APU::ReadRegister(uint16_t address) { - // ... -} - -void APU::WriteRegister(uint16_t address, uint8_t value) { - // ... -} - -const std::vector& APU::GetAudioSamples() const { - // ... -} - -void APU::UpdateChannelSettings() { - // ... -} - -int16_t APU::GenerateSample(int channel) { - // ... -} - -void APU::ApplyEnvelope(int channel) { - // ... -} - -uint8_t APU::ReadDSPMemory(uint16_t address) { - // ... -} - -void APU::WriteDSPMemory(uint16_t address, uint8_t value) { - // ... -} - -} // namespace emu -} // namespace app -} // namespace yaze \ No newline at end of file diff --git a/src/app/emu/apu.h b/src/app/emu/apu.h deleted file mode 100644 index 237f90ed..00000000 --- a/src/app/emu/apu.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef YAZE_APP_EMU_APU_H_ -#define YAZE_APP_EMU_APU_H_ - -#include - -#include "app/emu/clock.h" -#include "app/emu/mem.h" -#include "app/emu/spc700.h" - -namespace yaze { -namespace app { -namespace emu { - -const int kApuClockSpeed = 1024000; // 1.024 MHz -const int apuSampleRate = 32000; // 32 KHz -const int apuClocksPerSample = 64; // 64 clocks per sample - -class APU : public Observer { - public: - // Initializes the APU with the necessary resources and dependencies - APU(Memory &memory, VirtualAudioRAM &aram, VirtualClock &clock) - : memory_(memory), clock_(clock), aram_(aram) {} - - void Init(); - - // Resets the APU to its initial state - void Reset(); - - // Runs the APU for one frame - void Update(); - - void Notify(uint32_t address, uint8_t data) override { - if (address >= 0x2140 && address <= 0x2143) { - // Handle communication with the APU - } - } - - void UpdateClock(int delta_time) { clock_.UpdateClock(delta_time); } - - // Reads a byte from the specified APU register - uint8_t ReadRegister(uint16_t address); - - // Writes a byte to the specified APU register - void WriteRegister(uint16_t address, uint8_t value); - - // Returns the audio samples for the current frame - const std::vector &GetAudioSamples() const; - - private: - // Internal methods to handle APU operations and sound generation - - // Updates internal state based on APU register settings - void UpdateChannelSettings(); - - // Generates a sample for an audio channel - int16_t GenerateSample(int channel); - - // Applies an envelope to an audio channel - void ApplyEnvelope(int channel); - - // Handles DSP (Digital Signal Processor) memory reads and writes - uint8_t ReadDSPMemory(uint16_t address); - void WriteDSPMemory(uint16_t address, uint8_t value); - - // Member variables to store internal APU state and resources - Memory &memory_; - VirtualClock &clock_; - VirtualAudioRAM &aram_; - - SPC700 spc700_{aram_}; - std::vector audioSamples_; - // Other state variables (registers, counters, channel settings, etc.) -}; - -} // namespace emu -} // namespace app -} // namespace yaze - -#endif \ No newline at end of file diff --git a/src/app/emu/audio/apu.cc b/src/app/emu/audio/apu.cc new file mode 100644 index 00000000..deb17f9f --- /dev/null +++ b/src/app/emu/audio/apu.cc @@ -0,0 +1,125 @@ +#include "app/emu/audio/apu.h" + +#include +#include +#include +#include + +#include "app/emu/audio/dsp.h" +#include "app/emu/audio/spc700.h" +#include "app/emu/clock.h" +#include "app/emu/mem.h" + +namespace yaze { +namespace app { +namespace emu { + +void APU::Init() { + // Set the clock frequency + clock_.SetFrequency(kApuClockSpeed); + + // Initialize Digital Signal Processor Callbacks + dsp_.SetSampleFetcher([this](uint16_t address) -> uint8_t { + return this->FetchSampleFromRam(address); + }); + + dsp_.SetSamplePusher( + [this](int16_t sample) { this->PushToAudioBuffer(sample); }); + + // Initialize registers + // ... +} + +void APU::Reset() { + // Reset the clock + clock_.ResetAccumulatedTime(); + + // Reset the SPC700 + // ... +} + +void APU::Update() { + auto cycles_to_run = clock_.GetCycleCount(); + + for (auto i = 0; i < cycles_to_run; ++i) { + // Update the APU + // ... + + // Update the SPC700 + // ... + } +} + +void APU::ProcessSamples() { + // Fetch sample data from AudioRam + // Iterate over all voices + for (uint8_t voice_num = 0; voice_num < 8; voice_num++) { + // Fetch the sample data for the current voice from AudioRam + uint8_t sample = FetchSampleForVoice(voice_num); + + // Process the sample through DSP + int16_t processed_sample = dsp_.ProcessSample(voice_num, sample); + + // Add the processed sample to the audio buffer + audioSamples_.push_back(processed_sample); + } +} + +uint8_t APU::FetchSampleForVoice(uint8_t voice_num) { + // Define how you determine the address based on the voice_num + uint16_t address = CalculateAddressForVoice(voice_num); + return aram_.read(address); +} + +uint16_t APU::CalculateAddressForVoice(uint8_t voice_num) { + // Placeholder logic to calculate the address in the AudioRam + // based on the voice number. + return voice_num; // Assuming each voice has a fixed size +} + +int16_t APU::GetNextSample() { + // This method fetches the next sample. If there's no sample available, it can + // return 0 or the last sample. + if (!audioSamples_.empty()) { + int16_t sample = audioSamples_.front(); + audioSamples_.erase(audioSamples_.begin()); + return sample; + } + return 0; // or return the last sample +} + +uint8_t APU::ReadRegister(uint16_t address) { + // ... +} + +void APU::WriteRegister(uint16_t address, uint8_t value) { + // ... +} + +const std::vector& APU::GetAudioSamples() const { + // ... +} + +void APU::UpdateChannelSettings() { + // ... +} + +int16_t APU::GenerateSample(int channel) { + // ... +} + +void APU::ApplyEnvelope(int channel) { + // ... +} + +uint8_t APU::ReadDSPMemory(uint16_t address) { + // ... +} + +void APU::WriteDSPMemory(uint16_t address, uint8_t value) { + // ... +} + +} // namespace emu +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/emu/audio/apu.h b/src/app/emu/audio/apu.h new file mode 100644 index 00000000..73d08aa9 --- /dev/null +++ b/src/app/emu/audio/apu.h @@ -0,0 +1,192 @@ +#ifndef YAZE_APP_EMU_APU_H_ +#define YAZE_APP_EMU_APU_H_ + +#include +#include +#include + +#include "app/emu/audio/dsp.h" +#include "app/emu/audio/spc700.h" +#include "app/emu/clock.h" +#include "app/emu/mem.h" + +namespace yaze { +namespace app { +namespace emu { + +/** + * + * 64 kilobytes of RAM are mapped across the 16-bit memory space of the SPC-700. + * Some regions of this space are overlaid with special hardware functions. + * + * Range Note + * $0000-00EF Zero Page RAM + * $00F0-00FF Sound CPU Registers + * $0100-01FF Stack Page RAM + * $0200-FFBF RAM + * $FFC0-FFFF IPL ROM or RAM + * + * The region at $FFC0-FFFF will normally read from the 64-byte IPL ROM, but the + * underlying RAM can always be written to, and the high bit of the Control + * register $F1 can be cleared to unmap the IPL ROM and allow read access to + * this RAM. + * + */ + +const int kApuClockSpeed = 1024000; // 1.024 MHz +const int apuSampleRate = 32000; // 32 KHz +const int apuClocksPerSample = 64; // 64 clocks per sample + +class APU : public Observer { + public: + // Initializes the APU with the necessary resources and dependencies + APU(Memory &memory, AudioRam &aram, Clock &clock) + : memory_(memory), aram_(aram), clock_(clock) {} + + void Init(); + + // Resets the APU to its initial state + void Reset(); + + // Runs the APU for one frame + void Update(); + + void ProcessSamples(); + + uint8_t FetchSampleForVoice(uint8_t voice_num); + + uint16_t CalculateAddressForVoice(uint8_t voice_num); + + int16_t GetNextSample(); + + void Notify(uint32_t address, uint8_t data) override { + if (address >= 0x2140 && address <= 0x2143) { + // Handle communication with the APU + } + } + + void UpdateClock(int delta_time) { clock_.UpdateClock(delta_time); } + + // Method to fetch a sample from AudioRam + uint8_t FetchSampleFromRam(uint16_t address) { return aram_.read(address); } + + // Method to push a processed sample to the audio buffer + void PushToAudioBuffer(int16_t sample) { audioSamples_.push_back(sample); } + + // Reads a byte from the specified APU register + uint8_t ReadRegister(uint16_t address); + + // Writes a byte to the specified APU register + void WriteRegister(uint16_t address, uint8_t value); + + // Returns the audio samples for the current frame + const std::vector &GetAudioSamples() const; + + // Called upon a reset + void Initialize() { + spc700_.Reset(); + dsp_.Reset(); + // Set stack pointer, zero-page values, etc. for the SPC700 + SignalReady(); + } + + void SignalReady() { + // Set Port 0 = $AA and Port 1 = $BB + ports_[0] = READY_SIGNAL_0; + ports_[1] = READY_SIGNAL_1; + } + + bool IsReadySignalReceived() const { + return ports_[0] == READY_SIGNAL_0 && ports_[1] == READY_SIGNAL_1; + } + + void WaitForSignal() { + // This might be an active wait or a passive state where APU does nothing + // until it's externally triggered by the main CPU writing to its ports. + while (ports_[0] != BEGIN_SIGNAL) + ; + } + + uint16_t ReadAddressFromPorts() const { + // Read 2 byte address from port 2 (low) and 3 (high) + return static_cast(ports_[2]) | + (static_cast(ports_[3]) << 8); + } + + void AcknowledgeSignal() { + // Read value from Port 0 and write it back to Port 0 + ports_[0] = ports_[0]; + } + + void BeginTransfer() { + uint16_t destAddr = ReadAddressFromPorts(); + uint8_t counter = 0; + + // Port 1 determines whether to execute or transfer + while (ports_[1] != 0) { + uint8_t data = ports_[1]; + aram_.write(destAddr, data); + AcknowledgeSignal(); + + destAddr++; + counter++; + + // Synchronize with the counter from the main CPU + while (ports_[0] != counter) + ; + } + } + + void ExecuteProgram() { + // For now, this is a placeholder. Actual execution would involve running + // the SPC700's instruction at the specified address. + spc700_.ExecuteInstructions(ReadAddressFromPorts()); + } + + // This method will be called by the main CPU to write to the APU's ports. + void WriteToPort(uint8_t portNum, uint8_t value) { + if (portNum < 4) { + ports_[portNum] = value; + if (portNum == 0 && value == BEGIN_SIGNAL) { + BeginTransfer(); + } + } + } + + private: + // Constants for communication + static const uint8_t READY_SIGNAL_0 = 0xAA; + static const uint8_t READY_SIGNAL_1 = 0xBB; + static const uint8_t BEGIN_SIGNAL = 0xCC; + + // Port buffers (equivalent to $2140 to $2143 for the main CPU) + uint8_t ports_[4] = {0}; + + // Updates internal state based on APU register settings + void UpdateChannelSettings(); + + // Generates a sample for an audio channel + int16_t GenerateSample(int channel); + + // Applies an envelope to an audio channel + void ApplyEnvelope(int channel); + + // Handles DSP (Digital Signal Processor) memory reads and writes + uint8_t ReadDSPMemory(uint16_t address); + void WriteDSPMemory(uint16_t address, uint8_t value); + + // Member variables to store internal APU state and resources + Memory &memory_; + AudioRam &aram_; + Clock &clock_; + + SPC700 spc700_{aram_}; + Dsp dsp_; + std::vector audioSamples_; +}; + +} // namespace emu +} // namespace app +} // namespace yaze + +#endif \ No newline at end of file diff --git a/src/app/emu/audio/dsp.cc b/src/app/emu/audio/dsp.cc new file mode 100644 index 00000000..833550d0 --- /dev/null +++ b/src/app/emu/audio/dsp.cc @@ -0,0 +1,281 @@ +#include "app/emu/audio/dsp.h" + +#include "app/emu/mem.h" + +namespace yaze { +namespace app { +namespace emu { + +void Dsp::Reset() {} + +uint8_t Dsp::ReadVoiceReg(uint8_t voice, uint8_t reg) const { + voice %= kNumVoices; + switch (reg % kNumVoiceRegs) { + case 0: + return voices_[voice].vol_left; + case 1: + return voices_[voice].vol_right; + case 2: + return voices_[voice].pitch_low; + case 3: + return voices_[voice].pitch_high; + case 4: + return voices_[voice].source_number; + case 5: + return voices_[voice].adsr1; + case 6: + return voices_[voice].adsr2; + case 7: + return voices_[voice].gain; + case 8: + return voices_[voice].envx; + case 9: + return voices_[voice].outx; + default: + return 0; // This shouldn't happen, but it's good to have a default + // case + } +} + +void Dsp::WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value) { + voice %= kNumVoices; + switch (reg % kNumVoiceRegs) { + case 0: + voices_[voice].vol_left = static_cast(value); + break; + case 1: + voices_[voice].vol_right = static_cast(value); + break; + case 2: + voices_[voice].pitch_low = value; + break; + case 3: + voices_[voice].pitch_high = value; + break; + case 4: + voices_[voice].source_number = value; + break; + case 5: + voices_[voice].adsr1 = value; + break; + case 6: + voices_[voice].adsr2 = value; + break; + case 7: + voices_[voice].gain = value; + break; + // Note: envx and outx are read-only, so they don't have cases here + } +} + +// Set the callbacks +void Dsp::SetSampleFetcher(SampleFetcher fetcher) { sample_fetcher_ = fetcher; } + +void Dsp::SetSamplePusher(SamplePusher pusher) { sample_pusher_ = pusher; } + +int16_t Dsp::DecodeSample(uint8_t voice_num) { + Voice const& voice = voices_[voice_num]; + uint16_t sample_address = voice.source_number; + + // Use the callback to fetch the sample + int16_t sample = static_cast(sample_fetcher_(sample_address) << 8); + return sample; +} + +int16_t Dsp::ProcessSample(uint8_t voice_num, int16_t sample) { + Voice const& voice = voices_[voice_num]; + + // Adjust the pitch (for simplicity, we're just adjusting the sample value) + sample += voice.pitch_low + (voice.pitch_high << 8); + + // Apply volume (separate for left and right for stereo sound) + int16_t left_sample = (sample * voice.vol_left) / 255; + int16_t right_sample = (sample * voice.vol_right) / 255; + + // Combine stereo samples into a single 16-bit value + return (left_sample + right_sample) / 2; +} + +void Dsp::MixSamples() { + int16_t mixed_sample = 0; + + for (uint8_t i = 0; i < kNumVoices; i++) { + int16_t decoded_sample = DecodeSample(i); + int16_t processed_sample = ProcessSample(i, decoded_sample); + mixed_sample += processed_sample; + } + + // Clamp the mixed sample to 16-bit range + if (mixed_sample > 32767) { + mixed_sample = 32767; + } else if (mixed_sample < -32768) { + mixed_sample = -32768; + } + + // Use the callback to push the mixed sample + sample_pusher_(mixed_sample); +} + +void Dsp::UpdateEnvelope(uint8_t voice) { + uint8_t adsr1 = ReadVoiceReg(voice, 0x05); + uint8_t adsr2 = ReadVoiceReg(voice, 0x06); + uint8_t gain = ReadVoiceReg(voice, 0x07); + + uint8_t enableADSR = (adsr1 & 0x80) >> 7; + + if (enableADSR) { + // Handle ADSR envelope + Voice& voice_obj = voices_[voice]; + switch (voice_obj.state) { + case VoiceState::ATTACK: + // Update amplitude based on attack rate + voice_obj.current_amplitude += AttackRate(adsr1); + if (voice_obj.current_amplitude >= ENVELOPE_MAX) { + voice_obj.current_amplitude = ENVELOPE_MAX; + voice_obj.state = VoiceState::DECAY; + } + break; + case VoiceState::DECAY: + // Update amplitude based on decay rate + voice_obj.current_amplitude -= DecayRate(adsr2); + if (voice_obj.current_amplitude <= voice_obj.decay_level) { + voice_obj.current_amplitude = voice_obj.decay_level; + voice_obj.state = VoiceState::SUSTAIN; + } + break; + case VoiceState::SUSTAIN: + // Keep amplitude at the calculated decay level + voice_obj.current_amplitude = voice_obj.decay_level; + break; + case VoiceState::RELEASE: + // Update amplitude based on release rate + voice_obj.current_amplitude -= ReleaseRate(adsr2); + if (voice_obj.current_amplitude <= 0) { + voice_obj.current_amplitude = 0; + voice_obj.state = VoiceState::OFF; + } + break; + default: + break; + } + } else { + // Handle Gain envelope + // Extract mode from the gain byte + uint8_t mode = (gain & 0xE0) >> 5; + uint8_t rate = gain & 0x1F; + + Voice& voice_obj = voices_[voice]; + + switch (mode) { + case 0: // Direct Designation + case 1: + case 2: + case 3: + voice_obj.current_amplitude = + rate << 3; // Multiplying by 8 to scale to 0-255 + break; + + case 6: // Increase Mode (Linear) + voice_obj.current_amplitude += gainTimings[0][rate]; + if (voice_obj.current_amplitude > ENVELOPE_MAX) { + voice_obj.current_amplitude = ENVELOPE_MAX; + } + break; + + case 7: // Increase Mode (Bent Line) + // Hypothetical behavior: Increase linearly at first, then increase + // more slowly You'll likely need to adjust this based on your + // specific requirements + if (voice_obj.current_amplitude < (ENVELOPE_MAX / 2)) { + voice_obj.current_amplitude += gainTimings[1][rate]; + } else { + voice_obj.current_amplitude += gainTimings[1][rate] / 2; + } + if (voice_obj.current_amplitude > ENVELOPE_MAX) { + voice_obj.current_amplitude = ENVELOPE_MAX; + } + break; + + case 4: // Decrease Mode (Linear) + if (voice_obj.current_amplitude < gainTimings[2][rate]) { + voice_obj.current_amplitude = 0; + } else { + voice_obj.current_amplitude -= gainTimings[2][rate]; + } + break; + + case 5: // Decrease Mode (Exponential) + voice_obj.current_amplitude -= + (voice_obj.current_amplitude * gainTimings[3][rate]) / ENVELOPE_MAX; + break; + + default: + // Default behavior can be handled here if necessary + break; + } + } +} + +void Dsp::update_voice_state(uint8_t voice_num) { + if (voice_num >= kNumVoices) return; + + Voice& voice = voices_[voice_num]; + switch (voice.state) { + case VoiceState::OFF: + // Reset current amplitude + voice.current_amplitude = 0; + break; + + case VoiceState::ATTACK: + // Increase the current amplitude at a rate defined by the ATTACK + // setting + voice.current_amplitude += AttackRate(voice.adsr1); + if (voice.current_amplitude >= ENVELOPE_MAX) { + voice.current_amplitude = ENVELOPE_MAX; + voice.state = VoiceState::DECAY; + voice.decay_level = CalculateDecayLevel(voice.adsr2); + } + break; + + case VoiceState::DECAY: + // Decrease the current amplitude at a rate defined by the DECAY setting + voice.current_amplitude -= DecayRate(voice.adsr2); + if (voice.current_amplitude <= voice.decay_level) { + voice.current_amplitude = voice.decay_level; + voice.state = VoiceState::SUSTAIN; + } + break; + + case VoiceState::SUSTAIN: + // Keep the current amplitude at the decay level + break; + + case VoiceState::RELEASE: + // Decrease the current amplitude at a rate defined by the RELEASE + // setting + voice.current_amplitude -= ReleaseRate(voice.adsr2); + if (voice.current_amplitude == 0) { + voice.state = VoiceState::OFF; + } + break; + } +} + +void Dsp::process_envelope(uint8_t voice_num) { + if (voice_num >= kNumVoices) return; + + Voice& voice = voices_[voice_num]; + + // Update the voice state first (based on keys, etc.) + update_voice_state(voice_num); + + // Calculate the envelope value based on the current amplitude + voice.envx = calculate_envelope_value(voice.current_amplitude); + + // Apply the envelope value to the audio output + apply_envelope_to_output(voice_num); +} + +} // namespace emu +} // namespace app +} // namespace yaze \ No newline at end of file diff --git a/src/app/emu/audio/dsp.h b/src/app/emu/audio/dsp.h new file mode 100644 index 00000000..7dd570e6 --- /dev/null +++ b/src/app/emu/audio/dsp.h @@ -0,0 +1,315 @@ +#ifndef YAZE_APP_EMU_AUDIO_S_DSP_H +#define YAZE_APP_EMU_AUDIO_S_DSP_H + +#include +#include +#include + +#include "app/emu/mem.h" + +namespace yaze { +namespace app { +namespace emu { + +using SampleFetcher = std::function; +using SamplePusher = std::function; + +/** + * + * The S-DSP is a digital signal processor generating the sound data. + * + * A DSP register can be selected with $F2, after which it can be read or + * written at $F3. Often it is useful to load the register address into A, and + * the value to send in Y, so that MOV $F2, YA can be used to do both in one + * 16-bit instruction. + * + * The DSP register address space only has 7 bits. The high bit of $F2, if set, + * will make the selected register read-only via $F3. + * + * When initializing the DSP registers for the first time, take care not to + * accidentally enable echo writeback via FLG, because it will immediately begin + * overwriting values in RAM. + * + * Voices + * There are 8 voices, numbered 0 to 7. + * Each voice X has 10 registers in the range $X0-$X9. + * + * Name Address Bits Notes + * VOL (L) $X0 SVVV VVVV Left channel volume, signed. + * VOL (R) $X1 SVVV VVVV Right channel volume, signed. + * P (L) $X2 LLLL LLLL Low 8 bits of sample pitch. + * P (H) $X3 --HH HHHH High 6 bits of sample pitch. + * SCRN $X4 SSSS SSSS Selects a sample source entry from the + * directory ADSR (1) $X5 EDDD AAAA ADSR enable (E), decay rate (D), + * attack rate (A). + * ADSR (2) $X6 SSSR RRRR Sustain level (S), release rate (R). + * GAIN $X7 0VVV VVVV 1MMV VVVV Mode (M), value (V). + * ENVX $X8 0VVV VVVV Reads current 7-bit value of ADSR/GAIN + * envelope. + * OUTX $X9 SVVV VVVV Reads signed 8-bit value of current + * sample wave multiplied by ENVX, before applying VOL. + * + */ + +class Dsp { + private: + static const size_t kNumVoices = 8; + static const size_t kNumVoiceRegs = 10; + static const size_t kNumGlobalRegs = 15; + + enum class VoiceState { OFF, ATTACK, DECAY, SUSTAIN, RELEASE }; + + struct Voice { + int8_t vol_left; // x0 + int8_t vol_right; // x1 + uint8_t pitch_low; // x2 + uint8_t pitch_high; // x3 + uint8_t source_number; // x4 + uint8_t adsr1; // x5 + uint8_t adsr2; // x6 + uint8_t gain; // x7 + uint8_t envx; // x8 (read-only) + int8_t outx; // x9 (read-only) + + VoiceState state = VoiceState::OFF; + uint16_t current_amplitude = 0; // Current amplitude value used for ADSR + uint16_t decay_level; // Calculated decay level based on ADSR settings + }; + Voice voices_[8]; + + // Global DSP registers + uint8_t mvol_left; // 0C + uint8_t mvol_right; // 0D + uint8_t evol_left; // 0E + uint8_t evol_right; // 0F + uint8_t kon; // 10 + uint8_t koff; // 11 + uint8_t flags; // 12 + uint8_t endx; // 13 (read-only) + + // Global registers + std::vector globalRegs = std::vector(kNumGlobalRegs, 0x00); + + static const uint16_t ENVELOPE_MAX = 2047; // $7FF + + // Attack times in ms + const std::vector attackTimes = { + 4100, 2600, 1500, 1000, 640, 380, 260, 160, 96, 64, 40, 24, 16, 10, 6, 0}; + + // Decay times in ms + const std::vector decayTimes = {1200, 740, 440, 290, + 180, 110, 74, 37}; + + // Release times in ms + const std::vector releaseTimes = { + // "Infinite" is represented by a large value, e.g., UINT32_MAX + UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400, + 7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500, + 1200, 880, 740, 590, 440, 370, 290, 220, + 180, 150, 110, 92, 74, 55, 37, 18}; + + // Gain timings for decrease linear, decrease exponential, etc. + // Organized by mode: [Linear Increase, Bentline Increase, Linear Decrease, + // Exponential Decrease] + const std::vector> gainTimings = { + {UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380, + 320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32, + 24, 20, 16, 12, 10, 8, 6, 4, 2}, + {UINT32_MAX, 5400, 4600, 3500, 2600, 2300, 1800, 1300, 1100, 900, + 670, 560, 450, 340, 280, 220, 170, 140, 110, 84, + 70, 56, 42, 35, 28, 21, 18, 14, 11, 7, + /*3.5=*/3}, + // Repeating the Linear Increase timings for Linear Decrease, since they + // are the same. + {UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380, + 320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32, + 24, 20, 16, 12, 10, 8, 6, 4, 2}, + {UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400, + 7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500, + 1200, 880, 740, 590, 440, 370, 290, 220, + 180, 150, 110, 92, 55, 37, 18}}; + + // DSP Period Table + const std::vector> DspPeriodTable = { + // ... Your DSP period table here ... + }; + + // DSP Period Offset + const std::vector DspPeriodOffset = { + // ... Your DSP period offsets here ... + }; + + uint8_t calculate_envelope_value(uint16_t amplitude) const { + // Convert the 16-bit amplitude to an 8-bit envelope value + return amplitude >> 8; + } + + void apply_envelope_to_output(uint8_t voice_num) { + Voice& voice = voices_[voice_num]; + + // Scale the OUTX by the envelope value + // This might be a linear scaling, or more complex operations can be used + voice.outx = (voice.outx * voice.envx) / 255; + } + + SampleFetcher sample_fetcher_; + SamplePusher sample_pusher_; + + public: + Dsp() = default; + + void Reset(); + + void SetSampleFetcher(std::function fetcher); + void SetSamplePusher(std::function pusher); + + // Read a byte from a voice register + uint8_t ReadVoiceReg(uint8_t voice, uint8_t reg) const; + + // Write a byte to a voice register + void WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value); + + // Read a byte from a global register + uint8_t ReadGlobalReg(uint8_t reg) const { + return globalRegs[reg % kNumGlobalRegs]; + } + + // Write a byte to a global register + void WriteGlobalReg(uint8_t reg, uint8_t value) { + globalRegs[reg % kNumGlobalRegs] = value; + } + + int16_t DecodeSample(uint8_t voice_num); + int16_t ProcessSample(uint8_t voice_num, int16_t sample); + void MixSamples(); + + // Trigger a voice to start playing + void trigger_voice(uint8_t voice_num) { + if (voice_num >= kNumVoices) return; + + Voice& voice = voices_[voice_num]; + voice.state = VoiceState::ATTACK; + // Initialize other state management variables if needed + } + + // Release a voice (e.g., note release in ADSR) + void release_voice(uint8_t voice_num) { + if (voice_num >= kNumVoices) return; + + Voice& voice = voices_[voice_num]; + if (voice.state != VoiceState::OFF) { + voice.state = VoiceState::RELEASE; + } + // Update other state management variables if needed + } + + // Calculate envelope for a given voice + void UpdateEnvelope(uint8_t voice); + + // Voice-related functions (implementations) + void set_voice_volume(int voice_num, int8_t left, int8_t right) { + voices_[voice_num].vol_left = left; + voices_[voice_num].vol_right = right; + } + + void set_voice_pitch(int voice_num, uint16_t pitch) { + voices_[voice_num].pitch_low = pitch & 0xFF; + voices_[voice_num].pitch_high = (pitch >> 8) & 0xFF; + } + + void set_voice_source_number(int voice_num, uint8_t srcn) { + voices_[voice_num].source_number = srcn; + } + + void set_voice_adsr(int voice_num, uint8_t adsr1, uint8_t adsr2) { + voices_[voice_num].adsr1 = adsr1; + voices_[voice_num].adsr2 = adsr2; + } + + void set_voice_gain(int voice_num, uint8_t gain) { + voices_[voice_num].gain = gain; + } + + uint8_t read_voice_envx(int voice_num) { return voices_[voice_num].envx; } + + int8_t read_voice_outx(int voice_num) { return voices_[voice_num].outx; } + + // Global DSP functions + void set_master_volume(int8_t left, int8_t right) { + mvol_left = left; + mvol_right = right; + } + + void set_echo_volume(int8_t left, int8_t right) { + evol_left = left; + evol_right = right; + } + + void update_voice_state(uint8_t voice_num); + + // Override the key_on and key_off methods to utilize the new state management + void key_on(uint8_t value) { + for (uint8_t i = 0; i < kNumVoices; i++) { + if (value & (1 << i)) { + trigger_voice(i); + } + } + } + + void key_off(uint8_t value) { + for (uint8_t i = 0; i < kNumVoices; i++) { + if (value & (1 << i)) { + release_voice(i); + } + } + } + + void set_flags(uint8_t value) { + flags = value; + // More logic may be needed here depending on flag behaviors + } + + uint8_t read_endx() { return endx; } + + uint16_t AttackRate(uint8_t adsr1) { + // Convert the ATTACK portion of adsr1 into a rate of amplitude change + // You might need to adjust this logic based on the exact ADSR + // implementation details + return (adsr1 & 0x0F) * 16; // Just a hypothetical conversion + } + + uint16_t DecayRate(uint8_t adsr2) { + // Convert the DECAY portion of adsr2 into a rate of amplitude change + return ((adsr2 >> 4) & 0x07) * 8; // Hypothetical conversion + } + + uint16_t ReleaseRate(uint8_t adsr2) { + // Convert the RELEASE portion of adsr2 into a rate of amplitude change + return (adsr2 & 0x0F) * 16; // Hypothetical conversion + } + + uint16_t CalculateDecayLevel(uint8_t adsr2) { + // Calculate the decay level based on the SUSTAIN portion of adsr2 + // This is the level the amplitude will decay to before entering the SUSTAIN + // phase Again, adjust based on your implementation details + return ((adsr2 >> 4) & 0x07) * 256; // Hypothetical conversion + } + + // Envelope processing for all voices + // Goes through each voice and processes its envelope. + void process_envelopes() { + for (size_t i = 0; i < kNumVoices; ++i) { + process_envelope(i); + } + } + + // Envelope processing for a specific voice + // For a given voice, update its state (ADSR), calculate the envelope value, + // and apply the envelope to the audio output. + void process_envelope(uint8_t voice_num); +}; +} // namespace emu +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_EMU_AUDIO_S_DSP_H \ No newline at end of file diff --git a/src/app/emu/spc700.cc b/src/app/emu/audio/spc700.cc similarity index 99% rename from src/app/emu/spc700.cc rename to src/app/emu/audio/spc700.cc index bd550a5f..24f1c518 100644 --- a/src/app/emu/spc700.cc +++ b/src/app/emu/audio/spc700.cc @@ -1,4 +1,4 @@ -#include "app/emu/spc700.h" +#include "app/emu/audio/spc700.h" #include #include @@ -7,6 +7,8 @@ namespace yaze { namespace app { namespace emu { +void SPC700::Reset() {} + void SPC700::ExecuteInstructions(uint8_t opcode) { switch (opcode) { // 8-bit Move Memory to Register diff --git a/src/app/emu/spc700.h b/src/app/emu/audio/spc700.h similarity index 86% rename from src/app/emu/spc700.h rename to src/app/emu/audio/spc700.h index a1df080c..c2915794 100644 --- a/src/app/emu/spc700.h +++ b/src/app/emu/audio/spc700.h @@ -9,9 +9,9 @@ namespace yaze { namespace app { namespace emu { -class VirtualAudioRAM { +class AudioRam { public: - virtual ~VirtualAudioRAM() = default; + virtual ~AudioRam() = default; // Read a byte from ARAM at the given address virtual uint8_t read(uint16_t address) const = 0; @@ -20,12 +20,12 @@ class VirtualAudioRAM { virtual void write(uint16_t address, uint8_t value) = 0; }; -class AudioRAM : public VirtualAudioRAM { +class AudioRamImpl : public AudioRam { static const size_t ARAM_SIZE = 64 * 1024; // 64 KB std::vector ram = std::vector(ARAM_SIZE, 0); public: - AudioRAM() = default; + AudioRamImpl() = default; // Read a byte from ARAM at the given address uint8_t read(uint16_t address) const override { @@ -38,51 +38,12 @@ class AudioRAM : public VirtualAudioRAM { } }; -// Digital Signal Processor -class DigitalSignalProcessor { - private: - static const size_t NUM_VOICES = 8; - static const size_t NUM_VOICE_REGS = 10; - static const size_t NUM_GLOBAL_REGS = 15; - - // Each voice has 10 registers - std::vector> voices = std::vector>( - NUM_VOICES, std::vector(NUM_VOICE_REGS, 0)); - - // Global registers - std::vector globalRegs = std::vector(NUM_GLOBAL_REGS, 0x00); - - public: - DigitalSignalProcessor() = default; - - // Read a byte from a voice register - uint8_t ReadVoiceReg(uint8_t voice, uint8_t reg) const { - return voices[voice % NUM_VOICES][reg % NUM_VOICE_REGS]; - } - - // Write a byte to a voice register - void WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value) { - voices[voice % NUM_VOICES][reg % NUM_VOICE_REGS] = value; - } - - // Read a byte from a global register - uint8_t ReadGlobalReg(uint8_t reg) const { - return globalRegs[reg % NUM_GLOBAL_REGS]; - } - - // Write a byte to a global register - void WriteGlobalReg(uint8_t reg, uint8_t value) { - globalRegs[reg % NUM_GLOBAL_REGS] = value; - } -}; - class SPC700 { private: - VirtualAudioRAM& aram_; + AudioRam& aram_; public: - explicit SPC700(VirtualAudioRAM& aram) : aram_(aram) {} - DigitalSignalProcessor sdsp; + explicit SPC700(AudioRam& aram) : aram_(aram) {} uint8_t test_register_; uint8_t control_register_; uint8_t dsp_address_register_; @@ -107,6 +68,8 @@ class SPC700 { }; Flags PSW; // Processor status word + void Reset(); + void ExecuteInstructions(uint8_t opcode); // Read a byte from the memory-mapped registers @@ -118,8 +81,6 @@ class SPC700 { return control_register_; case 0xF2: return dsp_address_register_; - case 0xF3: - return sdsp.ReadGlobalReg(dsp_address_register_); default: if (address < 0xFFC0) { return aram_.read(address); @@ -142,9 +103,6 @@ class SPC700 { case 0xF2: dsp_address_register_ = value; break; - case 0xF3: - sdsp.WriteGlobalReg(dsp_address_register_, value); - break; default: if (address < 0xFFC0) { aram_.write(address, value); diff --git a/src/app/emu/clock.h b/src/app/emu/clock.h index 3ce5d2be..17a7a42c 100644 --- a/src/app/emu/clock.h +++ b/src/app/emu/clock.h @@ -7,9 +7,9 @@ namespace yaze { namespace app { namespace emu { -class VirtualClock { +class Clock { public: - virtual ~VirtualClock() = default; + virtual ~Clock() = default; virtual void UpdateClock(double delta) = 0; virtual unsigned long long GetCycleCount() const = 0; virtual void ResetAccumulatedTime() = 0; @@ -17,10 +17,10 @@ class VirtualClock { virtual float GetFrequency() const = 0; }; -class Clock : public VirtualClock { +class ClockImpl : public Clock { public: - Clock() = default; - virtual ~Clock() = default; + ClockImpl() = default; + virtual ~ClockImpl() = default; void UpdateCycleCount(double deltaTime) { accumulatedTime += deltaTime; diff --git a/src/app/emu/cpu.cc b/src/app/emu/cpu.cc index 4f2b105a..0c86765e 100644 --- a/src/app/emu/cpu.cc +++ b/src/app/emu/cpu.cc @@ -1176,6 +1176,7 @@ void CPU::ExecuteInstruction(uint8_t opcode) { void CPU::HandleInterrupts() {} +// ADC: Add with carry void CPU::ADC(uint8_t operand) { bool C = GetCarryFlag(); if (GetAccumulatorSize()) { // 8-bit mode @@ -1209,6 +1210,7 @@ void CPU::ADC(uint8_t operand) { } } +// AND: Logical AND void CPU::AND(uint16_t value, bool isImmediate) { uint16_t operand; if (E == 0) { // 16-bit mode @@ -1232,6 +1234,7 @@ void CPU::ANDAbsoluteLong(uint32_t address) { SetNegativeFlag(A & 0x80000000); } +// ASL: Arithmetic shift left void CPU::ASL(uint16_t address) { uint8_t value = memory.ReadByte(address); SetCarryFlag(!(value & 0x80)); // Set carry flag if bit 7 is set @@ -1242,6 +1245,7 @@ void CPU::ASL(uint16_t address) { SetZeroFlag(value); } +// BCC: Branch if carry clear void CPU::BCC(int8_t offset) { if (!GetCarryFlag()) { // If the carry flag is clear PC += offset; // Add the offset to the program counter @@ -1520,7 +1524,7 @@ void CPU::JSL(uint32_t address) { } // LDA: Load accumulator -void CPU::LDA(uint16_t address, bool isImmediate ) { +void CPU::LDA(uint16_t address, bool isImmediate) { if (GetAccumulatorSize()) { A = isImmediate ? address : memory.ReadByte(address); SetZeroFlag(A == 0); @@ -1533,7 +1537,7 @@ void CPU::LDA(uint16_t address, bool isImmediate ) { } // LDX: Load X register -void CPU::LDX(uint16_t address, bool isImmediate ) { +void CPU::LDX(uint16_t address, bool isImmediate) { if (GetIndexSize()) { X = isImmediate ? address : memory.ReadByte(address); SetZeroFlag(X == 0); @@ -1546,7 +1550,7 @@ void CPU::LDX(uint16_t address, bool isImmediate ) { } // LDY: Load Y register -void CPU::LDY(uint16_t address, bool isImmediate ) { +void CPU::LDY(uint16_t address, bool isImmediate) { if (GetIndexSize()) { Y = isImmediate ? address : memory.ReadByte(address); SetZeroFlag(Y == 0); @@ -1577,7 +1581,7 @@ void CPU::NOP() { } // ORA: Logical OR -void CPU::ORA(uint16_t address, bool isImmediate ) { +void CPU::ORA(uint16_t address, bool isImmediate) { if (GetAccumulatorSize()) { A |= isImmediate ? address : memory.ReadByte(address); SetZeroFlag(A == 0); diff --git a/src/app/emu/cpu.h b/src/app/emu/cpu.h index 477f077d..68e624d8 100644 --- a/src/app/emu/cpu.h +++ b/src/app/emu/cpu.h @@ -74,8 +74,7 @@ const int kCpuClockSpeed = 21477272; // 21.477272 MHz class CPU : public Memory, public Loggable { public: - explicit CPU(Memory& mem, VirtualClock& vclock) - : memory(mem), clock(vclock) {} + explicit CPU(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {} void Init() { clock.SetFrequency(kCpuClockSpeed); @@ -698,7 +697,7 @@ class CPU : public Memory, public Loggable { uint8_t at(int i) const override { return 0; } Memory& memory; - VirtualClock& clock; + Clock& clock; }; } // namespace emu diff --git a/src/app/emu/dbg.h b/src/app/emu/debug/debugger.h similarity index 84% rename from src/app/emu/dbg.h rename to src/app/emu/debug/debugger.h index c4441029..61c40f84 100644 --- a/src/app/emu/dbg.h +++ b/src/app/emu/debug/debugger.h @@ -1,9 +1,9 @@ -#ifndef YAZE_APP_EMU_DBG_H_ -#define YAZE_APP_EMU_DBG_H_ +#ifndef YAZE_APP_EMU_DEBUG_DEBUGGER_H_ +#define YAZE_APP_EMU_DEBUG_DEBUGGER_H_ -#include "app/emu/apu.h" +#include "app/emu/audio/apu.h" #include "app/emu/cpu.h" -#include "app/emu/ppu.h" +#include "app/emu/video/ppu.h" namespace yaze { namespace app { @@ -11,7 +11,7 @@ namespace emu { class Debugger { public: - Debugger()=default; + Debugger() = default; // Attach the debugger to the emulator // Debugger(CPU &cpu, PPU &ppu, APU &apu); diff --git a/src/app/emu/snes.cc b/src/app/emu/snes.cc index 4f0d11fe..dc0382b6 100644 --- a/src/app/emu/snes.cc +++ b/src/app/emu/snes.cc @@ -1,14 +1,20 @@ -#include "snes.h" +#include "app/emu/snes.h" + +#include #include #include #include #include -#include "app/emu/apu.h" +#include "app/emu/audio/apu.h" +#include "app/emu/audio/spc700.h" +#include "app/emu/clock.h" #include "app/emu/cpu.h" +#include "app/emu/debug/debugger.h" #include "app/emu/mem.h" -#include "app/emu/ppu.h" +#include "app/emu/video/ppu.h" +#include "app/rom.h" namespace yaze { namespace app { @@ -40,6 +46,16 @@ uint16_t GetHeaderOffset(const Memory& memory) { return offset; } +void audio_callback(void* userdata, uint8_t* stream, int len) { + auto* apu = static_cast(userdata); + auto* buffer = reinterpret_cast(stream); + + for (int i = 0; i < len / 2; i++) { // Assuming 16-bit samples + buffer[i] = apu->GetNextSample(); // This function should be implemented in + // APU to fetch the next sample + } +} + } // namespace void DMA::StartDMATransfer(uint8_t channelMask) { @@ -180,6 +196,9 @@ void SNES::Init(ROM& rom) { // Initialize APU apu.Init(); + // Initialize SDL_Mixer to play the audio samples + Mix_HookMusic(audio_callback, &apu); + // Disable interrupts and rendering memory_.WriteByte(0x4200, 0x00); // NMITIMEN memory_.WriteByte(0x420C, 0x00); // HDMAEN diff --git a/src/app/emu/snes.h b/src/app/emu/snes.h index 7f35722f..da250b20 100644 --- a/src/app/emu/snes.h +++ b/src/app/emu/snes.h @@ -1,12 +1,12 @@ #include #include -#include "app/emu/apu.h" +#include "app/emu/audio/apu.h" +#include "app/emu/audio/spc700.h" #include "app/emu/clock.h" #include "app/emu/cpu.h" -#include "app/emu/dbg.h" -#include "app/emu/ppu.h" -#include "app/emu/spc700.h" +#include "app/emu/debug/debugger.h" +#include "app/emu/video/ppu.h" #include "app/rom.h" namespace yaze { @@ -104,8 +104,8 @@ class SNES : public DMA { // Components of the SNES MemoryImpl memory_; - Clock clock_; - AudioRAM audio_ram_; + ClockImpl clock_; + AudioRamImpl audio_ram_; CPU cpu{memory_, clock_}; PPU ppu{memory_, clock_}; diff --git a/src/app/emu/ppu.cc b/src/app/emu/video/ppu.cc similarity index 87% rename from src/app/emu/ppu.cc rename to src/app/emu/video/ppu.cc index 8bb25ea9..276eed6f 100644 --- a/src/app/emu/ppu.cc +++ b/src/app/emu/video/ppu.cc @@ -1,4 +1,4 @@ -#include "app/emu/ppu.h" +#include "app/emu/video/ppu.h" #include #include @@ -155,7 +155,31 @@ void PPU::UpdateModeSettings() { } void PPU::RenderBackground(int layer) { - // ... + switch (layer) { + case 1: + // // Render the first background layer + // auto bg1_tilemap_info = + // PPURegisters::BGSC(ReadVRAM(PPURegisters::BG1SC)); auto bg1_chr_data = + // PPURegisters::BGNBA(ReadVRAM(PPURegisters::BG12NBA)); break; + // case 2: + // // Render the second background layer + // auto bg2_tilemap_info = + // PPURegisters::BGSC(ReadVRAM(PPURegisters::BG2SC)); auto bg2_chr_data = + // PPURegisters::BGNBA(ReadVRAM(PPURegisters::BG12NBA)); break; + // case 3: + // // Render the third background layer + // auto bg3_tilemap_info = + // PPURegisters::BGSC(ReadVRAM(PPURegisters::BG3SC)); auto bg3_chr_data = + // PPURegisters::BGNBA(ReadVRAM(PPURegisters::BG34NBA)); break; + // case 4: + // // Render the fourth background layer + // auto bg4_tilemap_info = + // PPURegisters::BGSC(ReadVRAM(PPURegisters::BG4SC)); auto bg4_chr_data = + // PPURegisters::BGNBA(ReadVRAM(PPURegisters::BG34NBA)); break; + default: + // Invalid layer, do nothing + break; + } } void PPU::RenderSprites() { diff --git a/src/app/emu/ppu.h b/src/app/emu/video/ppu.h similarity index 98% rename from src/app/emu/ppu.h rename to src/app/emu/video/ppu.h index c8ab89a8..e4ad214b 100644 --- a/src/app/emu/ppu.h +++ b/src/app/emu/video/ppu.h @@ -635,18 +635,20 @@ struct BackgroundLayer { const int kPpuClockSpeed = 5369318; // 5.369318 MHz -class PPU : public Clock, public Observer { +class PPU : public Observer { public: // Initializes the PPU with the necessary resources and dependencies - PPU(Memory& memory, VirtualClock& clock) : memory_(memory), clock_(clock) {} + PPU(Memory& memory, Clock& clock) : memory_(memory), clock_(clock) {} void Init() { // Initialize the frame buffer with a size that corresponds to the // screen resolution - SetFrequency(kPpuClockSpeed); + clock_.SetFrequency(kPpuClockSpeed); frame_buffer_.resize(256 * 240, 0); } + void UpdateClock(double delta_time) { clock_.UpdateClock(delta_time); } + // Resets the PPU to its initial state void Reset() { std::fill(frame_buffer_.begin(), frame_buffer_.end(), 0); } @@ -725,7 +727,7 @@ class PPU : public Clock, public Observer { // =========================================================== // Member variables to store internal PPU state and resources Memory& memory_; - VirtualClock& clock_; + Clock& clock_; std::vector frame_buffer_; Tilemap tilemap_; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1ec108be..13493b56 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,12 +15,17 @@ add_executable( yaze_test.cc z3ed_test.cc cpu_test.cc + spc700_test.cc ../src/cli/patch.cc ../src/cli/command_handler.cc compression_test.cc snes_palette_test.cc ../src/app/rom.cc ../src/app/emu/cpu.cc + ../src/app/emu/audio/apu.cc + ../src/app/emu/video/ppu.cc + ../src/app/emu/audio/dsp.cc + ../src/app/emu/audio/spc700.cc ../src/app/gfx/bitmap.cc ../src/app/gfx/snes_tile.cc ../src/app/gfx/snes_palette.cc diff --git a/test/cpu_test.cc b/test/cpu_test.cc index 0660b292..94cb2300 100644 --- a/test/cpu_test.cc +++ b/test/cpu_test.cc @@ -10,7 +10,7 @@ namespace yaze { namespace app { namespace emu { -class MockClock : public VirtualClock { +class MockClock : public Clock { public: MOCK_METHOD(void, UpdateClock, (double delta), (override)); MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override)); @@ -150,6 +150,24 @@ class CPUTest : public ::testing::Test { using ::testing::_; using ::testing::Return; +TEST_F(CPUTest, CPURunTest) { + // Arrange + EXPECT_CALL(mock_clock, UpdateClock(_)).Times(1); + EXPECT_CALL(mock_clock, GetCycleCount()).WillOnce(Return(1)); + EXPECT_CALL(mock_clock, ResetAccumulatedTime()).Times(1); + + mock_memory.InsertMemory(0x00, {0x69, 0x01, 0xEA, 0xEA}); + + // Act + mock_clock.UpdateClock(1.0); + cpu.Update(); + mock_clock.ResetAccumulatedTime(); + + // Assert + // Google Mock will automatically check the expectations + ASSERT_THAT(cpu.A, ::testing::Eq(0x01)); +} + // ============================================================================ // Infrastructure // ============================================================================ diff --git a/test/spc700_test.cc b/test/spc700_test.cc new file mode 100644 index 00000000..d5a218cf --- /dev/null +++ b/test/spc700_test.cc @@ -0,0 +1,26 @@ +#include "app/emu/audio/spc700.h" + +#include +#include + +namespace yaze { +namespace app { +namespace emu { + +class MockAudioRAM : public AudioRam { + public: + MOCK_METHOD(uint8_t, read, (uint16_t address), (const, override)); + MOCK_METHOD(void, write, (uint16_t address, uint8_t value), (override)); +}; + +class SPC700Test : public ::testing::Test { + protected: + SPC700Test() = default; + + MockAudioRAM audioRAM; + SPC700 spc700{audioRAM}; +}; + +} // namespace emu +} // namespace app +} // namespace yaze