Reorganize emu folder, update S-SMP system infra

This commit is contained in:
scawful
2023-08-26 01:59:57 -04:00
parent 758056dc98
commit 3d793c452d
19 changed files with 1054 additions and 238 deletions

125
src/app/emu/audio/apu.cc Normal file
View 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
View 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
View 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
View 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
View 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
View 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