Reorganize emu folder, update S-SMP system infra
This commit is contained in:
@@ -1,75 +0,0 @@
|
|||||||
#include "app/emu/apu.h"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <iostream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#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<int16_t>& 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
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#ifndef YAZE_APP_EMU_APU_H_
|
|
||||||
#define YAZE_APP_EMU_APU_H_
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#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<int16_t> &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<int16_t> audioSamples_;
|
|
||||||
// Other state variables (registers, counters, channel settings, etc.)
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace emu
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
125
src/app/emu/audio/apu.cc
Normal file
125
src/app/emu/audio/apu.cc
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include "app/emu/audio/apu.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<int16_t>& 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
|
||||||
192
src/app/emu/audio/apu.h
Normal file
192
src/app/emu/audio/apu.h
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
#ifndef YAZE_APP_EMU_APU_H_
|
||||||
|
#define YAZE_APP_EMU_APU_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<int16_t> &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<uint16_t>(ports_[2]) |
|
||||||
|
(static_cast<uint16_t>(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<int16_t> audioSamples_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
281
src/app/emu/audio/dsp.cc
Normal file
281
src/app/emu/audio/dsp.cc
Normal file
@@ -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<int8_t>(value);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
voices_[voice].vol_right = static_cast<int8_t>(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<int16_t>(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
|
||||||
315
src/app/emu/audio/dsp.h
Normal file
315
src/app/emu/audio/dsp.h
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
#ifndef YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||||
|
#define YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/emu/mem.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace emu {
|
||||||
|
|
||||||
|
using SampleFetcher = std::function<uint8_t(uint16_t)>;
|
||||||
|
using SamplePusher = std::function<void(int16_t)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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<uint8_t> globalRegs = std::vector<uint8_t>(kNumGlobalRegs, 0x00);
|
||||||
|
|
||||||
|
static const uint16_t ENVELOPE_MAX = 2047; // $7FF
|
||||||
|
|
||||||
|
// Attack times in ms
|
||||||
|
const std::vector<uint32_t> attackTimes = {
|
||||||
|
4100, 2600, 1500, 1000, 640, 380, 260, 160, 96, 64, 40, 24, 16, 10, 6, 0};
|
||||||
|
|
||||||
|
// Decay times in ms
|
||||||
|
const std::vector<uint32_t> decayTimes = {1200, 740, 440, 290,
|
||||||
|
180, 110, 74, 37};
|
||||||
|
|
||||||
|
// Release times in ms
|
||||||
|
const std::vector<uint32_t> 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<std::vector<uint32_t>> 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<std::vector<uint16_t>> DspPeriodTable = {
|
||||||
|
// ... Your DSP period table here ...
|
||||||
|
};
|
||||||
|
|
||||||
|
// DSP Period Offset
|
||||||
|
const std::vector<uint16_t> 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<uint8_t(uint16_t)> fetcher);
|
||||||
|
void SetSamplePusher(std::function<void(int16_t)> 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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "app/emu/spc700.h"
|
#include "app/emu/audio/spc700.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -7,6 +7,8 @@ namespace yaze {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
|
||||||
|
void SPC700::Reset() {}
|
||||||
|
|
||||||
void SPC700::ExecuteInstructions(uint8_t opcode) {
|
void SPC700::ExecuteInstructions(uint8_t opcode) {
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
// 8-bit Move Memory to Register
|
// 8-bit Move Memory to Register
|
||||||
@@ -9,9 +9,9 @@ namespace yaze {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
|
||||||
class VirtualAudioRAM {
|
class AudioRam {
|
||||||
public:
|
public:
|
||||||
virtual ~VirtualAudioRAM() = default;
|
virtual ~AudioRam() = default;
|
||||||
|
|
||||||
// Read a byte from ARAM at the given address
|
// Read a byte from ARAM at the given address
|
||||||
virtual uint8_t read(uint16_t address) const = 0;
|
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;
|
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
|
static const size_t ARAM_SIZE = 64 * 1024; // 64 KB
|
||||||
std::vector<uint8_t> ram = std::vector<uint8_t>(ARAM_SIZE, 0);
|
std::vector<uint8_t> ram = std::vector<uint8_t>(ARAM_SIZE, 0);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AudioRAM() = default;
|
AudioRamImpl() = default;
|
||||||
|
|
||||||
// Read a byte from ARAM at the given address
|
// Read a byte from ARAM at the given address
|
||||||
uint8_t read(uint16_t address) const override {
|
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<std::vector<uint8_t>> voices = std::vector<std::vector<uint8_t>>(
|
|
||||||
NUM_VOICES, std::vector<uint8_t>(NUM_VOICE_REGS, 0));
|
|
||||||
|
|
||||||
// Global registers
|
|
||||||
std::vector<uint8_t> globalRegs = std::vector<uint8_t>(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 {
|
class SPC700 {
|
||||||
private:
|
private:
|
||||||
VirtualAudioRAM& aram_;
|
AudioRam& aram_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SPC700(VirtualAudioRAM& aram) : aram_(aram) {}
|
explicit SPC700(AudioRam& aram) : aram_(aram) {}
|
||||||
DigitalSignalProcessor sdsp;
|
|
||||||
uint8_t test_register_;
|
uint8_t test_register_;
|
||||||
uint8_t control_register_;
|
uint8_t control_register_;
|
||||||
uint8_t dsp_address_register_;
|
uint8_t dsp_address_register_;
|
||||||
@@ -107,6 +68,8 @@ class SPC700 {
|
|||||||
};
|
};
|
||||||
Flags PSW; // Processor status word
|
Flags PSW; // Processor status word
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
|
||||||
void ExecuteInstructions(uint8_t opcode);
|
void ExecuteInstructions(uint8_t opcode);
|
||||||
|
|
||||||
// Read a byte from the memory-mapped registers
|
// Read a byte from the memory-mapped registers
|
||||||
@@ -118,8 +81,6 @@ class SPC700 {
|
|||||||
return control_register_;
|
return control_register_;
|
||||||
case 0xF2:
|
case 0xF2:
|
||||||
return dsp_address_register_;
|
return dsp_address_register_;
|
||||||
case 0xF3:
|
|
||||||
return sdsp.ReadGlobalReg(dsp_address_register_);
|
|
||||||
default:
|
default:
|
||||||
if (address < 0xFFC0) {
|
if (address < 0xFFC0) {
|
||||||
return aram_.read(address);
|
return aram_.read(address);
|
||||||
@@ -142,9 +103,6 @@ class SPC700 {
|
|||||||
case 0xF2:
|
case 0xF2:
|
||||||
dsp_address_register_ = value;
|
dsp_address_register_ = value;
|
||||||
break;
|
break;
|
||||||
case 0xF3:
|
|
||||||
sdsp.WriteGlobalReg(dsp_address_register_, value);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
if (address < 0xFFC0) {
|
if (address < 0xFFC0) {
|
||||||
aram_.write(address, value);
|
aram_.write(address, value);
|
||||||
@@ -7,9 +7,9 @@ namespace yaze {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
|
||||||
class VirtualClock {
|
class Clock {
|
||||||
public:
|
public:
|
||||||
virtual ~VirtualClock() = default;
|
virtual ~Clock() = default;
|
||||||
virtual void UpdateClock(double delta) = 0;
|
virtual void UpdateClock(double delta) = 0;
|
||||||
virtual unsigned long long GetCycleCount() const = 0;
|
virtual unsigned long long GetCycleCount() const = 0;
|
||||||
virtual void ResetAccumulatedTime() = 0;
|
virtual void ResetAccumulatedTime() = 0;
|
||||||
@@ -17,10 +17,10 @@ class VirtualClock {
|
|||||||
virtual float GetFrequency() const = 0;
|
virtual float GetFrequency() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Clock : public VirtualClock {
|
class ClockImpl : public Clock {
|
||||||
public:
|
public:
|
||||||
Clock() = default;
|
ClockImpl() = default;
|
||||||
virtual ~Clock() = default;
|
virtual ~ClockImpl() = default;
|
||||||
|
|
||||||
void UpdateCycleCount(double deltaTime) {
|
void UpdateCycleCount(double deltaTime) {
|
||||||
accumulatedTime += deltaTime;
|
accumulatedTime += deltaTime;
|
||||||
|
|||||||
@@ -1176,6 +1176,7 @@ void CPU::ExecuteInstruction(uint8_t opcode) {
|
|||||||
|
|
||||||
void CPU::HandleInterrupts() {}
|
void CPU::HandleInterrupts() {}
|
||||||
|
|
||||||
|
// ADC: Add with carry
|
||||||
void CPU::ADC(uint8_t operand) {
|
void CPU::ADC(uint8_t operand) {
|
||||||
bool C = GetCarryFlag();
|
bool C = GetCarryFlag();
|
||||||
if (GetAccumulatorSize()) { // 8-bit mode
|
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) {
|
void CPU::AND(uint16_t value, bool isImmediate) {
|
||||||
uint16_t operand;
|
uint16_t operand;
|
||||||
if (E == 0) { // 16-bit mode
|
if (E == 0) { // 16-bit mode
|
||||||
@@ -1232,6 +1234,7 @@ void CPU::ANDAbsoluteLong(uint32_t address) {
|
|||||||
SetNegativeFlag(A & 0x80000000);
|
SetNegativeFlag(A & 0x80000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ASL: Arithmetic shift left
|
||||||
void CPU::ASL(uint16_t address) {
|
void CPU::ASL(uint16_t address) {
|
||||||
uint8_t value = memory.ReadByte(address);
|
uint8_t value = memory.ReadByte(address);
|
||||||
SetCarryFlag(!(value & 0x80)); // Set carry flag if bit 7 is set
|
SetCarryFlag(!(value & 0x80)); // Set carry flag if bit 7 is set
|
||||||
@@ -1242,6 +1245,7 @@ void CPU::ASL(uint16_t address) {
|
|||||||
SetZeroFlag(value);
|
SetZeroFlag(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BCC: Branch if carry clear
|
||||||
void CPU::BCC(int8_t offset) {
|
void CPU::BCC(int8_t offset) {
|
||||||
if (!GetCarryFlag()) { // If the carry flag is clear
|
if (!GetCarryFlag()) { // If the carry flag is clear
|
||||||
PC += offset; // Add the offset to the program counter
|
PC += offset; // Add the offset to the program counter
|
||||||
@@ -1520,7 +1524,7 @@ void CPU::JSL(uint32_t address) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LDA: Load accumulator
|
// LDA: Load accumulator
|
||||||
void CPU::LDA(uint16_t address, bool isImmediate ) {
|
void CPU::LDA(uint16_t address, bool isImmediate) {
|
||||||
if (GetAccumulatorSize()) {
|
if (GetAccumulatorSize()) {
|
||||||
A = isImmediate ? address : memory.ReadByte(address);
|
A = isImmediate ? address : memory.ReadByte(address);
|
||||||
SetZeroFlag(A == 0);
|
SetZeroFlag(A == 0);
|
||||||
@@ -1533,7 +1537,7 @@ void CPU::LDA(uint16_t address, bool isImmediate ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LDX: Load X register
|
// LDX: Load X register
|
||||||
void CPU::LDX(uint16_t address, bool isImmediate ) {
|
void CPU::LDX(uint16_t address, bool isImmediate) {
|
||||||
if (GetIndexSize()) {
|
if (GetIndexSize()) {
|
||||||
X = isImmediate ? address : memory.ReadByte(address);
|
X = isImmediate ? address : memory.ReadByte(address);
|
||||||
SetZeroFlag(X == 0);
|
SetZeroFlag(X == 0);
|
||||||
@@ -1546,7 +1550,7 @@ void CPU::LDX(uint16_t address, bool isImmediate ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LDY: Load Y register
|
// LDY: Load Y register
|
||||||
void CPU::LDY(uint16_t address, bool isImmediate ) {
|
void CPU::LDY(uint16_t address, bool isImmediate) {
|
||||||
if (GetIndexSize()) {
|
if (GetIndexSize()) {
|
||||||
Y = isImmediate ? address : memory.ReadByte(address);
|
Y = isImmediate ? address : memory.ReadByte(address);
|
||||||
SetZeroFlag(Y == 0);
|
SetZeroFlag(Y == 0);
|
||||||
@@ -1577,7 +1581,7 @@ void CPU::NOP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ORA: Logical OR
|
// ORA: Logical OR
|
||||||
void CPU::ORA(uint16_t address, bool isImmediate ) {
|
void CPU::ORA(uint16_t address, bool isImmediate) {
|
||||||
if (GetAccumulatorSize()) {
|
if (GetAccumulatorSize()) {
|
||||||
A |= isImmediate ? address : memory.ReadByte(address);
|
A |= isImmediate ? address : memory.ReadByte(address);
|
||||||
SetZeroFlag(A == 0);
|
SetZeroFlag(A == 0);
|
||||||
|
|||||||
@@ -74,8 +74,7 @@ const int kCpuClockSpeed = 21477272; // 21.477272 MHz
|
|||||||
|
|
||||||
class CPU : public Memory, public Loggable {
|
class CPU : public Memory, public Loggable {
|
||||||
public:
|
public:
|
||||||
explicit CPU(Memory& mem, VirtualClock& vclock)
|
explicit CPU(Memory& mem, Clock& vclock) : memory(mem), clock(vclock) {}
|
||||||
: memory(mem), clock(vclock) {}
|
|
||||||
|
|
||||||
void Init() {
|
void Init() {
|
||||||
clock.SetFrequency(kCpuClockSpeed);
|
clock.SetFrequency(kCpuClockSpeed);
|
||||||
@@ -698,7 +697,7 @@ class CPU : public Memory, public Loggable {
|
|||||||
uint8_t at(int i) const override { return 0; }
|
uint8_t at(int i) const override { return 0; }
|
||||||
|
|
||||||
Memory& memory;
|
Memory& memory;
|
||||||
VirtualClock& clock;
|
Clock& clock;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace emu
|
} // namespace emu
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#ifndef YAZE_APP_EMU_DBG_H_
|
#ifndef YAZE_APP_EMU_DEBUG_DEBUGGER_H_
|
||||||
#define YAZE_APP_EMU_DBG_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/cpu.h"
|
||||||
#include "app/emu/ppu.h"
|
#include "app/emu/video/ppu.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
@@ -11,7 +11,7 @@ namespace emu {
|
|||||||
|
|
||||||
class Debugger {
|
class Debugger {
|
||||||
public:
|
public:
|
||||||
Debugger()=default;
|
Debugger() = default;
|
||||||
// Attach the debugger to the emulator
|
// Attach the debugger to the emulator
|
||||||
// Debugger(CPU &cpu, PPU &ppu, APU &apu);
|
// Debugger(CPU &cpu, PPU &ppu, APU &apu);
|
||||||
|
|
||||||
@@ -1,14 +1,20 @@
|
|||||||
#include "snes.h"
|
#include "app/emu/snes.h"
|
||||||
|
|
||||||
|
#include <SDL_mixer.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#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/cpu.h"
|
||||||
|
#include "app/emu/debug/debugger.h"
|
||||||
#include "app/emu/mem.h"
|
#include "app/emu/mem.h"
|
||||||
#include "app/emu/ppu.h"
|
#include "app/emu/video/ppu.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
@@ -40,6 +46,16 @@ uint16_t GetHeaderOffset(const Memory& memory) {
|
|||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void audio_callback(void* userdata, uint8_t* stream, int len) {
|
||||||
|
auto* apu = static_cast<APU*>(userdata);
|
||||||
|
auto* buffer = reinterpret_cast<int16_t*>(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
|
} // namespace
|
||||||
|
|
||||||
void DMA::StartDMATransfer(uint8_t channelMask) {
|
void DMA::StartDMATransfer(uint8_t channelMask) {
|
||||||
@@ -180,6 +196,9 @@ void SNES::Init(ROM& rom) {
|
|||||||
// Initialize APU
|
// Initialize APU
|
||||||
apu.Init();
|
apu.Init();
|
||||||
|
|
||||||
|
// Initialize SDL_Mixer to play the audio samples
|
||||||
|
Mix_HookMusic(audio_callback, &apu);
|
||||||
|
|
||||||
// Disable interrupts and rendering
|
// Disable interrupts and rendering
|
||||||
memory_.WriteByte(0x4200, 0x00); // NMITIMEN
|
memory_.WriteByte(0x4200, 0x00); // NMITIMEN
|
||||||
memory_.WriteByte(0x420C, 0x00); // HDMAEN
|
memory_.WriteByte(0x420C, 0x00); // HDMAEN
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#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/clock.h"
|
||||||
#include "app/emu/cpu.h"
|
#include "app/emu/cpu.h"
|
||||||
#include "app/emu/dbg.h"
|
#include "app/emu/debug/debugger.h"
|
||||||
#include "app/emu/ppu.h"
|
#include "app/emu/video/ppu.h"
|
||||||
#include "app/emu/spc700.h"
|
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
@@ -104,8 +104,8 @@ class SNES : public DMA {
|
|||||||
|
|
||||||
// Components of the SNES
|
// Components of the SNES
|
||||||
MemoryImpl memory_;
|
MemoryImpl memory_;
|
||||||
Clock clock_;
|
ClockImpl clock_;
|
||||||
AudioRAM audio_ram_;
|
AudioRamImpl audio_ram_;
|
||||||
|
|
||||||
CPU cpu{memory_, clock_};
|
CPU cpu{memory_, clock_};
|
||||||
PPU ppu{memory_, clock_};
|
PPU ppu{memory_, clock_};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "app/emu/ppu.h"
|
#include "app/emu/video/ppu.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -155,7 +155,31 @@ void PPU::UpdateModeSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PPU::RenderBackground(int layer) {
|
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() {
|
void PPU::RenderSprites() {
|
||||||
@@ -635,18 +635,20 @@ struct BackgroundLayer {
|
|||||||
|
|
||||||
const int kPpuClockSpeed = 5369318; // 5.369318 MHz
|
const int kPpuClockSpeed = 5369318; // 5.369318 MHz
|
||||||
|
|
||||||
class PPU : public Clock, public Observer {
|
class PPU : public Observer {
|
||||||
public:
|
public:
|
||||||
// Initializes the PPU with the necessary resources and dependencies
|
// 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() {
|
void Init() {
|
||||||
// Initialize the frame buffer with a size that corresponds to the
|
// Initialize the frame buffer with a size that corresponds to the
|
||||||
// screen resolution
|
// screen resolution
|
||||||
SetFrequency(kPpuClockSpeed);
|
clock_.SetFrequency(kPpuClockSpeed);
|
||||||
frame_buffer_.resize(256 * 240, 0);
|
frame_buffer_.resize(256 * 240, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateClock(double delta_time) { clock_.UpdateClock(delta_time); }
|
||||||
|
|
||||||
// Resets the PPU to its initial state
|
// Resets the PPU to its initial state
|
||||||
void Reset() { std::fill(frame_buffer_.begin(), frame_buffer_.end(), 0); }
|
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
|
// Member variables to store internal PPU state and resources
|
||||||
Memory& memory_;
|
Memory& memory_;
|
||||||
VirtualClock& clock_;
|
Clock& clock_;
|
||||||
std::vector<uint8_t> frame_buffer_;
|
std::vector<uint8_t> frame_buffer_;
|
||||||
|
|
||||||
Tilemap tilemap_;
|
Tilemap tilemap_;
|
||||||
@@ -15,12 +15,17 @@ add_executable(
|
|||||||
yaze_test.cc
|
yaze_test.cc
|
||||||
z3ed_test.cc
|
z3ed_test.cc
|
||||||
cpu_test.cc
|
cpu_test.cc
|
||||||
|
spc700_test.cc
|
||||||
../src/cli/patch.cc
|
../src/cli/patch.cc
|
||||||
../src/cli/command_handler.cc
|
../src/cli/command_handler.cc
|
||||||
compression_test.cc
|
compression_test.cc
|
||||||
snes_palette_test.cc
|
snes_palette_test.cc
|
||||||
../src/app/rom.cc
|
../src/app/rom.cc
|
||||||
../src/app/emu/cpu.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/bitmap.cc
|
||||||
../src/app/gfx/snes_tile.cc
|
../src/app/gfx/snes_tile.cc
|
||||||
../src/app/gfx/snes_palette.cc
|
../src/app/gfx/snes_palette.cc
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace yaze {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
|
||||||
class MockClock : public VirtualClock {
|
class MockClock : public Clock {
|
||||||
public:
|
public:
|
||||||
MOCK_METHOD(void, UpdateClock, (double delta), (override));
|
MOCK_METHOD(void, UpdateClock, (double delta), (override));
|
||||||
MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override));
|
MOCK_METHOD(unsigned long long, GetCycleCount, (), (const, override));
|
||||||
@@ -150,6 +150,24 @@ class CPUTest : public ::testing::Test {
|
|||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::Return;
|
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
|
// Infrastructure
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
26
test/spc700_test.cc
Normal file
26
test/spc700_test.cc
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "app/emu/audio/spc700.h"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user