SPU: Update ADSR envelope when register changes

Fixes menu sounds in Final Fantasy 7 staying audible for too long.
This commit is contained in:
Connor McLaughlin 2020-06-02 01:59:44 +10:00
parent dcfb929de5
commit 8eb3ac69b2
2 changed files with 23 additions and 12 deletions

View File

@ -71,8 +71,11 @@ void SPU::Reset()
v.current_block_samples.fill(s16(0)); v.current_block_samples.fill(s16(0));
v.previous_block_last_samples.fill(s16(0)); v.previous_block_last_samples.fill(s16(0));
v.adpcm_last_samples.fill(s32(0)); v.adpcm_last_samples.fill(s32(0));
v.SetADSRPhase(ADSRPhase::Off); v.adsr_envelope.Reset(0, false, false);
v.adsr_phase = ADSRPhase::Off;
v.adsr_target = 0;
v.has_samples = false; v.has_samples = false;
v.ignore_loop_address = false;
} }
m_transfer_fifo.Clear(); m_transfer_fifo.Clear();
@ -594,21 +597,25 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
case 0x08: // adsr low case 0x08: // adsr low
{ {
Log_DebugPrintf("SPU voice %u ADSR low <- 0x%04X", voice_index, value); Log_DebugPrintf("SPU voice %u ADSR low <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr.bits_low);
voice.regs.adsr.bits_low = value; voice.regs.adsr.bits_low = value;
if (voice.IsOn())
voice.UpdateADSREnvelope();
} }
break; break;
case 0x0A: // adsr high case 0x0A: // adsr high
{ {
Log_DebugPrintf("SPU voice %u ADSR high <- 0x%04X", voice_index, value); Log_DebugPrintf("SPU voice %u ADSR high <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr.bits_low);
voice.regs.adsr.bits_high = value; voice.regs.adsr.bits_high = value;
if (voice.IsOn())
voice.UpdateADSREnvelope();
} }
break; break;
case 0x0C: // adsr volume case 0x0C: // adsr volume
{ {
Log_DebugPrintf("SPU voice %u ADSR volume <- 0x%04X", voice_index, value); Log_DebugPrintf("SPU voice %u ADSR volume <- 0x%04X (was 0x%04X)", voice_index, value, voice.regs.adsr_volume);
voice.regs.adsr_volume = value; voice.regs.adsr_volume = value;
} }
break; break;
@ -1055,7 +1062,8 @@ void SPU::Voice::KeyOn()
adpcm_last_samples.fill(0); adpcm_last_samples.fill(0);
has_samples = false; has_samples = false;
ignore_loop_address = false; ignore_loop_address = false;
SetADSRPhase(ADSRPhase::Attack); adsr_phase = ADSRPhase::Attack;
UpdateADSREnvelope();
} }
void SPU::Voice::KeyOff() void SPU::Voice::KeyOff()
@ -1063,7 +1071,8 @@ void SPU::Voice::KeyOff()
if (adsr_phase == ADSRPhase::Off || adsr_phase == ADSRPhase::Release) if (adsr_phase == ADSRPhase::Off || adsr_phase == ADSRPhase::Release)
return; return;
SetADSRPhase(ADSRPhase::Release); adsr_phase = ADSRPhase::Release;
UpdateADSREnvelope();
} }
void SPU::Voice::ForceOff() void SPU::Voice::ForceOff()
@ -1072,7 +1081,7 @@ void SPU::Voice::ForceOff()
return; return;
regs.adsr_volume = 0; regs.adsr_volume = 0;
SetADSRPhase(ADSRPhase::Off); adsr_phase = ADSRPhase::Off;
} }
SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase) SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase)
@ -1216,10 +1225,9 @@ void SPU::VolumeSweep::Tick()
(envelope.decreasing ? (current_level > ENVELOPE_MIN_VOLUME) : (current_level < ENVELOPE_MAX_VOLUME)); (envelope.decreasing ? (current_level > ENVELOPE_MIN_VOLUME) : (current_level < ENVELOPE_MAX_VOLUME));
} }
void SPU::Voice::SetADSRPhase(ADSRPhase phase) void SPU::Voice::UpdateADSREnvelope()
{ {
adsr_phase = phase; switch (adsr_phase)
switch (phase)
{ {
case ADSRPhase::Off: case ADSRPhase::Off:
adsr_target = 0; adsr_target = 0;
@ -1261,7 +1269,10 @@ void SPU::Voice::TickADSR()
const bool reached_target = const bool reached_target =
adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target); adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target);
if (reached_target) if (reached_target)
SetADSRPhase(GetNextADSRPhase(adsr_phase)); {
adsr_phase = GetNextADSRPhase(adsr_phase);
UpdateADSREnvelope();
}
} }
} }

View File

@ -277,7 +277,7 @@ private:
s32 Interpolate() const; s32 Interpolate() const;
// Switches to the specified phase, filling in target. // Switches to the specified phase, filling in target.
void SetADSRPhase(ADSRPhase phase); void UpdateADSREnvelope();
// Updates the ADSR volume/phase. // Updates the ADSR volume/phase.
void TickADSR(); void TickADSR();