mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-03-06 14:27:44 +00:00
SPU: Refactor ADSR/implement exponential mode
This commit is contained in:
parent
e098c83342
commit
278aa86d14
175
src/core/spu.cpp
175
src/core/spu.cpp
|
@ -58,11 +58,12 @@ 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.adsr_phase = ADSRPhase::Off;
|
|
||||||
v.adsr_target = {};
|
|
||||||
v.adsr_ticks = 0;
|
|
||||||
v.adsr_ticks_remaining = 0;
|
v.adsr_ticks_remaining = 0;
|
||||||
v.adsr_step = 0;
|
v.adsr_phase = ADSRPhase::Off;
|
||||||
|
v.adsr_target = 0;
|
||||||
|
v.adsr_rate = 0;
|
||||||
|
v.adsr_decreasing = false;
|
||||||
|
v.adsr_exponential = false;
|
||||||
v.has_samples = false;
|
v.has_samples = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,11 +101,12 @@ bool SPU::DoState(StateWrapper& sw)
|
||||||
sw.Do(&v.previous_block_last_samples);
|
sw.Do(&v.previous_block_last_samples);
|
||||||
sw.Do(&v.adpcm_last_samples);
|
sw.Do(&v.adpcm_last_samples);
|
||||||
sw.Do(&v.last_amplitude);
|
sw.Do(&v.last_amplitude);
|
||||||
sw.Do(&v.adsr_phase);
|
|
||||||
sw.DoPOD(&v.adsr_target);
|
|
||||||
sw.Do(&v.adsr_ticks);
|
|
||||||
sw.Do(&v.adsr_ticks_remaining);
|
sw.Do(&v.adsr_ticks_remaining);
|
||||||
sw.Do(&v.adsr_step);
|
sw.Do(&v.adsr_target);
|
||||||
|
sw.Do(&v.adsr_phase);
|
||||||
|
sw.Do(&v.adsr_rate);
|
||||||
|
sw.Do(&v.adsr_decreasing);
|
||||||
|
sw.Do(&v.adsr_exponential);
|
||||||
sw.Do(&v.has_samples);
|
sw.Do(&v.has_samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,13 +437,14 @@ u16 SPU::ReadVoiceRegister(u32 offset)
|
||||||
const u32 voice_index = (offset / 0x10); //((offset >> 4) & 0x1F);
|
const u32 voice_index = (offset / 0x10); //((offset >> 4) & 0x1F);
|
||||||
Assert(voice_index < 24);
|
Assert(voice_index < 24);
|
||||||
|
|
||||||
if (reg_index >= 6)
|
const Voice& voice = m_voices[voice_index];
|
||||||
|
if (reg_index >= 6 && voice.IsOn())
|
||||||
{
|
{
|
||||||
// adsr volume needs to be updated when reading
|
// adsr volume needs to be updated when reading
|
||||||
m_sample_event->InvokeEarly();
|
m_sample_event->InvokeEarly();
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_voices[voice_index].regs.index[reg_index];
|
return voice.regs.index[reg_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPU::WriteVoiceRegister(u32 offset, u16 value)
|
void SPU::WriteVoiceRegister(u32 offset, u16 value)
|
||||||
|
@ -728,14 +731,16 @@ void SPU::Voice::KeyOn()
|
||||||
regs.adsr_volume = 0;
|
regs.adsr_volume = 0;
|
||||||
has_samples = false;
|
has_samples = false;
|
||||||
SetADSRPhase(ADSRPhase::Attack);
|
SetADSRPhase(ADSRPhase::Attack);
|
||||||
|
adsr_ticks_remaining = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPU::Voice::KeyOff()
|
void SPU::Voice::KeyOff()
|
||||||
{
|
{
|
||||||
if (adsr_phase == ADSRPhase::Off)
|
if (adsr_phase == ADSRPhase::Off || adsr_phase == ADSRPhase::Release)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SetADSRPhase(ADSRPhase::Release);
|
SetADSRPhase(ADSRPhase::Release);
|
||||||
|
adsr_ticks_remaining = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase)
|
SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase)
|
||||||
|
@ -761,79 +766,140 @@ SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ADSRTableEntry
|
||||||
|
{
|
||||||
|
s32 ticks;
|
||||||
|
s32 step;
|
||||||
|
};
|
||||||
|
enum : u32
|
||||||
|
{
|
||||||
|
NUM_ADSR_TABLE_ENTRIES = 128,
|
||||||
|
NUM_ADSR_DIRECTIONS = 2 // increasing, decreasing
|
||||||
|
};
|
||||||
|
using ADSRTableEntries = std::array<std::array<ADSRTableEntry, NUM_ADSR_TABLE_ENTRIES>, NUM_ADSR_DIRECTIONS>;
|
||||||
|
|
||||||
|
static constexpr ADSRTableEntries ComputeADSRTableEntries()
|
||||||
|
{
|
||||||
|
ADSRTableEntries entries = {};
|
||||||
|
for (u32 decreasing = 0; decreasing < 2; decreasing++)
|
||||||
|
{
|
||||||
|
for (u32 rate = 0; rate < NUM_ADSR_TABLE_ENTRIES; rate++)
|
||||||
|
{
|
||||||
|
if (rate < 48)
|
||||||
|
{
|
||||||
|
entries[decreasing][rate].ticks = 1;
|
||||||
|
if (decreasing != 0)
|
||||||
|
entries[decreasing][rate].step = static_cast<s32>(static_cast<u32>(-8 + static_cast<s32>(rate & 3)) << (11 - (rate >> 2)));
|
||||||
|
else
|
||||||
|
entries[decreasing][rate].step = (7 - static_cast<s32>(rate & 3)) << (11 - (rate >> 2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entries[decreasing][rate].ticks = 1 << (static_cast<s32>(rate >> 2) - 11);
|
||||||
|
if (decreasing != 0)
|
||||||
|
entries[decreasing][rate].step = (-8 + static_cast<s32>(rate & 3));
|
||||||
|
else
|
||||||
|
entries[decreasing][rate].step = (7 - static_cast<s32>(rate & 3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr ADSRTableEntries s_adsr_table = ComputeADSRTableEntries();
|
||||||
|
|
||||||
void SPU::Voice::SetADSRPhase(ADSRPhase phase)
|
void SPU::Voice::SetADSRPhase(ADSRPhase phase)
|
||||||
{
|
{
|
||||||
adsr_phase = phase;
|
adsr_phase = phase;
|
||||||
switch (phase)
|
switch (phase)
|
||||||
{
|
{
|
||||||
case ADSRPhase::Off:
|
case ADSRPhase::Off:
|
||||||
adsr_target = {};
|
adsr_target = 0;
|
||||||
adsr_ticks = 0;
|
adsr_decreasing = false;
|
||||||
adsr_ticks_remaining = 0;
|
adsr_exponential = false;
|
||||||
adsr_step = 0;
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case ADSRPhase::Attack:
|
case ADSRPhase::Attack:
|
||||||
adsr_target.level = 32767; // 0 -> max
|
adsr_target = 32767; // 0 -> max
|
||||||
adsr_target.step = regs.adsr.attack_step;
|
adsr_decreasing = false;
|
||||||
adsr_target.shift = regs.adsr.attack_shift;
|
adsr_exponential = regs.adsr.attack_exponential;
|
||||||
adsr_target.decreasing = false;
|
adsr_rate = regs.adsr.attack_rate;
|
||||||
adsr_target.exponential = regs.adsr.attack_exponential;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ADSRPhase::Decay:
|
case ADSRPhase::Decay:
|
||||||
adsr_target.level = (u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800; // max -> sustain level
|
adsr_target = (u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800; // max -> sustain level
|
||||||
adsr_target.step = 0;
|
adsr_decreasing = true;
|
||||||
adsr_target.shift = regs.adsr.decay_shift;
|
adsr_exponential = true;
|
||||||
adsr_target.decreasing = true;
|
adsr_rate = regs.adsr.decay_rate_shr2 << 2;
|
||||||
adsr_target.exponential = true;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ADSRPhase::Sustain:
|
case ADSRPhase::Sustain:
|
||||||
adsr_target.level = 0;
|
adsr_target = 0;
|
||||||
adsr_target.step = regs.adsr.sustain_step;
|
adsr_decreasing = regs.adsr.sustain_direction_decrease;
|
||||||
adsr_target.shift = regs.adsr.sustain_shift;
|
adsr_exponential = regs.adsr.sustain_exponential;
|
||||||
adsr_target.decreasing = regs.adsr.sustain_direction_decrease;
|
adsr_rate = regs.adsr.sustain_rate;
|
||||||
adsr_target.exponential = regs.adsr.sustain_exponential;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ADSRPhase::Release:
|
case ADSRPhase::Release:
|
||||||
adsr_target.level = 0;
|
adsr_target = 0;
|
||||||
adsr_target.step = 0;
|
adsr_decreasing = true;
|
||||||
adsr_target.shift = regs.adsr.release_shift;
|
adsr_exponential = regs.adsr.release_exponential;
|
||||||
adsr_target.decreasing = true;
|
adsr_rate = regs.adsr.release_rate_shr2 << 2;
|
||||||
adsr_target.exponential = regs.adsr.release_exponential;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const s16 step = adsr_target.decreasing ? (-8 + adsr_target.step) : (7 - adsr_target.step);
|
|
||||||
adsr_ticks = 1 << std::max<s16>(0, static_cast<s16>(ZeroExtend16(adsr_target.shift)) - 11);
|
|
||||||
adsr_ticks_remaining = adsr_ticks;
|
|
||||||
adsr_step = step << std::max<s16>(0, 11 - static_cast<s16>(ZeroExtend16(adsr_target.shift)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPU::Voice::TickADSR()
|
void SPU::Voice::TickADSR()
|
||||||
{
|
{
|
||||||
adsr_ticks_remaining--;
|
adsr_ticks_remaining--;
|
||||||
if (adsr_ticks_remaining <= 0)
|
if (adsr_ticks_remaining > 0)
|
||||||
{
|
return;
|
||||||
const s32 new_volume = s32(regs.adsr_volume) + s32(adsr_step);
|
|
||||||
regs.adsr_volume = static_cast<s16>(std::clamp<s32>(new_volume, ADSR_MIN_VOLUME, ADSR_MAX_VOLUME));
|
|
||||||
|
|
||||||
const bool reached_target =
|
// set up for next tick
|
||||||
adsr_target.decreasing ? (new_volume <= adsr_target.level) : (new_volume >= adsr_target.level);
|
const ADSRTableEntry& table_entry = s_adsr_table[BoolToUInt8(adsr_decreasing)][adsr_rate];
|
||||||
if (adsr_phase != ADSRPhase::Sustain && reached_target)
|
|
||||||
|
s32 this_step = table_entry.step;
|
||||||
|
adsr_ticks_remaining = table_entry.ticks;
|
||||||
|
|
||||||
|
if (adsr_exponential)
|
||||||
{
|
{
|
||||||
// next phase
|
if (adsr_decreasing)
|
||||||
SetADSRPhase(GetNextADSRPhase(adsr_phase));
|
{
|
||||||
|
this_step = (this_step * regs.adsr_volume) >> 15;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
adsr_ticks_remaining = adsr_ticks;
|
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));
|
||||||
|
|
||||||
|
if (adsr_phase != ADSRPhase::Sustain)
|
||||||
|
{
|
||||||
|
const bool reached_target = adsr_decreasing ? (new_volume <= adsr_target) : (new_volume >= adsr_target);
|
||||||
|
if (reached_target)
|
||||||
|
SetADSRPhase(GetNextADSRPhase(adsr_phase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1063,7 +1129,8 @@ void SPU::EnsureCDAudioSpace(u32 remaining_frames)
|
||||||
{
|
{
|
||||||
if (m_cd_audio_buffer.IsEmpty())
|
if (m_cd_audio_buffer.IsEmpty())
|
||||||
{
|
{
|
||||||
// we want the audio to start playing at the right point, not a few cycles early, otherwise this'll cause sync issues.
|
// we want the audio to start playing at the right point, not a few cycles early, otherwise this'll cause sync
|
||||||
|
// issues.
|
||||||
m_sample_event->InvokeEarly();
|
m_sample_event->InvokeEarly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1153,7 +1220,7 @@ void SPU::DrawDebugStateWindow()
|
||||||
// headers
|
// headers
|
||||||
static constexpr std::array<const char*, NUM_COLUMNS> column_titles = {
|
static constexpr std::array<const char*, NUM_COLUMNS> column_titles = {
|
||||||
{"#", "InterpIndex", "SampleIndex", "CurAddr", "StartAddr", "RepeatAddr", "SampleRate", "VolLeft", "VolRight",
|
{"#", "InterpIndex", "SampleIndex", "CurAddr", "StartAddr", "RepeatAddr", "SampleRate", "VolLeft", "VolRight",
|
||||||
"ADSR", "ADSRPhase", "ADSRVol"}};
|
"ADSRPhase", "ADSRVol", "ADSRTicks"}};
|
||||||
static constexpr std::array<const char*, 5> adsr_phases = {{"Off", "Attack", "Decay", "Sustain", "Release"}};
|
static constexpr std::array<const char*, 5> adsr_phases = {{"Off", "Attack", "Decay", "Sustain", "Release"}};
|
||||||
for (u32 i = 0; i < NUM_COLUMNS; i++)
|
for (u32 i = 0; i < NUM_COLUMNS; i++)
|
||||||
{
|
{
|
||||||
|
@ -1184,12 +1251,12 @@ void SPU::DrawDebugStateWindow()
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.volume_right.bits));
|
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.volume_right.bits));
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
ImGui::TextColored(color, "%08X", v.regs.adsr.bits);
|
|
||||||
ImGui::NextColumn();
|
|
||||||
ImGui::TextColored(color, "%s", adsr_phases[static_cast<u8>(v.adsr_phase)]);
|
ImGui::TextColored(color, "%s", adsr_phases[static_cast<u8>(v.adsr_phase)]);
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
ImGui::TextColored(color, "%d", ZeroExtend32(v.regs.adsr_volume));
|
ImGui::TextColored(color, "%d", ZeroExtend32(v.regs.adsr_volume));
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
|
ImGui::TextColored(color, "%d", v.adsr_ticks_remaining);
|
||||||
|
ImGui::NextColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Columns(1);
|
ImGui::Columns(1);
|
||||||
|
|
|
@ -114,15 +114,13 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
BitField<u32, u8, 0, 4> sustain_level;
|
BitField<u32, u8, 0, 4> sustain_level;
|
||||||
BitField<u32, u8, 4, 4> decay_shift;
|
BitField<u32, u8, 4, 4> decay_rate_shr2;
|
||||||
BitField<u32, u8, 8, 2> attack_step;
|
BitField<u32, u8, 8, 7> attack_rate;
|
||||||
BitField<u32, u8, 10, 5> attack_shift;
|
|
||||||
BitField<u32, bool, 15, 1> attack_exponential;
|
BitField<u32, bool, 15, 1> attack_exponential;
|
||||||
|
|
||||||
BitField<u32, u8, 16, 5> release_shift;
|
BitField<u32, u8, 16, 5> release_rate_shr2;
|
||||||
BitField<u32, bool, 21, 1> release_exponential;
|
BitField<u32, bool, 21, 1> release_exponential;
|
||||||
BitField<u32, u8, 22, 2> sustain_step;
|
BitField<u32, u8, 22, 7> sustain_rate;
|
||||||
BitField<u32, u8, 24, 5> sustain_shift;
|
|
||||||
BitField<u32, bool, 30, 1> sustain_direction_decrease;
|
BitField<u32, bool, 30, 1> sustain_direction_decrease;
|
||||||
BitField<u32, bool, 31, 1> sustain_exponential;
|
BitField<u32, bool, 31, 1> sustain_exponential;
|
||||||
};
|
};
|
||||||
|
@ -214,15 +212,6 @@ private:
|
||||||
Release = 4
|
Release = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ADSRTarget
|
|
||||||
{
|
|
||||||
s32 level;
|
|
||||||
s16 step;
|
|
||||||
u8 shift;
|
|
||||||
bool decreasing;
|
|
||||||
bool exponential;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Voice
|
struct Voice
|
||||||
{
|
{
|
||||||
u16 current_address;
|
u16 current_address;
|
||||||
|
@ -234,11 +223,12 @@ private:
|
||||||
std::array<s32, 2> adpcm_last_samples;
|
std::array<s32, 2> adpcm_last_samples;
|
||||||
s32 last_amplitude;
|
s32 last_amplitude;
|
||||||
|
|
||||||
ADSRPhase adsr_phase;
|
|
||||||
ADSRTarget adsr_target;
|
|
||||||
TickCount adsr_ticks;
|
|
||||||
TickCount adsr_ticks_remaining;
|
TickCount adsr_ticks_remaining;
|
||||||
s16 adsr_step;
|
s32 adsr_target;
|
||||||
|
ADSRPhase adsr_phase;
|
||||||
|
u8 adsr_rate;
|
||||||
|
bool adsr_decreasing;
|
||||||
|
bool adsr_exponential;
|
||||||
bool has_samples;
|
bool has_samples;
|
||||||
|
|
||||||
bool IsOn() const { return adsr_phase != ADSRPhase::Off; }
|
bool IsOn() const { return adsr_phase != ADSRPhase::Off; }
|
||||||
|
|
Loading…
Reference in a new issue