DSPHLE/Zelda: use precise 32-step volume ramping

Whilst the 80-step approach theoretically improves ramping smoothness,
in practice it causes accumulating rounding errors because the delta
value is not always a multiple of the buffer size.

Also pull the step computation into AddBuffersWithVolumeRamp() so that
all ramping related math is in the same place.
This commit is contained in:
Tillmann Karras 2024-01-14 02:23:01 +00:00
parent 4f5e3674e1
commit 0233286729
2 changed files with 25 additions and 19 deletions

View File

@ -1249,8 +1249,7 @@ void ZeldaAudioRenderer::AddVoice(u16 voice_id)
};
for (const auto& buffer : buffers)
{
AddBuffersWithVolumeRamp(buffer.buffer, input_samples, buffer.volume << 16,
(buffer.volume_delta << 16) / (s32)buffer.buffer.size());
AddBuffersWithVolumeRamp(buffer.buffer, input_samples, buffer.volume, buffer.volume_delta);
}
vpb.dolby_volume_current = vpb.dolby_volume_target;
@ -1285,13 +1284,11 @@ void ZeldaAudioRenderer::AddVoice(u16 voice_id)
else
volume_delta = vpb.channels[i].target_volume - vpb.channels[i].current_volume;
s32 volume_step = (volume_delta << 16) / (s32)input_samples.size(); // In 1.31 format.
// TODO: The last value of each channel structure is used to
// determine whether a channel should be skipped or not. Not
// implemented yet.
if (!vpb.channels[i].current_volume && !volume_step)
if (!vpb.channels[i].current_volume && !volume_delta)
continue;
MixingBuffer* dst_buffer = BufferForID(vpb.channels[i].id);
@ -1303,9 +1300,9 @@ void ZeldaAudioRenderer::AddVoice(u16 voice_id)
continue;
}
s32 new_volume = AddBuffersWithVolumeRamp(*dst_buffer, input_samples,
vpb.channels[i].current_volume << 16, volume_step);
vpb.channels[i].current_volume = new_volume >> 16;
s16 new_volume = AddBuffersWithVolumeRamp(*dst_buffer, input_samples,
vpb.channels[i].current_volume, volume_delta);
vpb.channels[i].current_volume = new_volume;
}
}

View File

@ -79,24 +79,33 @@ private:
ApplyVolumeInPlace<N, 4>(buf, vol);
}
// Mixes two buffers together while applying a volume to one of them. The
// volume ramps up/down in N steps using the provided step delta value.
//
// Note: On a real GC, the stepping happens in 32 steps instead. But hey,
// we can do better here with very low risk. Why not? :)
s32 AddBuffersWithVolumeRamp(MixingBuffer& dst, const MixingBuffer& src, s32 vol,
s32 step)
// Mixes two buffers together while applying a volume to one of them.
// We try to match LLE, hence the slightly odd ramping.
s16 AddBuffersWithVolumeRamp(MixingBuffer& dst, const MixingBuffer& src, s16 start_volume,
s16 delta)
{
if (!vol && !step)
return vol;
if (!start_volume && !delta)
return start_volume;
for (size_t i = 0; i < 0x50; ++i)
// The delta is applied in 32 steps over the first 64 samples.
s32 vol = start_volume << 16;
s32 step = delta << (16 - 5);
for (size_t i = 0; i < 0x40;)
{
dst[i] += ((vol >> 16) * src[i]) >> 16;
++i;
dst[i] += ((vol >> 16) * src[i]) >> 16;
++i;
vol += step;
}
return vol;
// The last 16 samples are mixed at the target volume.
for (size_t i = 0x40; i < 0x50; ++i)
{
dst[i] += ((vol >> 16) * src[i]) >> 16;
}
return vol >> 16;
}
// Does not use std::array because it needs to be able to process partial