Context: I'm a software engineer with very little exposure into the core principles of audio engineering. I have an open source plugin that plays audio in response to application & user behavior. One feature I've been working on is to apply a low-pass filter to music based on user settings to give the music a 'muffled' effect. For example, the music could become 'muffled' when entering the settings of the application. My plugin relies on the audio library NAudio to perform most of its audio-related operations.
Problem: When the audio is transitioning into & off of the filter, its very common for there to be a jarring "popping" sound. Does anyone know a clear solution to this "popping" effect?
Most resources I've read indicate too harsh of a transition, but nothing I tried on this front seems to have resolved the issue.
I made a reddit post & tried the solutions offered there. There's a walk-through of the original logic there for those interested. The class responsible for this behavior can be found here.
I've tried fading the filter from/to 20khz with the popping simply cropping in toward the end of the fade, what I'm assuming is the more audible range.
As suggested by one user in the reddit post, I attempted to create two frequency groups, each residing on each side of the filter threshold & then mix them back into the audio buffer. As time progresses, one group is faded out & the other is faded in, depending if the 'muffle' effect is enabled.
I established the low & high filters for each group:
_lowPassFilters = new BiQuadFilter[provider.WaveFormat.Channels];
_highPassFilters = new BiQuadFilter[provider.WaveFormat.Channels];
for (var i = 0; i < provider.WaveFormat.Channels; i++)
{
_lowPassFilters[i] = BiQuadFilter.LowPassFilter(
WaveFormat.SampleRate, muffledLowerBound, muffledBandwidth);
_highPassFilters[i] = BiQuadFilter.HighPassFilter(
WaveFormat.SampleRate, muffledUpperBound, muffledBandwidth);
}
The code responsible for calculating the cut-off/threshold frequency & volume for each sample on fade in:
var fadedCutOff = _upperBound - _muffleCutOffFrequencyIncrement * _muffleFadeSamplePosition;
var lowFilterVolume = _muffleFadeSamplePosition / (float)_muffleFadeSampleCount;
ApplyFadedMuffle(buffer, offset, fadedCutOff, lowFilterVolume, ref sampleIndex);
The code responsible for calculating the cut-off/threshold frequency & volume for each sample on fade out:
var fadedCutOff = _lowerBound + _muffleCutOffFrequencyIncrement * _muffleFadeSamplePosition;
var lowFilterVolume = 1 - _muffleFadeSamplePosition / (float)_muffleFadeSampleCount;
ApplyFadedMuffle(buffer, offset, fadedCutOff, lowFilterVolume, ref sampleIndex);
And the method that applies the filters to the audio stream:
private void ApplyFadedMuffle(float[] buffer, int offset, float fadedCutOff, float lowPassVolume, ref int sampleIndex)
{
var highPassVolume = 1 - lowPassVolume;
for (var i = 0; i < WaveFormat.Channels; i++)
{
var lowFilter = _lowPassFilters[i];
var highFilter = _highPassFilters[i];
lowFilter.SetLowPassFilter(WaveFormat.SampleRate, fadedCutOff, _bandwidth);
highFilter.SetHighPassFilter(WaveFormat.SampleRate, fadedCutOff, _bandwidth);
var bufferIndex = offset + sampleIndex++;
var value = buffer[bufferIndex];
var lowPassValue = lowFilter.Transform(value) * lowPassVolume;
var highPassValue = highFilter.Transform(value) * highPassVolume;
buffer[bufferIndex] = lowPassValue + highPassValue;
}
}
I also tried a variation of the above method where instead of having a high filter applied, I simply mix in the original audio sample multiplied by the the same volume as before:
var highPassValue = value * highPassVolume;
I still experienced the same popping with the above solutions, with some minor audio distortions added during the fade.
While "fading in" the muffle, I was applying the volume, then the filter. Once fully faded in, I reversed & applied the filter, then the volume.
Switching the order when fully applied fixed the issue.