I’ve got a frequency slider here controlling the HP filter. I put smoothing in because when I rapidly altered the frequency slider, there was a scrubbing noise with the audio.
Below you can see I am setting the HP filter cuttoff just outside the main loop in the processBlock(). I’m wondering if this is the best place.
I fixed the scrubbing noise by introducing some smoothing.
These are the settings in my prepareToPlay(). I landed on 0.001 after some experimentation.
Its Ok but not ideal because it takes a little time to get to the new frequency, especially noticeable if I change the frequency from 20000 Hz down to 20Hz rapidly.
Whats your approach to smoothing a frequency that controls a HP filter, can my method be improved?
juce::SmoothedValue<float> mFrequencySmoothed;
prepareToPlay()
mFrequencySmoothed.reset(sampleRate, 0.001);
mFrequencySmoothed.setCurrentAndTargetValue(get_mFrequency_value());
...
processBlock()
...
float frequencyValue = apvts.getRawParameterValue("mFrequency")->load();
mFrequencySmoothed.setTargetValue(frequencyValue);
float smoothedFrequency = mFrequencySmoothed.getNextValue();
set_mFrequency_value(smoothedFrequency); //atomic float
mFilter_highpass.setCutoffFrequency(//based on the nextvalue of smoothedFrequency);
//main loop
for (int channel = 0; channel < totalNumInputChannels; ++channel)
{
auto* channelData = buffer.getWritePointer(channel);
...
I reset the filter if the frequency changes more than 2 octaves.
Perhaps you can smooth (if changes are small) and reset (if changes are large). I don’t do smoothing because of some high order filters.
you could crossfade between 2 parallel processors whenever smoothing occurs and one of them always has the updated frequency. you can reset the filter at the start because it hasn’t faded in yet anyway, so it can have almost any desired speed without fear of breaking out or being unsmooth. only problem is that it is a slightly different sound than actually moving a filter.
another thing to consider here is to make a pitch parameter instead of a frequency parameter, so you can smoothen pitch values instead of frequency ones. that makes drifts from 20k to 20 more reasonable, because it’s just a fade between 136 and 24 or something like that (forgot the exact values)
@Mrugalla @zsliu98
Is the mFilter_highpass.setCutoffFrequency() something that could be done in my process loop (like below), or is that not advisable? (I am processing the filter on a sample-by-sample basis, not contextblock.)
//main loop
for (int channel = 0; channel < totalNumInputChannels; ++channel)
{
auto* channelData = buffer.getWritePointer(channel);
...
float smoothedFrequency = mFrequencySmoothed.getNextValue();
mFilter_highpass.setCutoffFrequency(//based on the nextvalue of smoothedFrequency);
you can update a filter sample accurately, which sounds smooth when the parameter was smoothed. it just usually doesn’t perform very well, which is why people figure out various alternative ways
1 Like
It’s OK if you only have a few low-order filters. If there are several high-order filters (or you use complex methods to calculate coeff, e.g., iteration methods), the coeff calculation may slow down your code.
By the way, it seems that if you update the coeff per block, the actual block size will also affect how often you update the coeff. Perhaps you can count the time elapsed (based on # samples) and update the coeff at a constant speed.
1 Like
Your smoother should be multiplicative not linear because you’re smoothing a frequency.
Then, the usual way of handling filter frequency updates is to only set the cutoff every ‘n’ samples where ‘n’ can typically be say 16 or 32. Make sure you use the ‘skip’ method on the smoother to skip n-1 steps.
1 Like
I might try a couple of solutions mentioned above. This one first as it seems easiest.
How does this implementation look to you? After testing it, to my ears, its improved the situation.
float frequencyValue = apvts.getRawParameterValue("mFrequency")->load(); // Fetch the latest target value from APVTS
mFrequencySmoothed.setTargetValue(frequencyValue); // Always update the target value for smoothing
float smoothedFrequency = mFrequencySmoothed.getNextValue(); // Then, fetch the next smoothed value
// Check for a significant frequency change
if (mPreviousFrequencyValue > 0.0f) // Ensure we have a previous value to compare (0.0f is the default value)
{
// Calculate the ratio of the change
float changeRatio = frequencyValue / mPreviousFrequencyValue;
// Check if the change is more than doubling (upwards) or less than half (downwards), = significant change
if (changeRatio >= 4.0f || changeRatio <= 0.25f)
{
// Significant change detected, reset the filters
mFilter_lowpass.reset();
mFilter_highpass.reset();
}
}
// Update the previous frequency value for the next comparison
mPreviousFrequencyValue = frequencyValue;
set_mFrequency_value(smoothedFrequency); // Apply this smoothed value
It looks great and very safe (maybe a little time-consuming, you may skip set_mFrequency_value when smoothedFrequency does not change).
If you are doing it per block, you may want to use skip() to make sure that the smoothing is independent of the block size.
You may also smooth the freq in the log domain as they suggested (it is a one-line change, i.e, using ValueSmoothingTypes::Multiplicative). I haven’t tried it though, but from intuition it will work.
1 Like
I think this sounds better now with the Multiplicative.
juce::SmoothedValue<float, juce::ValueSmoothingTypes::Multiplicative> mFrequencySmoothed;
--------------
float frequencyValue = apvts.getRawParameterValue("mFrequency")->load(); // Fetch the latest target value from APVTS
mFrequencySmoothed.setTargetValue(frequencyValue); // Always update the target value for smoothing
float smoothedFrequency = mFrequencySmoothed.getNextValue();
// Check for a significant frequency change
if (mPreviousFrequencyValue > 0.0f && (frequencyValue / mPreviousFrequencyValue >= 4.0f || frequencyValue / mPreviousFrequencyValue <= 0.25f))
{
mFilter_lowpass.reset();
mFilter_highpass.reset();
}
mPreviousFrequencyValue = frequencyValue; // Update the previous frequency value for the next comparison
set_mFrequency_value(smoothedFrequency);
1 Like
Cool. Now on the basis that the block size can be different on each call to processBlock, you want to loop through your audio buffer updating the filter frequency every 'n" samples…where ‘n’ is 16, 32 something like that.
1 Like