315 lines
11 KiB
C++
315 lines
11 KiB
C++
#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/memory/memory.h"
|
|
|
|
namespace yaze {
|
|
namespace app {
|
|
namespace emu {
|
|
namespace audio {
|
|
|
|
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 DigitalSignalProcessor {
|
|
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>> DigitalSignalProcessorPeriodTable = {
|
|
// ... Your DSP period table here ...
|
|
};
|
|
|
|
// DSP Period Offset
|
|
const std::vector<uint16_t> DigitalSignalProcessorPeriodOffset = {
|
|
// ... 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:
|
|
DigitalSignalProcessor() = 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 audio
|
|
} // namespace emu
|
|
} // namespace app
|
|
} // namespace yaze
|
|
|
|
#endif // YAZE_APP_EMU_AUDIO_S_DSP_H
|