mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-23 06:15:38 +00:00
SPU: Always inline SPU::ExecuteVoice
This commit is contained in:
parent
245edd94d7
commit
325f83065c
290
src/core/spu.cpp
290
src/core/spu.cpp
|
@ -689,150 +689,6 @@ void SPU::IncrementCaptureBufferPosition()
|
||||||
m_SPUSTAT.second_half_capture_buffer = m_capture_buffer_position >= (CAPTURE_BUFFER_SIZE_PER_CHANNEL / 2);
|
m_SPUSTAT.second_half_capture_buffer = m_capture_buffer_position >= (CAPTURE_BUFFER_SIZE_PER_CHANNEL / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPU::Execute(TickCount ticks)
|
|
||||||
{
|
|
||||||
u32 remaining_frames;
|
|
||||||
if (g_settings.cpu_overclock_active)
|
|
||||||
{
|
|
||||||
// (X * D) / N / 768 -> (X * D) / (N * 768)
|
|
||||||
const u64 num = (static_cast<u64>(ticks) * g_settings.cpu_overclock_denominator) + static_cast<u32>(m_ticks_carry);
|
|
||||||
remaining_frames = static_cast<u32>(num / m_cpu_tick_divider);
|
|
||||||
m_ticks_carry = static_cast<TickCount>(num % m_cpu_tick_divider);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
remaining_frames = static_cast<u32>((ticks + m_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK);
|
|
||||||
m_ticks_carry = (ticks + m_ticks_carry) % SYSCLK_TICKS_PER_SPU_TICK;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (remaining_frames > 0)
|
|
||||||
{
|
|
||||||
AudioStream* const output_stream = g_host_interface->GetAudioStream();
|
|
||||||
s16* output_frame_start;
|
|
||||||
u32 output_frame_space = remaining_frames;
|
|
||||||
output_stream->BeginWrite(&output_frame_start, &output_frame_space);
|
|
||||||
|
|
||||||
s16* output_frame = output_frame_start;
|
|
||||||
const u32 frames_in_this_batch = std::min(remaining_frames, output_frame_space);
|
|
||||||
for (u32 i = 0; i < frames_in_this_batch; i++)
|
|
||||||
{
|
|
||||||
s32 left_sum = 0;
|
|
||||||
s32 right_sum = 0;
|
|
||||||
s32 reverb_in_left = 0;
|
|
||||||
s32 reverb_in_right = 0;
|
|
||||||
|
|
||||||
u32 key_on_register = m_key_on_register;
|
|
||||||
m_key_on_register = 0;
|
|
||||||
u32 key_off_register = m_key_off_register;
|
|
||||||
m_key_off_register = 0;
|
|
||||||
u32 reverb_on_register = m_reverb_on_register;
|
|
||||||
|
|
||||||
for (u32 voice = 0; voice < NUM_VOICES; voice++)
|
|
||||||
{
|
|
||||||
const auto [left, right] = SampleVoice(voice);
|
|
||||||
left_sum += left;
|
|
||||||
right_sum += right;
|
|
||||||
|
|
||||||
if (reverb_on_register & 1u)
|
|
||||||
{
|
|
||||||
reverb_in_left += left;
|
|
||||||
reverb_in_right += right;
|
|
||||||
}
|
|
||||||
reverb_on_register >>= 1;
|
|
||||||
|
|
||||||
if (key_off_register & 1u)
|
|
||||||
m_voices[voice].KeyOff();
|
|
||||||
key_off_register >>= 1;
|
|
||||||
|
|
||||||
if (key_on_register & 1u)
|
|
||||||
{
|
|
||||||
m_endx_register &= ~(1u << voice);
|
|
||||||
m_voices[voice].KeyOn();
|
|
||||||
}
|
|
||||||
key_on_register >>= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_SPUCNT.mute_n)
|
|
||||||
{
|
|
||||||
left_sum = 0;
|
|
||||||
right_sum = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update noise once per frame.
|
|
||||||
UpdateNoise();
|
|
||||||
|
|
||||||
// Mix in CD audio.
|
|
||||||
const auto [cd_audio_left, cd_audio_right] = g_cdrom.GetAudioFrame();
|
|
||||||
if (m_SPUCNT.cd_audio_enable)
|
|
||||||
{
|
|
||||||
const s32 cd_audio_volume_left = ApplyVolume(s32(cd_audio_left), m_cd_audio_volume_left);
|
|
||||||
const s32 cd_audio_volume_right = ApplyVolume(s32(cd_audio_right), m_cd_audio_volume_right);
|
|
||||||
|
|
||||||
left_sum += cd_audio_volume_left;
|
|
||||||
right_sum += cd_audio_volume_right;
|
|
||||||
|
|
||||||
if (m_SPUCNT.cd_audio_reverb)
|
|
||||||
{
|
|
||||||
reverb_in_left += cd_audio_volume_left;
|
|
||||||
reverb_in_right += cd_audio_volume_right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute reverb.
|
|
||||||
s32 reverb_out_left, reverb_out_right;
|
|
||||||
ProcessReverb(static_cast<s16>(Clamp16(reverb_in_left)), static_cast<s16>(Clamp16(reverb_in_right)),
|
|
||||||
&reverb_out_left, &reverb_out_right);
|
|
||||||
|
|
||||||
// Mix in reverb.
|
|
||||||
left_sum += reverb_out_left;
|
|
||||||
right_sum += reverb_out_right;
|
|
||||||
|
|
||||||
// Apply main volume after clamping. A maximum volume should not overflow here because both are 16-bit values.
|
|
||||||
*(output_frame++) = static_cast<s16>(ApplyVolume(Clamp16(left_sum), m_main_volume_left.current_level));
|
|
||||||
*(output_frame++) = static_cast<s16>(ApplyVolume(Clamp16(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);
|
|
||||||
WriteToCaptureBuffer(1, cd_audio_right);
|
|
||||||
WriteToCaptureBuffer(2, static_cast<s16>(Clamp16(m_voices[1].last_volume)));
|
|
||||||
WriteToCaptureBuffer(3, static_cast<s16>(Clamp16(m_voices[3].last_volume)));
|
|
||||||
IncrementCaptureBufferPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_dump_writer)
|
|
||||||
m_dump_writer->WriteFrames(output_frame_start, frames_in_this_batch);
|
|
||||||
|
|
||||||
output_stream->EndWrite(frames_in_this_batch);
|
|
||||||
remaining_frames -= frames_in_this_batch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPU::UpdateEventInterval()
|
|
||||||
{
|
|
||||||
// Don't generate more than the audio buffer since in a single slice, otherwise we'll both overflow the buffers when
|
|
||||||
// we do write it, and the audio thread will underflow since it won't have enough data it the game isn't messing with
|
|
||||||
// the SPU state.
|
|
||||||
const u32 max_slice_frames = g_host_interface->GetAudioStream()->GetBufferSize();
|
|
||||||
|
|
||||||
// TODO: Make this predict how long until the interrupt will be hit instead...
|
|
||||||
const u32 interval = (m_SPUCNT.enable && m_SPUCNT.irq9_enable) ? 1 : max_slice_frames;
|
|
||||||
const TickCount interval_ticks = static_cast<TickCount>(interval) * m_cpu_ticks_per_spu_tick;
|
|
||||||
if (m_tick_event->IsActive() && m_tick_event->GetInterval() == interval_ticks)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Ensure all pending ticks have been executed, since we won't get them back after rescheduling.
|
|
||||||
m_tick_event->InvokeEarly(true);
|
|
||||||
m_tick_event->SetInterval(interval_ticks);
|
|
||||||
|
|
||||||
TickCount downcount = interval_ticks;
|
|
||||||
if (!g_settings.cpu_overclock_active)
|
|
||||||
downcount -= m_ticks_carry;
|
|
||||||
|
|
||||||
m_tick_event->Schedule(downcount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPU::ExecuteTransfer(TickCount ticks)
|
void SPU::ExecuteTransfer(TickCount ticks)
|
||||||
{
|
{
|
||||||
const RAMTransferMode mode = m_SPUCNT.ram_transfer_mode;
|
const RAMTransferMode mode = m_SPUCNT.ram_transfer_mode;
|
||||||
|
@ -1442,7 +1298,7 @@ void SPU::ReadADPCMBlock(u16 address, ADPCMBlock* block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
|
ALWAYS_INLINE_RELEASE std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
|
||||||
{
|
{
|
||||||
Voice& voice = m_voices[voice_index];
|
Voice& voice = m_voices[voice_index];
|
||||||
if (!voice.IsOn() && !m_SPUCNT.irq9_enable)
|
if (!voice.IsOn() && !m_SPUCNT.irq9_enable)
|
||||||
|
@ -1764,6 +1620,150 @@ void SPU::ProcessReverb(s16 left_in, s16 right_in, s32* left_out, s32* right_out
|
||||||
s_last_reverb_output[1] = *right_out = ApplyVolume(out[1], m_reverb_registers.vROUT);
|
s_last_reverb_output[1] = *right_out = ApplyVolume(out[1], m_reverb_registers.vROUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SPU::Execute(TickCount ticks)
|
||||||
|
{
|
||||||
|
u32 remaining_frames;
|
||||||
|
if (g_settings.cpu_overclock_active)
|
||||||
|
{
|
||||||
|
// (X * D) / N / 768 -> (X * D) / (N * 768)
|
||||||
|
const u64 num = (static_cast<u64>(ticks) * g_settings.cpu_overclock_denominator) + static_cast<u32>(m_ticks_carry);
|
||||||
|
remaining_frames = static_cast<u32>(num / m_cpu_tick_divider);
|
||||||
|
m_ticks_carry = static_cast<TickCount>(num % m_cpu_tick_divider);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remaining_frames = static_cast<u32>((ticks + m_ticks_carry) / SYSCLK_TICKS_PER_SPU_TICK);
|
||||||
|
m_ticks_carry = (ticks + m_ticks_carry) % SYSCLK_TICKS_PER_SPU_TICK;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (remaining_frames > 0)
|
||||||
|
{
|
||||||
|
AudioStream* const output_stream = g_host_interface->GetAudioStream();
|
||||||
|
s16* output_frame_start;
|
||||||
|
u32 output_frame_space = remaining_frames;
|
||||||
|
output_stream->BeginWrite(&output_frame_start, &output_frame_space);
|
||||||
|
|
||||||
|
s16* output_frame = output_frame_start;
|
||||||
|
const u32 frames_in_this_batch = std::min(remaining_frames, output_frame_space);
|
||||||
|
for (u32 i = 0; i < frames_in_this_batch; i++)
|
||||||
|
{
|
||||||
|
s32 left_sum = 0;
|
||||||
|
s32 right_sum = 0;
|
||||||
|
s32 reverb_in_left = 0;
|
||||||
|
s32 reverb_in_right = 0;
|
||||||
|
|
||||||
|
u32 key_on_register = m_key_on_register;
|
||||||
|
m_key_on_register = 0;
|
||||||
|
u32 key_off_register = m_key_off_register;
|
||||||
|
m_key_off_register = 0;
|
||||||
|
u32 reverb_on_register = m_reverb_on_register;
|
||||||
|
|
||||||
|
for (u32 voice = 0; voice < NUM_VOICES; voice++)
|
||||||
|
{
|
||||||
|
const auto [left, right] = SampleVoice(voice);
|
||||||
|
left_sum += left;
|
||||||
|
right_sum += right;
|
||||||
|
|
||||||
|
if (reverb_on_register & 1u)
|
||||||
|
{
|
||||||
|
reverb_in_left += left;
|
||||||
|
reverb_in_right += right;
|
||||||
|
}
|
||||||
|
reverb_on_register >>= 1;
|
||||||
|
|
||||||
|
if (key_off_register & 1u)
|
||||||
|
m_voices[voice].KeyOff();
|
||||||
|
key_off_register >>= 1;
|
||||||
|
|
||||||
|
if (key_on_register & 1u)
|
||||||
|
{
|
||||||
|
m_endx_register &= ~(1u << voice);
|
||||||
|
m_voices[voice].KeyOn();
|
||||||
|
}
|
||||||
|
key_on_register >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_SPUCNT.mute_n)
|
||||||
|
{
|
||||||
|
left_sum = 0;
|
||||||
|
right_sum = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update noise once per frame.
|
||||||
|
UpdateNoise();
|
||||||
|
|
||||||
|
// Mix in CD audio.
|
||||||
|
const auto [cd_audio_left, cd_audio_right] = g_cdrom.GetAudioFrame();
|
||||||
|
if (m_SPUCNT.cd_audio_enable)
|
||||||
|
{
|
||||||
|
const s32 cd_audio_volume_left = ApplyVolume(s32(cd_audio_left), m_cd_audio_volume_left);
|
||||||
|
const s32 cd_audio_volume_right = ApplyVolume(s32(cd_audio_right), m_cd_audio_volume_right);
|
||||||
|
|
||||||
|
left_sum += cd_audio_volume_left;
|
||||||
|
right_sum += cd_audio_volume_right;
|
||||||
|
|
||||||
|
if (m_SPUCNT.cd_audio_reverb)
|
||||||
|
{
|
||||||
|
reverb_in_left += cd_audio_volume_left;
|
||||||
|
reverb_in_right += cd_audio_volume_right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute reverb.
|
||||||
|
s32 reverb_out_left, reverb_out_right;
|
||||||
|
ProcessReverb(static_cast<s16>(Clamp16(reverb_in_left)), static_cast<s16>(Clamp16(reverb_in_right)),
|
||||||
|
&reverb_out_left, &reverb_out_right);
|
||||||
|
|
||||||
|
// Mix in reverb.
|
||||||
|
left_sum += reverb_out_left;
|
||||||
|
right_sum += reverb_out_right;
|
||||||
|
|
||||||
|
// Apply main volume after clamping. A maximum volume should not overflow here because both are 16-bit values.
|
||||||
|
*(output_frame++) = static_cast<s16>(ApplyVolume(Clamp16(left_sum), m_main_volume_left.current_level));
|
||||||
|
*(output_frame++) = static_cast<s16>(ApplyVolume(Clamp16(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);
|
||||||
|
WriteToCaptureBuffer(1, cd_audio_right);
|
||||||
|
WriteToCaptureBuffer(2, static_cast<s16>(Clamp16(m_voices[1].last_volume)));
|
||||||
|
WriteToCaptureBuffer(3, static_cast<s16>(Clamp16(m_voices[3].last_volume)));
|
||||||
|
IncrementCaptureBufferPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_dump_writer)
|
||||||
|
m_dump_writer->WriteFrames(output_frame_start, frames_in_this_batch);
|
||||||
|
|
||||||
|
output_stream->EndWrite(frames_in_this_batch);
|
||||||
|
remaining_frames -= frames_in_this_batch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SPU::UpdateEventInterval()
|
||||||
|
{
|
||||||
|
// Don't generate more than the audio buffer since in a single slice, otherwise we'll both overflow the buffers when
|
||||||
|
// we do write it, and the audio thread will underflow since it won't have enough data it the game isn't messing with
|
||||||
|
// the SPU state.
|
||||||
|
const u32 max_slice_frames = g_host_interface->GetAudioStream()->GetBufferSize();
|
||||||
|
|
||||||
|
// TODO: Make this predict how long until the interrupt will be hit instead...
|
||||||
|
const u32 interval = (m_SPUCNT.enable && m_SPUCNT.irq9_enable) ? 1 : max_slice_frames;
|
||||||
|
const TickCount interval_ticks = static_cast<TickCount>(interval) * m_cpu_ticks_per_spu_tick;
|
||||||
|
if (m_tick_event->IsActive() && m_tick_event->GetInterval() == interval_ticks)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ensure all pending ticks have been executed, since we won't get them back after rescheduling.
|
||||||
|
m_tick_event->InvokeEarly(true);
|
||||||
|
m_tick_event->SetInterval(interval_ticks);
|
||||||
|
|
||||||
|
TickCount downcount = interval_ticks;
|
||||||
|
if (!g_settings.cpu_overclock_active)
|
||||||
|
downcount -= m_ticks_carry;
|
||||||
|
|
||||||
|
m_tick_event->Schedule(downcount);
|
||||||
|
}
|
||||||
|
|
||||||
void SPU::DrawDebugStateWindow()
|
void SPU::DrawDebugStateWindow()
|
||||||
{
|
{
|
||||||
#ifdef WITH_IMGUI
|
#ifdef WITH_IMGUI
|
||||||
|
|
Loading…
Reference in a new issue