From 8eb3ac69b2c63733739336bff76bae8cebc87a15 Mon Sep 17 00:00:00 2001
From: Connor McLaughlin <stenzek@gmail.com>
Date: Tue, 2 Jun 2020 01:59:44 +1000
Subject: [PATCH] SPU: Update ADSR envelope when register changes

Fixes menu sounds in Final Fantasy 7 staying audible for too long.
---
 src/core/spu.cpp | 33 ++++++++++++++++++++++-----------
 src/core/spu.h   |  2 +-
 2 files changed, 23 insertions(+), 12 deletions(-)

diff --git a/src/core/spu.cpp b/src/core/spu.cpp
index e6c8a4eb8..8c8b1465f 100644
--- a/src/core/spu.cpp
+++ b/src/core/spu.cpp
@@ -71,8 +71,11 @@ void SPU::Reset()
     v.current_block_samples.fill(s16(0));
     v.previous_block_last_samples.fill(s16(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.ignore_loop_address = false;
   }
 
   m_transfer_fifo.Clear();
@@ -594,21 +597,25 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
 
     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;
+      if (voice.IsOn())
+        voice.UpdateADSREnvelope();
     }
     break;
 
     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;
+      if (voice.IsOn())
+        voice.UpdateADSREnvelope();
     }
     break;
 
     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;
     }
     break;
@@ -1055,7 +1062,8 @@ void SPU::Voice::KeyOn()
   adpcm_last_samples.fill(0);
   has_samples = false;
   ignore_loop_address = false;
-  SetADSRPhase(ADSRPhase::Attack);
+  adsr_phase = ADSRPhase::Attack;
+  UpdateADSREnvelope();
 }
 
 void SPU::Voice::KeyOff()
@@ -1063,7 +1071,8 @@ void SPU::Voice::KeyOff()
   if (adsr_phase == ADSRPhase::Off || adsr_phase == ADSRPhase::Release)
     return;
 
-  SetADSRPhase(ADSRPhase::Release);
+  adsr_phase = ADSRPhase::Release;
+  UpdateADSREnvelope();
 }
 
 void SPU::Voice::ForceOff()
@@ -1072,7 +1081,7 @@ void SPU::Voice::ForceOff()
     return;
 
   regs.adsr_volume = 0;
-  SetADSRPhase(ADSRPhase::Off);
+  adsr_phase = ADSRPhase::Off;
 }
 
 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));
 }
 
-void SPU::Voice::SetADSRPhase(ADSRPhase phase)
+void SPU::Voice::UpdateADSREnvelope()
 {
-  adsr_phase = phase;
-  switch (phase)
+  switch (adsr_phase)
   {
     case ADSRPhase::Off:
       adsr_target = 0;
@@ -1261,7 +1269,10 @@ void SPU::Voice::TickADSR()
     const bool reached_target =
       adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target);
     if (reached_target)
-      SetADSRPhase(GetNextADSRPhase(adsr_phase));
+    {
+      adsr_phase = GetNextADSRPhase(adsr_phase);
+      UpdateADSREnvelope();
+    }
   }
 }
 
diff --git a/src/core/spu.h b/src/core/spu.h
index fc0f73218..591cb1377 100644
--- a/src/core/spu.h
+++ b/src/core/spu.h
@@ -277,7 +277,7 @@ private:
     s32 Interpolate() const;
 
     // Switches to the specified phase, filling in target.
-    void SetADSRPhase(ADSRPhase phase);
+    void UpdateADSREnvelope();
 
     // Updates the ADSR volume/phase.
     void TickADSR();