#include "app/emu/audio/dsp.h" #include "app/emu/memory/memory.h" namespace yaze { namespace app { namespace emu { void DigitalSignalProcessor::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 DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value) { voice %= kNumVoices; switch (reg % kNumVoiceRegs) { case 0: voices_[voice].vol_left = static_cast(value); break; case 1: voices_[voice].vol_right = static_cast(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(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 emu } // namespace app } // namespace yaze