Fix wrong waveform and random crackles in N163 audio #294
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This pull request aims to fix N163 audio bugs I've discovered.
Changes in this PR:
Background
N163 playback, like other chips, uses
Blip_Synth
andBlip_Buffer
for resampling. Each sound chip has its own Blip_Synth, with an independent volume control and treble filter (a set of impulse responses).Chip emulators create audio by calling
Blip_Synth::update(time_clocks, amplitude)
. Blip_Synth calculatesdelta = amplitude - previous amplitude
, then inserts an impulse of sizedelta
into aBlip_Buffer
.Blip_Synth
performs resampling by convertingtime_clocks
into fractional samples, then adding a fractionally delayed impulse to Blip_Buffer's difference array starting atint(sample timestamp)
. (This introduces a few samples of delay, from the impulse start to center point.)Each frame lasts an integer number of clocks, but a fractional number of samples. At the end of a frame,
Blip_Buffer::read_samples()
finds the fractional sample where the next frame begins, generates audio up toint(sample timestamp)
using a running sum of Blip_Buffer's difference array (with optional highpass DC removal), then outputs and discardsint(sample timestamp)
samples (keeping the fractional part ofsample timestamp
, so the next frame's impulses begin midway through the new first sample of the buffer).Bug
Dn-FT outputs audio from a global Blip_Buffer,
CMixer::BlipBuffer
. In order to apply (approximately emulated) audio filtering,CN163
has its own Blip_Buffer,CN163::m_BlipN163
.When we begin playback, we call
CAPU::Reset()
→CMixer::ClearBuffer()
andCN163::Reset()
, which both resetBlip_Buffer::offset_
to 0. When loading a document with a nonstandard tick rate or pressing F12, CSoundGen and CAPU's frames can desync, causing CAPU to get reset outside ofCAPU::EndFrame()
boundaries.The bug in this case was that when FamiTracker called
CN163::Reset()
without ending the previous frame normally,CN163
failed to clear its "time since frame begin" field (CN163::m_iTime
). Normally this would result in steps being delayed slightly for the next frame. But CN163 has its own Blip_Buffer, so each subsequent frame starts and stops at a different fractional sample than the global Blip_Buffer, resulting in different numbers of completed samples being generated each frame.These frames of incorrect length get mixed into the global Blip_Buffer when
CN163::EndFrame()
callsOutput.mix_samples_raw()
(whereOutput
points toCMixer::BlipBuffer
). Whenever a N163 frame doesn't match the length of its corresponding global frame, it can produce 1-sample overlaps or gaps at frame borders, resulting in audio crackling.Fix
Make
CN163::Reset()
setm_iTime = 0
, so the first N163 frame adds samples at the correct time, has the correct length, and all subsequent frames have the correct phase relative to the global Blip_Buffer.Does CN163 need to have a separate Blip_Buffer? It's complicated. It's used to perform a first-order lowpass filter at 12 kHz, but this is close enough to Nyquist (at the usual sampling rate of 44-48 kHz) to have noticeable frequency warping. I don't know if this is hardware-accurate or an attempt to filter out switching noise.