Add Dsp::GetSamples and cleanup Apu
This commit is contained in:
@@ -7,282 +7,21 @@ namespace app {
|
||||
namespace emu {
|
||||
namespace audio {
|
||||
|
||||
void DigitalSignalProcessor::Reset() {}
|
||||
void Dsp::Reset() {}
|
||||
|
||||
uint8_t DigitalSignalProcessor::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::GetSamples(int16_t* sample_data, int samples_per_frame,
|
||||
bool pal_timing) {
|
||||
// resample from 534 / 641 samples per frame to wanted value
|
||||
float wantedSamples = (pal_timing ? 641.0 : 534.0);
|
||||
double adder = wantedSamples / samples_per_frame;
|
||||
double location = sample_offset_ - wantedSamples;
|
||||
for (int i = 0; i < samples_per_frame; i++) {
|
||||
sample_data[i * 2] = sample_buffer_[(((int)location) & 0x3ff) * 2];
|
||||
sample_data[i * 2 + 1] = sample_buffer_[(((int)location) & 0x3ff) * 2 + 1];
|
||||
location += adder;
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::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 DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) {
|
||||
sample_fetcher_ = fetcher;
|
||||
}
|
||||
|
||||
void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) {
|
||||
sample_pusher_ = pusher;
|
||||
}
|
||||
|
||||
int16_t DigitalSignalProcessor::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 DigitalSignalProcessor::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 DigitalSignalProcessor::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 DigitalSignalProcessor::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 DigitalSignalProcessor::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 DigitalSignalProcessor::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 audio
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
|
||||
Reference in New Issue
Block a user