mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 15:45:42 +00:00
SPU: Implement volume sweep functionality
This commit is contained in:
parent
a5083f0ee4
commit
0a6295a9b4
|
@ -2,4 +2,4 @@
|
|||
#include "types.h"
|
||||
|
||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 8;
|
||||
static constexpr u32 SAVE_STATE_VERSION = 9;
|
||||
|
|
210
src/core/spu.cpp
210
src/core/spu.cpp
|
@ -11,10 +11,7 @@
|
|||
Log_SetChannel(SPU);
|
||||
|
||||
// TODO:
|
||||
// - Reverb
|
||||
// - Noise
|
||||
// - Volume Sweep
|
||||
// - Pulse Modulation
|
||||
|
||||
SPU::SPU() = default;
|
||||
|
||||
|
@ -40,8 +37,10 @@ void SPU::Reset()
|
|||
m_transfer_address_reg = 0;
|
||||
m_irq_address = 0;
|
||||
m_capture_buffer_position = 0;
|
||||
m_main_volume_left.bits = 0;
|
||||
m_main_volume_right.bits = 0;
|
||||
m_main_volume_left_reg.bits = 0;
|
||||
m_main_volume_right_reg.bits = 0;
|
||||
m_main_volume_left = {};
|
||||
m_main_volume_right = {};
|
||||
m_cd_audio_volume_left = 0;
|
||||
m_cd_audio_volume_right = 0;
|
||||
m_key_on_register = 0;
|
||||
|
@ -65,12 +64,7 @@ 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.adsr_ticks_remaining = 0;
|
||||
v.adsr_phase = ADSRPhase::Off;
|
||||
v.adsr_target = 0;
|
||||
v.adsr_rate = 0;
|
||||
v.adsr_decreasing = false;
|
||||
v.adsr_exponential = false;
|
||||
v.SetADSRPhase(ADSRPhase::Off);
|
||||
v.has_samples = false;
|
||||
}
|
||||
|
||||
|
@ -91,8 +85,10 @@ bool SPU::DoState(StateWrapper& sw)
|
|||
sw.Do(&m_transfer_address_reg);
|
||||
sw.Do(&m_irq_address);
|
||||
sw.Do(&m_capture_buffer_position);
|
||||
sw.Do(&m_main_volume_left.bits);
|
||||
sw.Do(&m_main_volume_right.bits);
|
||||
sw.Do(&m_main_volume_left_reg.bits);
|
||||
sw.Do(&m_main_volume_right_reg.bits);
|
||||
sw.DoPOD(&m_main_volume_left);
|
||||
sw.DoPOD(&m_main_volume_right);
|
||||
sw.Do(&m_cd_audio_volume_left);
|
||||
sw.Do(&m_cd_audio_volume_right);
|
||||
sw.Do(&m_key_on_register);
|
||||
|
@ -117,12 +113,11 @@ bool SPU::DoState(StateWrapper& sw)
|
|||
sw.Do(&v.previous_block_last_samples);
|
||||
sw.Do(&v.adpcm_last_samples);
|
||||
sw.Do(&v.last_amplitude);
|
||||
sw.Do(&v.adsr_ticks_remaining);
|
||||
sw.Do(&v.adsr_target);
|
||||
sw.DoPOD(&v.left_volume);
|
||||
sw.DoPOD(&v.right_volume);
|
||||
sw.DoPOD(&v.adsr_envelope);
|
||||
sw.Do(&v.adsr_phase);
|
||||
sw.Do(&v.adsr_rate);
|
||||
sw.Do(&v.adsr_decreasing);
|
||||
sw.Do(&v.adsr_exponential);
|
||||
sw.Do(&v.adsr_target);
|
||||
sw.Do(&v.has_samples);
|
||||
}
|
||||
|
||||
|
@ -144,10 +139,10 @@ u16 SPU::ReadRegister(u32 offset)
|
|||
switch (offset)
|
||||
{
|
||||
case 0x1F801D80 - SPU_BASE:
|
||||
return m_main_volume_left.bits;
|
||||
return m_main_volume_left_reg.bits;
|
||||
|
||||
case 0x1F801D82 - SPU_BASE:
|
||||
return m_main_volume_right.bits;
|
||||
return m_main_volume_right_reg.bits;
|
||||
|
||||
case 0x1F801D84 - SPU_BASE:
|
||||
return m_reverb_registers.vLOUT;
|
||||
|
@ -241,7 +236,8 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
{
|
||||
Log_DebugPrintf("SPU main volume left <- 0x%04X", ZeroExtend32(value));
|
||||
m_tick_event->InvokeEarly();
|
||||
m_main_volume_left.bits = value;
|
||||
m_main_volume_left_reg.bits = value;
|
||||
m_main_volume_left.Reset(m_main_volume_left_reg);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -249,7 +245,8 @@ void SPU::WriteRegister(u32 offset, u16 value)
|
|||
{
|
||||
Log_DebugPrintf("SPU main volume right <- 0x%04X", ZeroExtend32(value));
|
||||
m_tick_event->InvokeEarly();
|
||||
m_main_volume_right.bits = value;
|
||||
m_main_volume_right_reg.bits = value;
|
||||
m_main_volume_right.Reset(m_main_volume_right_reg);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -522,6 +519,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
|
|||
{
|
||||
Log_DebugPrintf("SPU voice %u volume left <- 0x%04X", voice_index, value);
|
||||
voice.regs.volume_left.bits = value;
|
||||
voice.left_volume.Reset(voice.regs.volume_left);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -529,6 +527,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
|
|||
{
|
||||
Log_DebugPrintf("SPU voice %u volume right <- 0x%04X", voice_index, value);
|
||||
voice.regs.volume_right.bits = value;
|
||||
voice.right_volume.Reset(voice.regs.volume_right);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -772,8 +771,10 @@ void SPU::Execute(TickCount ticks)
|
|||
right_sum += m_reverb_right_output;
|
||||
|
||||
// Apply main volume before clamping.
|
||||
*(output_frame++) = Clamp16(ApplyVolume(left_sum, m_main_volume_left.GetVolume()));
|
||||
*(output_frame++) = Clamp16(ApplyVolume(right_sum, m_main_volume_right.GetVolume()));
|
||||
*(output_frame++) = Clamp16(ApplyVolume(left_sum, m_main_volume_left.current_level));
|
||||
*(output_frame++) = Clamp16(ApplyVolume(right_sum, m_main_volume_right.current_level));
|
||||
m_main_volume_left.Tick();
|
||||
m_main_volume_right.Tick();
|
||||
|
||||
// Write to capture buffers.
|
||||
WriteToCaptureBuffer(0, cd_audio_left);
|
||||
|
@ -855,7 +856,6 @@ void SPU::Voice::KeyOn()
|
|||
regs.adsr_volume = 0;
|
||||
has_samples = false;
|
||||
SetADSRPhase(ADSRPhase::Attack);
|
||||
adsr_ticks_remaining = 0;
|
||||
}
|
||||
|
||||
void SPU::Voice::KeyOff()
|
||||
|
@ -864,7 +864,6 @@ void SPU::Voice::KeyOff()
|
|||
return;
|
||||
|
||||
SetADSRPhase(ADSRPhase::Release);
|
||||
adsr_ticks_remaining = 0;
|
||||
}
|
||||
|
||||
SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase)
|
||||
|
@ -934,6 +933,80 @@ static constexpr ADSRTableEntries ComputeADSRTableEntries()
|
|||
|
||||
static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries();
|
||||
|
||||
void SPU::VolumeEnvelope::Reset(u8 rate_, bool decreasing_, bool exponential_)
|
||||
{
|
||||
rate = rate_;
|
||||
decreasing = decreasing_;
|
||||
exponential = exponential_;
|
||||
|
||||
const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate];
|
||||
counter = table_entry.ticks;
|
||||
}
|
||||
|
||||
s16 SPU::VolumeEnvelope::Tick(s16 current_level)
|
||||
{
|
||||
counter--;
|
||||
if (counter > 0)
|
||||
return current_level;
|
||||
|
||||
const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(decreasing)][rate];
|
||||
s32 this_step = table_entry.step;
|
||||
counter = table_entry.ticks;
|
||||
|
||||
if (exponential)
|
||||
{
|
||||
if (decreasing)
|
||||
{
|
||||
this_step = (this_step * current_level) >> 15;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current_level >= 0x6000)
|
||||
{
|
||||
if (rate < 40)
|
||||
{
|
||||
this_step >>= 2;
|
||||
}
|
||||
else if (rate >= 44)
|
||||
{
|
||||
counter >>= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
this_step >>= 1;
|
||||
counter >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<s16>(
|
||||
std::clamp<s32>(static_cast<s32>(current_level) + this_step, ENVELOPE_MIN_VOLUME, ENVELOPE_MAX_VOLUME));
|
||||
}
|
||||
|
||||
void SPU::VolumeSweep::Reset(VolumeRegister reg)
|
||||
{
|
||||
if (!reg.sweep_mode)
|
||||
{
|
||||
current_level = reg.fixed_volume_shr1 * 2;
|
||||
envelope_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
envelope.Reset(reg.sweep_rate, reg.sweep_direction_decrease, reg.sweep_exponential);
|
||||
envelope_active = true;
|
||||
}
|
||||
|
||||
void SPU::VolumeSweep::Tick()
|
||||
{
|
||||
if (!envelope_active)
|
||||
return;
|
||||
|
||||
current_level = envelope.Tick(current_level);
|
||||
envelope_active =
|
||||
(envelope.decreasing ? (current_level > ENVELOPE_MIN_VOLUME) : (current_level < ENVELOPE_MAX_VOLUME));
|
||||
}
|
||||
|
||||
void SPU::Voice::SetADSRPhase(ADSRPhase phase)
|
||||
{
|
||||
adsr_phase = phase;
|
||||
|
@ -941,36 +1014,28 @@ void SPU::Voice::SetADSRPhase(ADSRPhase phase)
|
|||
{
|
||||
case ADSRPhase::Off:
|
||||
adsr_target = 0;
|
||||
adsr_decreasing = false;
|
||||
adsr_exponential = false;
|
||||
adsr_envelope.Reset(0, false, false);
|
||||
return;
|
||||
|
||||
case ADSRPhase::Attack:
|
||||
adsr_target = 32767; // 0 -> max
|
||||
adsr_decreasing = false;
|
||||
adsr_exponential = regs.adsr.attack_exponential;
|
||||
adsr_rate = regs.adsr.attack_rate;
|
||||
adsr_envelope.Reset(regs.adsr.attack_rate, false, regs.adsr.attack_exponential);
|
||||
break;
|
||||
|
||||
case ADSRPhase::Decay:
|
||||
adsr_target = (u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800; // max -> sustain level
|
||||
adsr_decreasing = true;
|
||||
adsr_exponential = true;
|
||||
adsr_rate = regs.adsr.decay_rate_shr2 << 2;
|
||||
adsr_target = static_cast<s16>(std::min<s32>((u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800,
|
||||
ENVELOPE_MAX_VOLUME)); // max -> sustain level
|
||||
adsr_envelope.Reset(regs.adsr.decay_rate_shr2 << 2, true, true);
|
||||
break;
|
||||
|
||||
case ADSRPhase::Sustain:
|
||||
adsr_target = 0;
|
||||
adsr_decreasing = regs.adsr.sustain_direction_decrease;
|
||||
adsr_exponential = regs.adsr.sustain_exponential;
|
||||
adsr_rate = regs.adsr.sustain_rate;
|
||||
adsr_envelope.Reset(regs.adsr.sustain_rate, regs.adsr.sustain_direction_decrease, regs.adsr.sustain_exponential);
|
||||
break;
|
||||
|
||||
case ADSRPhase::Release:
|
||||
adsr_target = 0;
|
||||
adsr_decreasing = true;
|
||||
adsr_exponential = regs.adsr.release_exponential;
|
||||
adsr_rate = regs.adsr.release_rate_shr2 << 2;
|
||||
adsr_envelope.Reset(regs.adsr.release_rate_shr2 << 2, true, regs.adsr.release_exponential);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -980,49 +1045,12 @@ void SPU::Voice::SetADSRPhase(ADSRPhase phase)
|
|||
|
||||
void SPU::Voice::TickADSR()
|
||||
{
|
||||
adsr_ticks_remaining--;
|
||||
if (adsr_ticks_remaining > 0)
|
||||
return;
|
||||
|
||||
// set up for next tick
|
||||
const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(adsr_decreasing)][adsr_rate];
|
||||
|
||||
s32 this_step = table_entry.step;
|
||||
adsr_ticks_remaining = table_entry.ticks;
|
||||
|
||||
if (adsr_exponential)
|
||||
{
|
||||
if (adsr_decreasing)
|
||||
{
|
||||
this_step = (this_step * regs.adsr_volume) >> 15;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (regs.adsr_volume >= 0x6000)
|
||||
{
|
||||
if (adsr_rate < 40)
|
||||
{
|
||||
this_step >>= 2;
|
||||
}
|
||||
else if (adsr_rate >= 44)
|
||||
{
|
||||
adsr_ticks_remaining >>= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
this_step >>= 1;
|
||||
adsr_ticks_remaining >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const s32 new_volume = s32(regs.adsr_volume) + s32(this_step);
|
||||
regs.adsr_volume = static_cast<s16>(std::clamp<s32>(new_volume, ADSR_MIN_VOLUME, ADSR_MAX_VOLUME));
|
||||
regs.adsr_volume = adsr_envelope.Tick(regs.adsr_volume);
|
||||
|
||||
if (adsr_phase != ADSRPhase::Sustain)
|
||||
{
|
||||
const bool reached_target = adsr_decreasing ? (new_volume <= adsr_target) : (new_volume >= adsr_target);
|
||||
const bool reached_target =
|
||||
adsr_envelope.decreasing ? (regs.adsr_volume <= adsr_target) : (regs.adsr_volume >= adsr_target);
|
||||
if (reached_target)
|
||||
SetADSRPhase(GetNextADSRPhase(adsr_phase));
|
||||
}
|
||||
|
@ -1245,8 +1273,10 @@ std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
|
|||
}
|
||||
|
||||
// apply per-channel volume
|
||||
const s32 left = ApplyVolume(amplitude, voice.regs.volume_left.GetVolume());
|
||||
const s32 right = ApplyVolume(amplitude, voice.regs.volume_right.GetVolume());
|
||||
const s32 left = ApplyVolume(amplitude, voice.left_volume.current_level);
|
||||
const s32 right = ApplyVolume(amplitude, voice.right_volume.current_level);
|
||||
voice.left_volume.Tick();
|
||||
voice.right_volume.Tick();
|
||||
return std::make_tuple(left, right);
|
||||
}
|
||||
|
||||
|
@ -1438,7 +1468,7 @@ void SPU::DrawDebugStateWindow()
|
|||
static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f};
|
||||
const float framebuffer_scale = ImGui::GetIO().DisplayFramebufferScale.x;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 600.0f * framebuffer_scale), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(800.0f * framebuffer_scale, 800.0f * framebuffer_scale), ImGuiCond_FirstUseEver);
|
||||
if (!ImGui::Begin("SPU State", &m_system->GetSettings().debugging.show_spu_state))
|
||||
{
|
||||
ImGui::End();
|
||||
|
@ -1487,9 +1517,9 @@ void SPU::DrawDebugStateWindow()
|
|||
|
||||
ImGui::Text("Volume: ");
|
||||
ImGui::SameLine(offsets[0]);
|
||||
ImGui::Text("Left: %d%%", ApplyVolume(100, m_main_volume_left.GetVolume()));
|
||||
ImGui::Text("Left: %d%%", ApplyVolume(100, m_main_volume_left.current_level));
|
||||
ImGui::SameLine(offsets[1]);
|
||||
ImGui::Text("Right: %d%%", ApplyVolume(100, m_main_volume_right.GetVolume()));
|
||||
ImGui::Text("Right: %d%%", ApplyVolume(100, m_main_volume_right.current_level));
|
||||
|
||||
ImGui::Text("CD Audio: ");
|
||||
ImGui::SameLine(offsets[0]);
|
||||
|
@ -1540,15 +1570,15 @@ void SPU::DrawDebugStateWindow()
|
|||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%.2f", (float(v.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f);
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.volume_left.bits));
|
||||
ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.left_volume.current_level));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.volume_right.bits));
|
||||
ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.right_volume.current_level));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%s", adsr_phases[static_cast<u8>(v.adsr_phase)]);
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%d", ZeroExtend32(v.regs.adsr_volume));
|
||||
ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.regs.adsr_volume));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%d", v.adsr_ticks_remaining);
|
||||
ImGui::TextColored(color, "%d", v.adsr_envelope.counter);
|
||||
ImGui::NextColumn();
|
||||
}
|
||||
|
||||
|
|
|
@ -65,8 +65,8 @@ private:
|
|||
static constexpr u32 NUM_SAMPLES_PER_ADPCM_BLOCK = 28;
|
||||
static constexpr u32 SAMPLE_RATE = 44100;
|
||||
static constexpr u32 SYSCLK_TICKS_PER_SPU_TICK = MASTER_CLOCK / SAMPLE_RATE; // 0x300
|
||||
static constexpr s16 ADSR_MIN_VOLUME = 0;
|
||||
static constexpr s16 ADSR_MAX_VOLUME = 0x7FFF;
|
||||
static constexpr s16 ENVELOPE_MIN_VOLUME = 0;
|
||||
static constexpr s16 ENVELOPE_MAX_VOLUME = 0x7FFF;
|
||||
static constexpr u32 CD_AUDIO_SAMPLE_BUFFER_SIZE = 44100 * 2;
|
||||
static constexpr u32 CAPTURE_BUFFER_SIZE_PER_CHANNEL = 0x400;
|
||||
static constexpr u32 MINIMUM_TICKS_BETWEEN_KEY_ON_OFF = 2;
|
||||
|
@ -145,15 +145,12 @@ private:
|
|||
u16 bits;
|
||||
|
||||
BitField<u16, bool, 15, 1> sweep_mode;
|
||||
BitField<u16, s16, 0, 15> fixed_volume; // divided by 2
|
||||
BitField<u16, s16, 0, 15> fixed_volume_shr1; // divided by 2
|
||||
|
||||
BitField<u16, bool, 14, 1> sweep_exponential;
|
||||
BitField<u16, bool, 13, 1> sweep_direction_decrease;
|
||||
BitField<u16, bool, 12, 1> sweep_phase_negative;
|
||||
BitField<u16, u8, 2, 5> sweep_shift;
|
||||
BitField<u16, u8, 0, 2> sweep_step;
|
||||
|
||||
s16 GetVolume() { return fixed_volume * 2; }
|
||||
BitField<u16, u8, 0, 7> sweep_rate;
|
||||
};
|
||||
|
||||
// organized so we can replace this with a u16 array in the future
|
||||
|
@ -218,6 +215,27 @@ private:
|
|||
u8 GetNibble(u32 index) const { return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; }
|
||||
};
|
||||
|
||||
struct VolumeEnvelope
|
||||
{
|
||||
s32 counter;
|
||||
u8 rate;
|
||||
bool decreasing;
|
||||
bool exponential;
|
||||
|
||||
void Reset(u8 rate_, bool decreasing_, bool exponential_);
|
||||
s16 Tick(s16 current_level);
|
||||
};
|
||||
|
||||
struct VolumeSweep
|
||||
{
|
||||
VolumeEnvelope envelope;
|
||||
bool envelope_active;
|
||||
s16 current_level;
|
||||
|
||||
void Reset(VolumeRegister reg);
|
||||
void Tick();
|
||||
};
|
||||
|
||||
enum class ADSRPhase : u8
|
||||
{
|
||||
Off = 0,
|
||||
|
@ -238,12 +256,12 @@ private:
|
|||
std::array<s32, 2> adpcm_last_samples;
|
||||
s32 last_amplitude;
|
||||
|
||||
TickCount adsr_ticks_remaining;
|
||||
s32 adsr_target;
|
||||
VolumeSweep left_volume;
|
||||
VolumeSweep right_volume;
|
||||
|
||||
VolumeEnvelope adsr_envelope;
|
||||
ADSRPhase adsr_phase;
|
||||
u8 adsr_rate;
|
||||
bool adsr_decreasing;
|
||||
bool adsr_exponential;
|
||||
s16 adsr_target;
|
||||
bool has_samples;
|
||||
|
||||
bool IsOn() const { return adsr_phase != ADSRPhase::Off; }
|
||||
|
@ -366,8 +384,10 @@ private:
|
|||
u16 m_irq_address = 0;
|
||||
u16 m_capture_buffer_position = 0;
|
||||
|
||||
VolumeRegister m_main_volume_left = {};
|
||||
VolumeRegister m_main_volume_right = {};
|
||||
VolumeRegister m_main_volume_left_reg = {};
|
||||
VolumeRegister m_main_volume_right_reg = {};
|
||||
VolumeSweep m_main_volume_left = {};
|
||||
VolumeSweep m_main_volume_right = {};
|
||||
|
||||
s16 m_cd_audio_volume_left = 0;
|
||||
s16 m_cd_audio_volume_right = 0;
|
||||
|
|
Loading…
Reference in a new issue