Reorganize emu folder, update S-SMP system infra
This commit is contained in:
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
|
||||
533
src/app/emu/audio/spc700.cc
Normal file
533
src/app/emu/audio/spc700.cc
Normal file
@@ -0,0 +1,533 @@
|
||||
#include "app/emu/audio/spc700.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
void SPC700::Reset() {}
|
||||
|
||||
void SPC700::ExecuteInstructions(uint8_t opcode) {
|
||||
switch (opcode) {
|
||||
// 8-bit Move Memory to Register
|
||||
case 0xE8: // MOV A, #imm
|
||||
MOV(A, imm());
|
||||
break;
|
||||
case 0xE6: // MOV A, (X)
|
||||
MOV(A, X);
|
||||
break;
|
||||
case 0xBF: // MOV A, (X)+
|
||||
MOV(A, X);
|
||||
X++;
|
||||
break;
|
||||
case 0xE4: // MOV A, dp
|
||||
MOV(A, dp());
|
||||
break;
|
||||
case 0xF4: // MOV A, dp+X
|
||||
MOV(A, dp_plus_x());
|
||||
break;
|
||||
case 0xE5: // MOV A, !abs
|
||||
MOV(A, abs());
|
||||
break;
|
||||
case 0xF5: // MOV A, !abs+X
|
||||
MOV(A, abs() + X);
|
||||
break;
|
||||
case 0xF6: // MOV A, !abs+Y
|
||||
MOV(A, abs() + Y);
|
||||
break;
|
||||
case 0xE7: // MOV A, [dp+X]
|
||||
MOV(A, read(dp_plus_x_indirect()));
|
||||
break;
|
||||
case 0xF7: // MOV A, [dp]+Y
|
||||
MOV(A, read(dp_indirect_plus_y()));
|
||||
break;
|
||||
case 0xCD: // MOV X, #imm
|
||||
MOV(X, imm());
|
||||
break;
|
||||
case 0xF8: // MOV X, dp
|
||||
MOV(X, dp());
|
||||
break;
|
||||
case 0xF9: // MOV X, dp+Y
|
||||
MOV(X, dp_plus_y());
|
||||
break;
|
||||
case 0xE9: // MOV X, !abs
|
||||
MOV(X, abs());
|
||||
break;
|
||||
case 0x8D: // MOV Y, #imm
|
||||
MOV(Y, imm());
|
||||
break;
|
||||
case 0xEB: // MOV Y, dp
|
||||
MOV(Y, dp());
|
||||
break;
|
||||
case 0xFB: // MOV Y, dp+X
|
||||
MOV(Y, dp_plus_x());
|
||||
break;
|
||||
case 0xEC: // MOV Y, !abs
|
||||
MOV(Y, abs());
|
||||
break;
|
||||
|
||||
// 8-bit move register to memory
|
||||
|
||||
case 0xC6: // MOV (X), A
|
||||
break;
|
||||
case 0xAF: // MOV (X)+, A
|
||||
break;
|
||||
case 0xC4: // MOV dp, A
|
||||
MOV_ADDR(get_dp_addr(), A);
|
||||
break;
|
||||
case 0xD4: // MOV dp+X, A
|
||||
MOV_ADDR(get_dp_addr() + X, A);
|
||||
break;
|
||||
case 0xC5: // MOV !abs, A
|
||||
MOV_ADDR(abs(), A);
|
||||
break;
|
||||
case 0xD5: // MOV !abs+X, A
|
||||
MOV_ADDR(abs() + X, A);
|
||||
break;
|
||||
case 0xD6: // MOV !abs+Y, A
|
||||
MOV_ADDR(abs() + Y, A);
|
||||
break;
|
||||
case 0xC7: // MOV [dp+X], A
|
||||
MOV_ADDR(dp_plus_x_indirect(), A);
|
||||
break;
|
||||
case 0xD7: // MOV [dp]+Y, A
|
||||
MOV_ADDR(dp_indirect_plus_y(), A);
|
||||
break;
|
||||
case 0xD8: // MOV dp, X
|
||||
MOV_ADDR(get_dp_addr(), X);
|
||||
break;
|
||||
case 0xD9: // MOV dp+Y, X
|
||||
MOV_ADDR(get_dp_addr() + Y, X);
|
||||
break;
|
||||
case 0xC9: // MOV !abs, X
|
||||
MOV_ADDR(abs(), X);
|
||||
break;
|
||||
case 0xCB: // MOV dp, Y
|
||||
MOV_ADDR(get_dp_addr(), Y);
|
||||
break;
|
||||
case 0xDB: // MOV dp+X, Y
|
||||
MOV_ADDR(get_dp_addr() + X, Y);
|
||||
break;
|
||||
case 0xCC: // MOV !abs, Y
|
||||
MOV_ADDR(abs(), Y);
|
||||
break;
|
||||
|
||||
// . 8-bit move register to register / special direct page moves
|
||||
|
||||
case 0x7D: // MOV A, X
|
||||
break;
|
||||
case 0xDD: // MOV A, Y
|
||||
break;
|
||||
case 0x5D: // MOV X, A
|
||||
break;
|
||||
case 0xFD: // MOV Y, A
|
||||
break;
|
||||
case 0x9D: // MOV X, SP
|
||||
break;
|
||||
case 0xBD: // MOV SP, X
|
||||
break;
|
||||
case 0xFA: // MOV dp, dp
|
||||
break;
|
||||
case 0x8F: // MOV dp, #imm
|
||||
break;
|
||||
|
||||
// . 8-bit arithmetic
|
||||
|
||||
case 0x88: // ADC A, #imm
|
||||
ADC(A, imm());
|
||||
break;
|
||||
case 0x86: // ADC A, (X)
|
||||
break;
|
||||
case 0x84: // ADC A, dp
|
||||
ADC(A, dp());
|
||||
break;
|
||||
case 0x94: // ADC A, dp+X
|
||||
ADC(A, dp_plus_x());
|
||||
break;
|
||||
case 0x85: // ADC A, !abs
|
||||
ADC(A, abs());
|
||||
break;
|
||||
case 0x95: // ADC A, !abs+X
|
||||
break;
|
||||
case 0x96: // ADC A, !abs+Y
|
||||
break;
|
||||
case 0x87: // ADC A, [dp+X]
|
||||
ADC(A, dp_plus_x_indirect());
|
||||
break;
|
||||
case 0x97: // ADC A, [dp]+Y
|
||||
ADC(A, dp_indirect_plus_y());
|
||||
break;
|
||||
case 0x99: // ADC (X), (Y)
|
||||
break;
|
||||
case 0x89: // ADC dp, dp
|
||||
break;
|
||||
case 0x98: // ADC dp, #imm
|
||||
break;
|
||||
case 0xA8: // SBC A, #imm
|
||||
SBC(A, imm());
|
||||
break;
|
||||
case 0xA6: // SBC A, (X)
|
||||
break;
|
||||
case 0xA4: // SBC A, dp
|
||||
break;
|
||||
case 0xB4: // SBC A, dp+X
|
||||
break;
|
||||
case 0xA5: // SBC A, !abs
|
||||
break;
|
||||
case 0xB5: // SBC A, !abs+X
|
||||
break;
|
||||
case 0xB6: // SBC A, !abs+Y
|
||||
break;
|
||||
case 0xA7: // SBC A, [dp+X]
|
||||
break;
|
||||
case 0xB7: // SBC A, [dp]+Y
|
||||
break;
|
||||
case 0xB9: // SBC (X), (Y)
|
||||
break;
|
||||
case 0xA9: // SBC dp, dp
|
||||
break;
|
||||
case 0xB8: // SBC dp, #imm
|
||||
break;
|
||||
case 0x68: // CMP A, #imm
|
||||
break;
|
||||
case 0x66: // CMP A, (X)
|
||||
break;
|
||||
case 0x64: // CMP A, dp
|
||||
break;
|
||||
case 0x74: // CMP A, dp+X
|
||||
break;
|
||||
case 0x65: // CMP A, !abs
|
||||
break;
|
||||
case 0x75: // CMP A, !abs+X
|
||||
break;
|
||||
case 0x76: // CMP A, !abs+Y
|
||||
break;
|
||||
case 0x67: // CMP A, [dp+X]
|
||||
break;
|
||||
case 0x77: // CMP A, [dp]+Y
|
||||
break;
|
||||
case 0x79: // CMP (X), (Y)
|
||||
break;
|
||||
case 0x69: // CMP dp, dp
|
||||
break;
|
||||
case 0x78: // CMP dp, #imm
|
||||
break;
|
||||
case 0xC8: // CMP X, #imm
|
||||
break;
|
||||
case 0x3E: // CMP X, dp
|
||||
break;
|
||||
case 0x1E: // CMP X, !abs
|
||||
break;
|
||||
case 0xAD: // CMP Y, #imm
|
||||
break;
|
||||
case 0x7E: // CMP Y, dp
|
||||
break;
|
||||
case 0x5E: // CMP Y, !abs
|
||||
break;
|
||||
|
||||
// 8-bit boolean logic
|
||||
|
||||
case 0x28: // AND A, #imm
|
||||
break;
|
||||
case 0x26: // AND A, (X)
|
||||
break;
|
||||
case 0x24: // AND A, dp
|
||||
break;
|
||||
case 0x34: // AND A, dp+X
|
||||
break;
|
||||
case 0x25: // AND A, !abs
|
||||
break;
|
||||
case 0x35: // AND A, !abs+X
|
||||
break;
|
||||
case 0x36: // AND A, !abs+Y
|
||||
break;
|
||||
case 0x27: // AND A, [dp+X]
|
||||
break;
|
||||
case 0x37: // AND A, [dp]+Y
|
||||
break;
|
||||
case 0x39: // AND (X), (Y)
|
||||
break;
|
||||
case 0x29: // AND dp, dp
|
||||
break;
|
||||
case 0x38: // AND dp, #imm
|
||||
break;
|
||||
case 0x08: // OR A, #imm
|
||||
OR(A, imm());
|
||||
break;
|
||||
case 0x06: // OR A, (X)
|
||||
break;
|
||||
case 0x04: // OR A, dp
|
||||
OR(A, dp());
|
||||
break;
|
||||
case 0x14: // OR A, dp+X
|
||||
OR(A, dp_plus_x());
|
||||
break;
|
||||
case 0x05: // OR A, !abs
|
||||
OR(A, abs());
|
||||
break;
|
||||
case 0x15: // OR A, !abs+X
|
||||
break;
|
||||
case 0x16: // OR A, !abs+Y
|
||||
break;
|
||||
case 0x07: // OR A, [dp+X]
|
||||
break;
|
||||
case 0x17: // OR A, [dp]+Y
|
||||
break;
|
||||
case 0x19: // OR (X), (Y)
|
||||
break;
|
||||
case 0x09: // OR dp, dp
|
||||
break;
|
||||
case 0x18: // OR dp, #imm
|
||||
break;
|
||||
case 0x48: // EOR A, #imm
|
||||
EOR(A, imm());
|
||||
break;
|
||||
case 0x46: // EOR A, (X)
|
||||
break;
|
||||
case 0x44: // EOR A, dp
|
||||
EOR(A, dp());
|
||||
break;
|
||||
case 0x54: // EOR A, dp+X
|
||||
break;
|
||||
case 0x45: // EOR A, !abs
|
||||
break;
|
||||
case 0x55: // EOR A, !abs+X
|
||||
break;
|
||||
case 0x56: // EOR A, !abs+Y
|
||||
break;
|
||||
case 0x47: // EOR A, [dp+X]
|
||||
break;
|
||||
case 0x57: // EOR A, [dp]+Y
|
||||
break;
|
||||
case 0x59: // EOR (X), (Y)
|
||||
break;
|
||||
case 0x49: // EOR dp, dp
|
||||
break;
|
||||
case 0x58: // EOR dp, #imm
|
||||
break;
|
||||
|
||||
// . 8-bit increment / decrement
|
||||
|
||||
case 0xBC: // INC A
|
||||
INC(A);
|
||||
break;
|
||||
case 0xAB: // INC dp
|
||||
break;
|
||||
case 0xBB: // INC dp+X
|
||||
break;
|
||||
case 0xAC: // INC !abs
|
||||
break;
|
||||
case 0x3D: // INC X
|
||||
INC(X);
|
||||
break;
|
||||
case 0xFC: // INC Y
|
||||
INC(Y);
|
||||
break;
|
||||
case 0x9C: // DEC A
|
||||
DEC(A);
|
||||
break;
|
||||
case 0x8B: // DEC dp
|
||||
break;
|
||||
case 0x9B: // DEC dp+X
|
||||
break;
|
||||
case 0x8C: // DEC !abs
|
||||
break;
|
||||
case 0x1D: // DEC X
|
||||
DEC(X);
|
||||
break;
|
||||
case 0xDC: // DEC Y
|
||||
DEC(Y);
|
||||
break;
|
||||
|
||||
// 8-bit shift / rotation
|
||||
|
||||
case 0x1C: // ASL A
|
||||
ASL(A);
|
||||
break;
|
||||
case 0x0B: // ASL dp
|
||||
ASL(dp());
|
||||
break;
|
||||
case 0x1B: // ASL dp+X
|
||||
ASL(dp_plus_x());
|
||||
break;
|
||||
case 0x0C: // ASL !abs
|
||||
ASL(abs());
|
||||
break;
|
||||
case 0x5C: // LSR A
|
||||
LSR(A);
|
||||
break;
|
||||
case 0x4B: // LSR dp
|
||||
break;
|
||||
case 0x5B: // LSR dp+X
|
||||
break;
|
||||
case 0x4C: // LSR !abs
|
||||
break;
|
||||
case 0x3C: // ROL A
|
||||
break;
|
||||
case 0x2B: // ROL dp
|
||||
break;
|
||||
case 0x3B: // ROL dp+X
|
||||
break;
|
||||
case 0x2C: // ROL !abs
|
||||
break;
|
||||
case 0x7C: // ROR A
|
||||
break;
|
||||
case 0x6B: // ROR dp
|
||||
break;
|
||||
case 0x7B: // ROR dp+X
|
||||
break;
|
||||
case 0x6C: // ROR !abs
|
||||
break;
|
||||
case 0x9F: // XCN A Exchange nibbles of A
|
||||
|
||||
break;
|
||||
|
||||
// . 16-bit operations
|
||||
|
||||
case 0xBA: // MOVW YA, dp
|
||||
break;
|
||||
case 0xDA: // MOVW dp, YA
|
||||
break;
|
||||
case 0x3A: // INCW dp
|
||||
break;
|
||||
case 0x1A: // DECW dp
|
||||
break;
|
||||
case 0x7A: // ADDW YA, dp
|
||||
break;
|
||||
case 0x9A: // SUBW YA, dp
|
||||
break;
|
||||
case 0x5A: // CMPW YA, dp
|
||||
break;
|
||||
case 0xCF: // MUL YA
|
||||
break;
|
||||
case 0x9E: // DIV YA, X
|
||||
break;
|
||||
|
||||
// . decimal adjust
|
||||
|
||||
case 0xDF: // DAA A
|
||||
break;
|
||||
case 0xBE: // DAS A
|
||||
break;
|
||||
|
||||
// . branching
|
||||
|
||||
case 0x2F: // BRA rel
|
||||
break;
|
||||
case 0xF0: // BEQ rel
|
||||
break;
|
||||
case 0xD0: // BNE rel
|
||||
break;
|
||||
case 0xB0: // BCS rel
|
||||
break;
|
||||
case 0x90: // BCC rel
|
||||
break;
|
||||
case 0x70: // BVS rel
|
||||
break;
|
||||
case 0x50: // BVC rel
|
||||
break;
|
||||
case 0x30: // BMI rel
|
||||
break;
|
||||
case 0x10: // BPL rel
|
||||
break;
|
||||
case 0x2E: // CBNE dp, rel
|
||||
break;
|
||||
case 0xDE: // CBNE dp+X, rel
|
||||
break;
|
||||
case 0x6E: // DBNZ dp, rel
|
||||
break;
|
||||
case 0xFE: // DBNZ Y, rel
|
||||
break;
|
||||
case 0x5F: // JMP !abs
|
||||
break;
|
||||
case 0x1F: // JMP [!abs+X]
|
||||
break;
|
||||
|
||||
// . subroutines
|
||||
|
||||
case 0x3F: // CALL !abs
|
||||
break;
|
||||
case 0x4F: // PCALL up
|
||||
break;
|
||||
case 0x6F: // RET
|
||||
break;
|
||||
case 0x7F: // RETI
|
||||
break;
|
||||
|
||||
// . stack
|
||||
|
||||
case 0x2D: // PUSH A
|
||||
break;
|
||||
case 0x4D: // PUSH X
|
||||
break;
|
||||
case 0x6D: // PUSH Y
|
||||
break;
|
||||
case 0x0D: // PUSH PSW
|
||||
break;
|
||||
case 0xAE: // POP A
|
||||
break;
|
||||
case 0xCE: // POP X
|
||||
break;
|
||||
case 0xEE: // POP Y
|
||||
break;
|
||||
case 0x8E: // POP PSW
|
||||
break;
|
||||
|
||||
// . memory bit operations
|
||||
|
||||
case 0xEA: // NOT1 abs, bit
|
||||
break;
|
||||
case 0xAA: // MOV1 C, abs, bit
|
||||
break;
|
||||
case 0xCA: // MOV1 abs, bit, C
|
||||
break;
|
||||
case 0x4A: // AND1 C, abs, bit
|
||||
break;
|
||||
case 0x6A: // AND1 C, /abs, bit
|
||||
break;
|
||||
case 0x0A: // OR1 C, abs, bit
|
||||
break;
|
||||
case 0x2A: // OR1 C, /abs, bit
|
||||
break;
|
||||
case 0x8A: // EOR1 C, abs, bit
|
||||
break;
|
||||
|
||||
// . status flags
|
||||
|
||||
case 0x60: // CLRC
|
||||
break;
|
||||
case 0x80: // SETC
|
||||
break;
|
||||
case 0xED: // NOTC
|
||||
break;
|
||||
case 0xE0: // CLRV
|
||||
break;
|
||||
case 0x20: // CLRP
|
||||
break;
|
||||
case 0x40: // SETP
|
||||
break;
|
||||
case 0xA0: // EI
|
||||
break;
|
||||
case 0xC0: // DI
|
||||
break;
|
||||
|
||||
// .no-operation and halt
|
||||
|
||||
case 0x00: // NOP
|
||||
break;
|
||||
case 0xEF: // SLEEP
|
||||
break;
|
||||
case 0x0F: // STOP
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cout << "Unknown opcode: " << std::hex << opcode << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
480
src/app/emu/audio/spc700.h
Normal file
480
src/app/emu/audio/spc700.h
Normal file
@@ -0,0 +1,480 @@
|
||||
#ifndef YAZE_APP_EMU_SPC700_H
|
||||
#define YAZE_APP_EMU_SPC700_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
class AudioRam {
|
||||
public:
|
||||
virtual ~AudioRam() = default;
|
||||
|
||||
// Read a byte from ARAM at the given address
|
||||
virtual uint8_t read(uint16_t address) const = 0;
|
||||
|
||||
// Write a byte to ARAM at the given address
|
||||
virtual void write(uint16_t address, uint8_t value) = 0;
|
||||
};
|
||||
|
||||
class AudioRamImpl : public AudioRam {
|
||||
static const size_t ARAM_SIZE = 64 * 1024; // 64 KB
|
||||
std::vector<uint8_t> ram = std::vector<uint8_t>(ARAM_SIZE, 0);
|
||||
|
||||
public:
|
||||
AudioRamImpl() = default;
|
||||
|
||||
// Read a byte from ARAM at the given address
|
||||
uint8_t read(uint16_t address) const override {
|
||||
return ram[address % ARAM_SIZE];
|
||||
}
|
||||
|
||||
// Write a byte to ARAM at the given address
|
||||
void write(uint16_t address, uint8_t value) override {
|
||||
ram[address % ARAM_SIZE] = value;
|
||||
}
|
||||
};
|
||||
|
||||
class SPC700 {
|
||||
private:
|
||||
AudioRam& aram_;
|
||||
|
||||
public:
|
||||
explicit SPC700(AudioRam& aram) : aram_(aram) {}
|
||||
uint8_t test_register_;
|
||||
uint8_t control_register_;
|
||||
uint8_t dsp_address_register_;
|
||||
|
||||
// Registers
|
||||
uint8_t A; // 8-bit accumulator
|
||||
uint8_t X; // 8-bit index
|
||||
uint8_t Y; // 8-bit index
|
||||
uint16_t YA; // 16-bit pair of A (lsb) and Y (msb)
|
||||
uint16_t PC; // program counter
|
||||
uint8_t SP; // stack pointer
|
||||
|
||||
struct Flags {
|
||||
uint8_t N : 1; // Negative flag
|
||||
uint8_t V : 1; // Overflow flag
|
||||
uint8_t P : 1; // Direct page flag
|
||||
uint8_t B : 1; // Break flag
|
||||
uint8_t H : 1; // Half-carry flag
|
||||
uint8_t I : 1; // Interrupt enable
|
||||
uint8_t Z : 1; // Zero flag
|
||||
uint8_t C : 1; // Carry flag
|
||||
};
|
||||
Flags PSW; // Processor status word
|
||||
|
||||
void Reset();
|
||||
|
||||
void ExecuteInstructions(uint8_t opcode);
|
||||
|
||||
// Read a byte from the memory-mapped registers
|
||||
uint8_t read(uint16_t address) {
|
||||
switch (address) {
|
||||
case 0xF0:
|
||||
return test_register_;
|
||||
case 0xF1:
|
||||
return control_register_;
|
||||
case 0xF2:
|
||||
return dsp_address_register_;
|
||||
default:
|
||||
if (address < 0xFFC0) {
|
||||
return aram_.read(address);
|
||||
} else {
|
||||
// Handle IPL ROM or RAM reads here
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write a byte to the memory-mapped registers
|
||||
void write(uint16_t address, uint8_t value) {
|
||||
switch (address) {
|
||||
case 0xF0:
|
||||
test_register_ = value;
|
||||
break;
|
||||
case 0xF1:
|
||||
control_register_ = value;
|
||||
break;
|
||||
case 0xF2:
|
||||
dsp_address_register_ = value;
|
||||
break;
|
||||
default:
|
||||
if (address < 0xFFC0) {
|
||||
aram_.write(address, value);
|
||||
} else {
|
||||
// Handle IPL ROM or RAM writes here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Addressing modes
|
||||
|
||||
// Immediate
|
||||
uint8_t imm() {
|
||||
PC++;
|
||||
return read(PC);
|
||||
}
|
||||
|
||||
// Direct page
|
||||
uint8_t dp() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return read((PSW.P << 8) + offset);
|
||||
}
|
||||
|
||||
uint8_t get_dp_addr() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return (PSW.P << 8) + offset;
|
||||
}
|
||||
|
||||
// Direct page indexed by X
|
||||
uint8_t dp_plus_x() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return read((PSW.P << 8) + offset + X);
|
||||
}
|
||||
|
||||
// Direct page indexed by Y
|
||||
uint8_t dp_plus_y() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
return read((PSW.P << 8) + offset + Y);
|
||||
}
|
||||
|
||||
// Indexed indirect (add index before 16-bit lookup).
|
||||
uint16_t dp_plus_x_indirect() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
uint16_t addr = read((PSW.P << 8) + offset + X) |
|
||||
(read((PSW.P << 8) + offset + X + 1) << 8);
|
||||
return addr;
|
||||
}
|
||||
|
||||
// Indirect indexed (add index after 16-bit lookup).
|
||||
uint16_t dp_indirect_plus_y() {
|
||||
PC++;
|
||||
uint8_t offset = read(PC);
|
||||
uint16_t baseAddr =
|
||||
read((PSW.P << 8) + offset) | (read((PSW.P << 8) + offset + 1) << 8);
|
||||
return baseAddr + Y;
|
||||
}
|
||||
|
||||
uint16_t abs() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
return addr;
|
||||
}
|
||||
|
||||
int8_t rel() {
|
||||
PC++;
|
||||
return static_cast<int8_t>(read(PC));
|
||||
}
|
||||
|
||||
uint8_t i() { return read((PSW.P << 8) + X); }
|
||||
|
||||
uint8_t i_postinc() {
|
||||
uint8_t value = read((PSW.P << 8) + X);
|
||||
X++;
|
||||
return value;
|
||||
}
|
||||
|
||||
uint16_t addr_plus_i() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
return read(addr) + X;
|
||||
}
|
||||
|
||||
uint16_t addr_plus_i_indexed() {
|
||||
PC++;
|
||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||
addr += X;
|
||||
return read(addr) | (read(addr + 1) << 8);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Instructions
|
||||
|
||||
// MOV
|
||||
void MOV(uint8_t& dest, uint8_t operand) {
|
||||
dest = operand;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
void MOV_ADDR(uint16_t address, uint8_t operand) {
|
||||
write(address, operand);
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
// ADC
|
||||
void ADC(uint8_t& dest, uint8_t operand) {
|
||||
uint16_t result = dest + operand + PSW.C;
|
||||
PSW.V = ((A ^ result) & (operand ^ result) & 0x80);
|
||||
PSW.C = (result > 0xFF);
|
||||
PSW.Z = ((result & 0xFF) == 0);
|
||||
PSW.N = (result & 0x80);
|
||||
PSW.H = ((A ^ operand ^ result) & 0x10);
|
||||
dest = result & 0xFF;
|
||||
}
|
||||
|
||||
// SBC
|
||||
void SBC(uint8_t& dest, uint8_t operand) {
|
||||
uint16_t result = dest - operand - (1 - PSW.C);
|
||||
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x80);
|
||||
PSW.C = (result < 0x100);
|
||||
PSW.Z = ((result & 0xFF) == 0);
|
||||
PSW.N = (result & 0x80);
|
||||
PSW.H = ((dest ^ operand ^ result) & 0x10);
|
||||
dest = result & 0xFF;
|
||||
}
|
||||
|
||||
// CMP
|
||||
void CMP(uint8_t& dest, uint8_t operand) {
|
||||
uint16_t result = dest - operand;
|
||||
PSW.C = (result < 0x100);
|
||||
PSW.Z = ((result & 0xFF) == 0);
|
||||
PSW.N = (result & 0x80);
|
||||
}
|
||||
|
||||
// AND
|
||||
void AND(uint8_t& dest, uint8_t operand) {
|
||||
dest &= operand;
|
||||
PSW.Z = (dest == 0);
|
||||
PSW.N = (dest & 0x80);
|
||||
}
|
||||
|
||||
// OR
|
||||
void OR(uint8_t& dest, uint8_t operand) {
|
||||
dest |= operand;
|
||||
PSW.Z = (dest == 0);
|
||||
PSW.N = (dest & 0x80);
|
||||
}
|
||||
|
||||
// EOR
|
||||
void EOR(uint8_t& dest, uint8_t operand) {
|
||||
dest ^= operand;
|
||||
PSW.Z = (dest == 0);
|
||||
PSW.N = (dest & 0x80);
|
||||
}
|
||||
|
||||
// ASL
|
||||
void ASL(uint8_t operand) {
|
||||
PSW.C = (operand & 0x80);
|
||||
operand <<= 1;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
// A = value;
|
||||
}
|
||||
|
||||
// LSR
|
||||
void LSR(uint8_t& operand) {
|
||||
PSW.C = (operand & 0x01);
|
||||
operand >>= 1;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
// ROL
|
||||
void ROL(uint8_t operand, bool isImmediate = false) {
|
||||
uint8_t value = isImmediate ? imm() : operand;
|
||||
uint8_t carry = PSW.C;
|
||||
PSW.C = (value & 0x80);
|
||||
value <<= 1;
|
||||
value |= carry;
|
||||
PSW.Z = (value == 0);
|
||||
PSW.N = (value & 0x80);
|
||||
// operand = value;
|
||||
}
|
||||
|
||||
// XCN
|
||||
void XCN(uint8_t operand, bool isImmediate = false) {
|
||||
uint8_t value = isImmediate ? imm() : operand;
|
||||
value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);
|
||||
PSW.Z = (value == 0);
|
||||
PSW.N = (value & 0x80);
|
||||
// operand = value;
|
||||
}
|
||||
|
||||
// INC
|
||||
void INC(uint8_t& operand) {
|
||||
operand++;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
// DEC
|
||||
void DEC(uint8_t& operand) {
|
||||
operand--;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x80);
|
||||
}
|
||||
|
||||
// MOVW
|
||||
void MOVW(uint16_t& dest, uint16_t operand) {
|
||||
dest = operand;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
// INCW
|
||||
void INCW(uint16_t& operand) {
|
||||
operand++;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
// DECW
|
||||
void DECW(uint16_t& operand) {
|
||||
operand--;
|
||||
PSW.Z = (operand == 0);
|
||||
PSW.N = (operand & 0x8000);
|
||||
}
|
||||
|
||||
// ADDW
|
||||
void ADDW(uint16_t& dest, uint16_t operand) {
|
||||
uint32_t result = dest + operand;
|
||||
PSW.C = (result > 0xFFFF);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
PSW.V = ((dest ^ result) & (operand ^ result) & 0x8000);
|
||||
dest = result & 0xFFFF;
|
||||
}
|
||||
|
||||
// SUBW
|
||||
void SUBW(uint16_t& dest, uint16_t operand) {
|
||||
uint32_t result = dest - operand;
|
||||
PSW.C = (result < 0x10000);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x8000);
|
||||
dest = result & 0xFFFF;
|
||||
}
|
||||
|
||||
// CMPW
|
||||
void CMPW(uint16_t operand) {
|
||||
uint32_t result = YA - operand;
|
||||
PSW.C = (result < 0x10000);
|
||||
PSW.Z = ((result & 0xFFFF) == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
}
|
||||
|
||||
// MUL
|
||||
void MUL(uint8_t operand) {
|
||||
uint16_t result = A * operand;
|
||||
YA = result;
|
||||
PSW.Z = (result == 0);
|
||||
PSW.N = (result & 0x8000);
|
||||
}
|
||||
|
||||
// DIV
|
||||
void DIV(uint8_t operand) {
|
||||
if (operand == 0) {
|
||||
// Handle divide by zero error
|
||||
return;
|
||||
}
|
||||
uint8_t quotient = A / operand;
|
||||
uint8_t remainder = A % operand;
|
||||
A = quotient;
|
||||
Y = remainder;
|
||||
PSW.Z = (quotient == 0);
|
||||
PSW.N = (quotient & 0x80);
|
||||
}
|
||||
|
||||
// DAA
|
||||
|
||||
// BRA
|
||||
void BRA(int8_t offset) { PC += offset; }
|
||||
|
||||
// BEQ
|
||||
void BEQ(int8_t offset) {
|
||||
if (PSW.Z) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// BNE
|
||||
void BNE(int8_t offset) {
|
||||
if (!PSW.Z) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// BCS
|
||||
void BCS(int8_t offset) {
|
||||
if (PSW.C) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// BCC
|
||||
void BCC(int8_t offset) {
|
||||
if (!PSW.C) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// BVS
|
||||
void BVS(int8_t offset) {
|
||||
if (PSW.V) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// BVC
|
||||
void BVC(int8_t offset) {
|
||||
if (!PSW.V) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// BMI
|
||||
void BMI(int8_t offset) {
|
||||
if (PSW.N) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// BPL
|
||||
void BPL(int8_t offset) {
|
||||
if (!PSW.N) {
|
||||
PC += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// BBS
|
||||
void BBS(uint8_t bit, uint8_t operand) {
|
||||
if (operand & (1 << bit)) {
|
||||
PC += rel();
|
||||
}
|
||||
}
|
||||
|
||||
// BBC
|
||||
void BBC(uint8_t bit, uint8_t operand) {
|
||||
if (!(operand & (1 << bit))) {
|
||||
PC += rel();
|
||||
}
|
||||
}
|
||||
|
||||
// CBNE DBNZ
|
||||
// JMP
|
||||
void JMP(uint16_t address) { PC = address; }
|
||||
|
||||
// CALL PCALL TCALL BRK RET RETI
|
||||
// PUSH POP
|
||||
// SET1 CLR1 TSET1 TCLR1 AND1 OR1 EOR1 NOT1 MOV1
|
||||
// CLRC SETC NOTC CLRV CLRP SETP EI DI
|
||||
// NOP SLEEP STOP
|
||||
};
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
#endif // YAZE_APP_EMU_SPC700_H
|
||||
Reference in New Issue
Block a user