SPU: Implement ADSR
This commit is contained in:
parent
3912e0e8d6
commit
a3446b8275
131
src/core/spu.cpp
131
src/core/spu.cpp
|
@ -283,7 +283,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
|
|||
Assert(voice_index < 24);
|
||||
|
||||
Voice& voice = m_voices[voice_index];
|
||||
if (voice.key_on)
|
||||
if (voice.IsOn())
|
||||
m_system->Synchronize();
|
||||
|
||||
switch (reg_index)
|
||||
|
@ -318,14 +318,14 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
|
|||
|
||||
case 0x08: // adsr low
|
||||
{
|
||||
Log_WarningPrintf("SPU voice %u ADSR low <- 0x%04X", voice_index, value);
|
||||
Log_DebugPrintf("SPU voice %u ADSR low <- 0x%04X", voice_index, value);
|
||||
voice.regs.adsr.bits_low = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0A: // adsr high
|
||||
{
|
||||
Log_WarningPrintf("SPU voice %u ADSR high <- 0x%04X", voice_index, value);
|
||||
Log_DebugPrintf("SPU voice %u ADSR high <- 0x%04X", voice_index, value);
|
||||
voice.regs.adsr.bits_high = value;
|
||||
}
|
||||
break;
|
||||
|
@ -404,14 +404,113 @@ void SPU::Execute(TickCount ticks)
|
|||
void SPU::Voice::KeyOn()
|
||||
{
|
||||
current_address = regs.adpcm_start_address;
|
||||
regs.adsr_volume = 0;
|
||||
has_samples = false;
|
||||
key_on = true;
|
||||
SetADSRPhase(ADSRPhase::Attack);
|
||||
}
|
||||
|
||||
void SPU::Voice::KeyOff()
|
||||
{
|
||||
has_samples = false;
|
||||
key_on = false;
|
||||
if (adsr_phase == ADSRPhase::Off)
|
||||
return;
|
||||
|
||||
SetADSRPhase(ADSRPhase::Release);
|
||||
}
|
||||
|
||||
SPU::ADSRPhase SPU::GetNextADSRPhase(ADSRPhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case ADSRPhase::Attack:
|
||||
// attack -> decay
|
||||
return ADSRPhase::Decay;
|
||||
|
||||
case ADSRPhase::Decay:
|
||||
// decay -> sustain
|
||||
return ADSRPhase::Sustain;
|
||||
|
||||
case ADSRPhase::Sustain:
|
||||
// sustain stays in sustain until key off
|
||||
return ADSRPhase::Sustain;
|
||||
|
||||
default:
|
||||
case ADSRPhase::Release:
|
||||
// end of release disables the voice
|
||||
return ADSRPhase::Off;
|
||||
}
|
||||
}
|
||||
|
||||
void SPU::Voice::SetADSRPhase(ADSRPhase phase)
|
||||
{
|
||||
adsr_phase = phase;
|
||||
switch (phase)
|
||||
{
|
||||
case ADSRPhase::Off:
|
||||
adsr_target = {};
|
||||
break;
|
||||
|
||||
case ADSRPhase::Attack:
|
||||
adsr_target.level = 32767; // 0 -> max
|
||||
adsr_target.step = regs.adsr.attack_step + 4;
|
||||
adsr_target.shift = regs.adsr.attack_shift;
|
||||
adsr_target.decreasing = false;
|
||||
adsr_target.exponential = regs.adsr.attack_exponential;
|
||||
break;
|
||||
|
||||
case ADSRPhase::Decay:
|
||||
adsr_target.level = (u32(regs.adsr.sustain_level.GetValue()) + 1) * 0x800; // max -> sustain level
|
||||
adsr_target.step = 0;
|
||||
adsr_target.shift = regs.adsr.decay_shift;
|
||||
adsr_target.decreasing = true;
|
||||
adsr_target.exponential = true;
|
||||
break;
|
||||
|
||||
case ADSRPhase::Sustain:
|
||||
adsr_target.level = regs.adsr.sustain_direction_decrease ? -1 : 1;
|
||||
adsr_target.step = 0;
|
||||
adsr_target.shift = regs.adsr.sustain_shift;
|
||||
adsr_target.decreasing = regs.adsr.sustain_direction_decrease;
|
||||
adsr_target.exponential = regs.adsr.sustain_exponential;
|
||||
break;
|
||||
|
||||
case ADSRPhase::Release:
|
||||
adsr_target.level = 0;
|
||||
adsr_target.step = 0;
|
||||
adsr_target.shift = regs.adsr.release_shift;
|
||||
adsr_target.decreasing = true;
|
||||
adsr_target.exponential = regs.adsr.release_exponential;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const s32 step = adsr_target.decreasing ? (-8 + adsr_target.step) : (7 - adsr_target.step);
|
||||
adsr_ticks = 1 << std::max<s16>(0, adsr_target.shift - 11);
|
||||
adsr_ticks_remaining = adsr_ticks;
|
||||
adsr_step = step << std::max<s16>(0, 11 - adsr_target.shift);
|
||||
}
|
||||
|
||||
void SPU::Voice::TickADSR()
|
||||
{
|
||||
adsr_ticks_remaining--;
|
||||
if (adsr_ticks_remaining <= 0)
|
||||
{
|
||||
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 =
|
||||
adsr_target.decreasing ? (new_volume <= adsr_target.level) : (new_volume >= adsr_target.level);
|
||||
if (adsr_phase != ADSRPhase::Sustain && reached_target)
|
||||
{
|
||||
// next phase
|
||||
SetADSRPhase(GetNextADSRPhase(adsr_phase));
|
||||
}
|
||||
else
|
||||
{
|
||||
adsr_ticks_remaining = adsr_ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SPU::Voice::DecodeBlock()
|
||||
|
@ -575,7 +674,7 @@ void SPU::DecodeADPCMBlock(const ADPCMBlock& block, SampleFormat out_samples[NUM
|
|||
std::tuple<SPU::SampleFormat, SPU::SampleFormat> SPU::SampleVoice(u32 voice_index)
|
||||
{
|
||||
Voice& voice = m_voices[voice_index];
|
||||
if (!voice.key_on)
|
||||
if (!voice.IsOn())
|
||||
return std::make_tuple<s16, s16>(0, 0);
|
||||
|
||||
if (!voice.has_samples)
|
||||
|
@ -624,10 +723,13 @@ std::tuple<SPU::SampleFormat, SPU::SampleFormat> SPU::SampleVoice(u32 voice_inde
|
|||
}
|
||||
|
||||
// TODO: Volume
|
||||
const float adsr_volume = S16ToFloat(voice.regs.adsr_volume);
|
||||
voice.TickADSR();
|
||||
|
||||
const s32 sample = voice.Interpolate();
|
||||
// s32 sample = voice.SampleBlock(voice.counter.sample_index);
|
||||
const s16 sample16 = Clamp16(sample);
|
||||
const float samplef = S16ToFloat(sample16);
|
||||
const float samplef = S16ToFloat(sample16) * adsr_volume;
|
||||
|
||||
// apply volume
|
||||
const float volume_left = S16ToFloat(voice.regs.volume_left.GetVolume());
|
||||
|
@ -672,7 +774,7 @@ void SPU::DrawDebugWindow()
|
|||
if (!m_debug_window_open)
|
||||
return;
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(700, 400), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(800, 400), ImGuiCond_FirstUseEver);
|
||||
if (!ImGui::Begin("SPU State", &m_debug_window_open))
|
||||
{
|
||||
ImGui::End();
|
||||
|
@ -682,14 +784,15 @@ void SPU::DrawDebugWindow()
|
|||
// draw voice states
|
||||
if (ImGui::CollapsingHeader("Voice State", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
{
|
||||
static constexpr u32 NUM_COLUMNS = 11;
|
||||
static constexpr u32 NUM_COLUMNS = 12;
|
||||
|
||||
ImGui::Columns(NUM_COLUMNS);
|
||||
|
||||
// headers
|
||||
static constexpr std::array<const char*, NUM_COLUMNS> column_titles = {
|
||||
{"#", "InterpIndex", "SampleIndex", "CurAddr", "StartAddr", "RepeatAddr", "SampleRate", "VolLeft", "VolRight",
|
||||
"ADSR", "ADSRVol"}};
|
||||
"ADSR", "ADSRPhase", "ADSRVol"}};
|
||||
static constexpr std::array<const char*, 5> adsr_phases = {{"Off", "Attack", "Decay", "Sustain", "Release"}};
|
||||
for (u32 i = 0; i < NUM_COLUMNS; i++)
|
||||
{
|
||||
ImGui::TextUnformatted(column_titles[i]);
|
||||
|
@ -700,7 +803,7 @@ void SPU::DrawDebugWindow()
|
|||
for (u32 voice_index = 0; voice_index < NUM_VOICES; voice_index++)
|
||||
{
|
||||
const Voice& v = m_voices[voice_index];
|
||||
ImVec4 color = v.key_on ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
ImVec4 color = v.IsOn() ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
ImGui::TextColored(color, "%u", ZeroExtend32(voice_index));
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.interpolation_index.GetValue()));
|
||||
|
@ -721,7 +824,9 @@ void SPU::DrawDebugWindow()
|
|||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%08X", v.regs.adsr.bits);
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adsr_volume));
|
||||
ImGui::TextColored(color, adsr_phases[static_cast<u8>(v.adsr_phase)]);
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextColored(color, "%d", ZeroExtend32(v.regs.adsr_volume));
|
||||
ImGui::NextColumn();
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,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;
|
||||
|
||||
enum class RAMTransferMode : u8
|
||||
{
|
||||
|
@ -99,7 +101,7 @@ private:
|
|||
BitField<u32, u8, 0, 4> sustain_level;
|
||||
BitField<u32, u8, 4, 4> decay_shift;
|
||||
BitField<u32, u8, 8, 2> attack_step;
|
||||
BitField<u32, u8, 10, 6> attack_shift;
|
||||
BitField<u32, u8, 10, 5> attack_shift;
|
||||
BitField<u32, bool, 15, 1> attack_exponential;
|
||||
|
||||
BitField<u32, u8, 16, 5> release_shift;
|
||||
|
@ -140,7 +142,7 @@ private:
|
|||
u16 adpcm_start_address; // multiply by 8
|
||||
|
||||
ADSRRegister adsr;
|
||||
u16 adsr_volume;
|
||||
s16 adsr_volume;
|
||||
|
||||
u16 adpcm_repeat_address; // multiply by 8
|
||||
};
|
||||
|
@ -187,6 +189,24 @@ private:
|
|||
u8 GetNibble(u32 index) const { return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; }
|
||||
};
|
||||
|
||||
enum class ADSRPhase : u8
|
||||
{
|
||||
Off = 0,
|
||||
Attack = 1,
|
||||
Decay = 2,
|
||||
Sustain = 3,
|
||||
Release = 4
|
||||
};
|
||||
|
||||
struct ADSRTarget
|
||||
{
|
||||
s32 level;
|
||||
s16 step;
|
||||
u8 shift;
|
||||
bool decreasing;
|
||||
bool exponential;
|
||||
};
|
||||
|
||||
struct Voice
|
||||
{
|
||||
u16 current_address;
|
||||
|
@ -197,8 +217,14 @@ private:
|
|||
std::array<SampleFormat, 3> previous_block_last_samples;
|
||||
std::array<s32, 2> adpcm_state;
|
||||
|
||||
bool has_samples;
|
||||
bool key_on;
|
||||
ADSRPhase adsr_phase;
|
||||
ADSRTarget adsr_target;
|
||||
TickCount adsr_ticks;
|
||||
TickCount adsr_ticks_remaining;
|
||||
s16 adsr_step;
|
||||
bool has_samples;
|
||||
|
||||
bool IsOn() const { return adsr_phase != ADSRPhase::Off; }
|
||||
|
||||
void KeyOn();
|
||||
void KeyOff();
|
||||
|
@ -206,8 +232,16 @@ private:
|
|||
void DecodeBlock();
|
||||
SampleFormat SampleBlock(s32 index) const;
|
||||
s16 Interpolate() const;
|
||||
|
||||
// Switches to the specified phase, filling in target.
|
||||
void SetADSRPhase(ADSRPhase phase);
|
||||
|
||||
// Updates the ADSR volume/phase.
|
||||
void TickADSR();
|
||||
};
|
||||
|
||||
static ADSRPhase GetNextADSRPhase(ADSRPhase phase);
|
||||
|
||||
u16 ReadVoiceRegister(u32 offset);
|
||||
void WriteVoiceRegister(u32 offset, u16 value);
|
||||
|
||||
|
|
Loading…
Reference in New Issue