How would you optimize coefficients calculation on modulated filter?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Nowhk wrote: Is it normal to modulate somethings with "manipulated-generated-real-signal"?
I just ask, since I really don't know hehe
For your 50 line-code use-case, definitly not :D
If you get rid of the exp() you will be fine.
But on bigger system is it quite common to seperate between control and audio signals. Control signals carry way less information, so you can spend less processing power on it.

Post

PurpleSunray wrote: So the problem you are struggling with is kind of a fundamental problem to DSP and it is solved by the sampling theorem. 2756Hz of sampling rate are enough to sample a 20Hz signal according to that theorem.
Thats something I dont get when talking about modulating signals :)

Because here I won't use the interpolated (continuos) signal "version" of my samples (which is the same 20hz signal using 2756, 44100 or 40 samples) to modulate my knob. If I use 2756, 44100 or 40 samples, the modulation itself change (I'll lost lot of movement), even if the modulation signal (once interpolated using Shannon) will remain the same.

If the modulation itself change due to the control rate, why struggling on making lfo signal (as the ones in my code; maybe alias free) when (at the end) is nothing more than a sample & hold with repetitions? :lol:

Post

Nowhk wrote:
PurpleSunray wrote: So the problem you are struggling with is kind of a fundamental problem to DSP and it is solved by the sampling theorem. 2756Hz of sampling rate are enough to sample a 20Hz signal according to that theorem.
Thats something I dont get when talking about modulating signals :)

Because here I won't use the interpolated (continuos) signal "version" of my samples (which is the same 20hz signal using 2756, 44100 or 40 samples) to modulate my knob. If I use 2756, 44100 or 40 samples, the modulation itself change (I'll lost lot of movement), even if the modulation signal (once interpolated using Shannon) will remain the same.

If the modulation itself change due to the control rate, why struggling on making lfo signal (as the ones in my code; maybe alias free) when (at the end) is nothing more than a sample & hold with repetitions? :lol:
damn, you'r persistent :D

It's all about when you combine the signals.
You have low-rate control and high-rate audio signal.

You can convert the low-rate into a high-rate signal at any time (Shannon) - we go that far.
But we want to optimize, so the question is, do we need to convert to high-rate first before applying the modulation?

If you modulate a filter cutoff on high-rate signal with that low-rate signal, you do not need to be sample-accurate necessarly.
It doesn't mix into the high-rate signal directly, so no danger of distortion there.
Instead it changes the filter cutoff. Or, put differntly, it changes the coefficient of your moving average filter math. You only hear that modulation if, and only if the audio signals contains frequencies affected by the filter. The filter needs a couple a sample to 'know' if those frequencies are present, it's a moving average not instant average. But I'm loosing track..
What I want to say with it, a change on the filter cutoff will not manifest as a direct change on the audio signal. You don't mix your steepy sine onto the audio signal, which will cause the DAC on the audio interface to interpolate strange things.
Instead you change the filter cutoff. You need to do this at a rate so that a saw-sweep appears like a saw-sweep and not like an arpeggio, but that rate is far from the audio signal sample rate.

Post

Hi man, I'm back here from the other topic because I think that's the correct place right now.
Reading this, I realize that you were talking about FIR filter as target of modulation, right? :?
PurpleSunray wrote: If you modulate a filter cutoff on high-rate signal with that low-rate signal, you do not need to be sample-accurate necessarly. It doesn't mix into the high-rate signal directly, so no danger of distortion there.
Instead it changes the filter cutoff. Or, put differntly, it changes the coefficient of your moving average filter math. You only hear that modulation if, and only if the audio signals contains frequencies affected by the filter. The filter needs a couple a sample to 'know' if those frequencies are present, it's a moving average not instant average.
The filter I'm using right now (on learning stuff) is the one posted (1 pole), but IIR (where input is the carrier signal, i.e. the target, audio):

Code: Select all

mBuffer += mCutOff * (input - mBuffer);
Thus, actually, I'm not "smoothing" (i.e. upsampling) the modulation, and it can introduce aliasing/click on rapid value modulation.

Let say I've a square wave as LFO modulator (control rate 1/8): if it brutally change between -1 (thus mCutOff 0.01) to 1 (thus mCutOff 0.99), it could create jumps/rapid slope when it process the value of mBuffer.

For example, if I'm filtering at low freq and mBuffer is around 0.08, and at the next sample input will be 1 and LFO modulate mCutOff to 0.99 (to actually filtering "nothing"), it will introduce a click/jump at the Process() call. It's direct, there's no smoothing, no "moving average"

Basically, I've not resolved the problem. :evil:

How would you upsampling (smooth) the control rate of 1/8 to my IIR filter's cut off in this case? I mean, in code, where should I put the upsampling?
PurpleSunray wrote: I would do like:
float lerp(float v0, float v1, float t) {
return (1-t)*v0 + t*v1;
}
A subroutine that call (for the next 8 samples after every control rate trigger) the 8-sample-position of filter cut off? Or sequencing LFO control rate->param smooth->iir filter cut off?

Because if so, at each LFO value I would to call that function 8 times (upsampling) and reset/recalculate coefficient 8 times.

Which is like come back to invoke the calculation of filter at sample rate 44100 in one second, loosing "control-rate" optimization.

Post

Nowhk wrote: For example, if I'm filtering at low freq and mBuffer is around 0.08, and at the next sample input will be 1 and LFO modulate mCutOff to 0.99 (to actually filtering "nothing"), it will introduce a click/jump at the Process() call. It's direct, there's no smoothing, no "moving average"
I do not put such logic to inside the modulation code. If the modulation signal causes a click, than for me it is ok to cause a click. If you do not want to cause a click, then do not send a signal that causes a click - the gain modulation will not fix it for you. Like if that signal is comming from a fade-in ramp on an audio sample, I want to follow that fade and not filter/interpolate stuff on top of that (if the fade is short and causes a click, than it causes a click - fix it on the fade enveloppe not on the gain moduation code).

But that's an architecture decision, you don't need to follow my idea of how things should work.
Nowhk wrote: Because if so, at each LFO value I would to call that function 8 times (upsampling) and reset/recalculate coefficient 8 times.
Which is like come back to invoke the calculation of filter at sample rate 44100 in one second, loosing "control-rate" optimization.
Filter before the upsampling (on low rate), not after (on high rate).
That's the whole point on of the control rate thing. Run all processing of the control signal at control rate (LFO -> Filter -> Upsample -> Gain Modulation, not LFO -> Upsample -> Filter -> Gain Modulation)

EDIT:
oh wait.. this thread was about modulating the filter cutoff not about gain modulation.. right?
There you won't need the upsamping with control rate of 1/8 of audio rate.
Just update the filter cutoff each 1/8 audio sample, it's fine.
(coefficient update each 1/8, instead of each audio sample, will not cause noise).

Post

Let's talk about Filter processing now, leaving Gain Modulation apart for a moment...
PurpleSunray wrote: Filter before the upsampling (on low rate), not after (on high rate).
That's the whole point on of the control rate thing. Run all processing of the control signal at control rate (LFO -> Filter -> Upsample -> Gain Modulation, not LFO -> Upsample -> Filter -> Gain Modulation)
Not sure what do you mean with Filter -> Upsample.
I followed your suggestion, and I've moved the CalculateCoefficients() within SetCutOff and SetCutOffMod, so only when knob gui change or when LFO change; control rate now is 1/8 samplerate instead of samplerate:

Code: Select all

inline void SetCutOff(double value) {
	mCutoff = value;
	CalculateCoefficients();
}
inline void SetCutOffMod(double value) {
	mCutoffMod = value;
	CalculateCoefficients();
}
inline double GetCalculatedCutOff() {
	return fmax(fmin(mCutoff + mCutoffMod, 0.99), 0.01);
};

inline void CalculateCoefficients() {
	double freq = exp(ln20 + GetCalculatedCutOff() * (ln20000 - ln20));
	mCoefficientA = 1.0 - exp(-2.0 * pi * freq / mSampleRate);
};

inline double Process(double input) {
	mBuffer += mCoefficientA * (input - mBuffer);
	return mBuffer;
}
Where do I do "Upsampling" here for smooth the filter cut off? It should be somewhere between SetCutOffMod() and Process() calls; else my IIR filter would "click" if SetCutOffMod() pass from 0.01 to 0.99 (usual if I use a square 20hz as LFO signal for example). A scaling factor * mCoefficientA?

Sorry if I seem an idiot :(

Post

Nowhk wrote: Where do I do "Upsampling" here for smooth the filter cut off? It should be somewhere between SetCutOffMod() and Process() calls; else my IIR filter would "click" if SetCutOffMod() pass from 0.01 to 0.99 (usual if I use a square 20hz as LFO signal for example). A scaling factor * mCoefficientA?
You don't.
Upsampling does not remove clicks, but noise that's caused by multiplication of 8-sample-blocks with 1 modulation value.
Your IIR does not multiply audio samples with the modulation signal sample; it doesn't cause noise because of the 8-sample-blocks with 1 modulation value like on gain modulation. So you don't need upsampling.

To remove the 'click' you need to filter the modulation signal, so before you call SetCutOffMod, filter the signal. Like:

Code: Select all

	                          _______________________
Static value(?) - CUTOFF >- |                      |                                   _______________
LFO---------------   IN  >- | Filter(anti - click) | - OUT ---------------- CUTOFF >- |               |     
		                      |______________________|     Audio signal ------   IN  >- | Filter(audio) | - OUT -> Audio signal
                                                                                      |_______________|
(resize browse window to max to enjoy my artistic asci art :lol: )

But as as said, I would not do it all because you will run into all kind of trouble later on. Like if the modulation signal is comming from an envelope drawn by user instead of your LFO, your anti-click-filter might produce unwanted results. Like, if you want to avoid click on the envelope, use a min. value for the attack/decay. This makes it clear to the user. If you allow to use 0 value on attack or deacy, some users might actually want to use that 0 attack/decay.. but you smooth it because of your anti-click filter, so it's no more 0-decay, but a 'smoothed' 0-decay which the user cannot control. You create a fade, where the user does not want a fade but an instant 0->max.

Post

PurpleSunray wrote:Your IIR does not multiply audio samples with the modulation signal sample; it doesn't cause noise because of the 8-sample-blocks with 1 modulation value like on gain modulation.
This is an oversimplification. For the filter in question, the modulation signal does multiply the input signal since the coefficient is a function of the modulation signal.

Cutoff modulation definitively causes zipper-noise and amplitude modulation. For filters with resonance, it also causes frequency modulation.

Post

Mayae wrote:
PurpleSunray wrote:Your IIR does not multiply audio samples with the modulation signal sample; it doesn't cause noise because of the 8-sample-blocks with 1 modulation value like on gain modulation.
This is an oversimplification. For the filter in question, the modulation signal does multiply the input signal since the coefficient is a function of the modulation signal.

Cutoff modulation definitively causes zipper-noise and amplitude modulation. For filters with resonance, it also causes frequency modulation.
Yes, trying to oversimplify on purpose, otherwise the thread will never end :D
The point is, that the "mBuffer += mCoefficientA * (input - mBuffer);" is recursive and has "build-in smoothing" already. mCoefficientA change does not manifest on the audio signal immediatly, but builds-up via the recursion.
You might notice the noise if you go with low control rates, such as 1/64 of audio rate or idk. But for 1/8 (and also 1/16) I do not hear any zipper noise on my filters.

Post

There are two issues being discussed together here, causing unnecessary confusion. The issues are (1) undersampling control signals to reduce processor load when calculating coefficients, and (2) smoothing of control signals to reduce artefacts in the modulated signals.

(1) If changes in your control signals cause an expensive recalculation, say of filter coefficients - then it's often OK to undersample it - i.e. pretend that the control signal only changes every N samples, so the recalculation is performed at most every N samples. If N is too big then the resulting abrupt jumps in the control signal might be audible if the control signal is meant to be smoothly varying, so it's a balancing act of audible 'zipper' artefacts versus CPU performance.

(2) If your (undersampled) control signal changes rapidly then it is much more likely to introduce clicks or aliasing artefacts in any signal it is modulating. If this is a problem then you can smooth the signal with some kind of low-pass filter. (Or as PurpleSunray says, just accept that the user selected the waveform so they know what they're doing.) If you do choose to filter it, then in order to get any benefit from undersampling you need to filter it first and then sample the result every N samples, so you aren't constantly recalculating filter coefficients.

So with your example of a squarewave LFO, you could first smooth it so that instead of transitioning between -1 and +1 in a single step, it took (say) 128 samples. This would make it less likely to induce clicks. Then grab the resulting signal every (say) 8 samples and use the result to calculate your filter coefficients. So you'd update the filter with 16 small changes over the course of the -1/+1 step instead of one big change. This would reduce the clicking, but of course would make the filter change more gradual and possibly introduce 'zipper' noise. You would have to decide how to balance the smoothing and subsampling to produce an acceptable result, since it will vary from one application to the next.

LFO ---> Optional smoothing ---> Sample/hold (undersampling) ---> Recalc coefficients

Post

kryptonaut wrote: LFO ---> Optional smoothing ---> Sample/hold (undersampling) ---> Recalc coefficients
If you do:
LFO ---> Optional smoothing ---> Recalc coefficient
instead, you save computing power on the LFO osc and smoothing because it runs at the rate as of after your sample/hold ;)
(No real point of running LFO+smoothing at audio rate, just to under-/donwsample it later on)

But I'm tired of that thread, guys please take over (& out) :P

Post

PurpleSunray wrote:The point is, that the "mBuffer += mCoefficientA * (input - mBuffer);" is recursive and has "build-in smoothing" already. mCoefficientA change does not manifest on the audio signal immediatly, but builds-up via the recursion.
Theres no "build-in smoothing" on that filter :neutral: It does manifest change on audio signal immediately; take my previous example, where mBuffer is 0.08, input 1.0 and mCoefficient 0.99 (so the moment where the LFO square modulation signal changes target filter's Cut Off from min to max): the next mBuffer will pass from 0.08 to 0,92 * 0,99 = 0,9108. It will click!
PurpleSunray wrote:But I'm tired of that thread, guys please take over (& out) :P
I'm sorry if you are stressed out, but would be useless to me end this discussion if I didn't get the whole point. If you go out, thank you very much for your help, anyway! You really help me (even if I don't get everything yet).
kryptonaut wrote:(1) If changes in your control signals cause an expensive recalculation, say of filter coefficients - then it's often OK to undersample it - i.e. pretend that the control signal only changes every N samples, so the recalculation is performed at most every N samples. If N is too big then the resulting abrupt jumps in the control signal might be audible if the control signal is meant to be smoothly varying, so it's a balancing act of audible 'zipper' artefacts versus CPU performance.
Clear :wink:
kryptonaut wrote:(2) If your (undersampled) control signal changes rapidly then it is much more likely to introduce clicks or aliasing artefacts in any signal it is modulating. If this is a problem then you can smooth the signal with some kind of low-pass filter. (Or as PurpleSunray says, just accept that the user selected the waveform so they know what they're doing.) If you do choose to filter it, then in order to get any benefit from undersampling you need to filter it first and then sample the result every N samples, so you aren't constantly recalculating filter coefficients.
Clear :wink:
kryptonaut wrote:So with your example of a squarewave LFO, you could first smooth it so that instead of transitioning between -1 and +1 in a single step, it took (say) 128 samples. This would make it less likely to induce clicks. Then grab the resulting signal every (say) 8 samples and use the result to calculate your filter coefficients. So you'd update the filter with 16 small changes over the course of the -1/+1 step instead of one big change. This would reduce the clicking, but of course would make the filter change more gradual and possibly introduce 'zipper' noise. You would have to decide how to balance the smoothing and subsampling to produce an acceptable result, since it will vary from one application to the next.

LFO ---> Optional smoothing ---> Sample/hold (undersampling) ---> Recalc coefficients
Here I've some uncertainties :) Not sure about that "Optional smoothing", since it will differs for every kind of wave; i.e. on sine wave, I don't need that 128-samples-smoothing.

At this point I have an idea of what it could work for my scenario. I try to explain it hoping you can analyze it and find potentials problems.

Well...
Won't be better to smooth out just each LFO single output (pulse; with control rate 1/8, one every 8 sample) using these 8 samples (between each LFO "pulse") as interpolated points and use them to modulate the Cut Off?

So for example if my LFO square pass between -1 a 1:

1 - at pulse (every 8 sample) I calc the coefficient by calling CalculateCoefficients()
2 - I calculate linear interpolation of 8 samples between mPreviousCoefficientA and mCoefficientA
3 - on the next 8 samples within the filter's Process() I use the interpolated coefficient for each t between [0..7]
4 - I store on mPreviousCoefficientA = mCoefficientA

Does it make sense? It should optimize CPU (CalculateCoefficients() at Control Rate) and reduce clicks by using a linear interpolation (even if some zipper noise could appairs).

Post

Nowhk wrote:
PurpleSunray wrote:The point is, that the "mBuffer += mCoefficientA * (input - mBuffer);" is recursive and has "build-in smoothing" already. mCoefficientA change does not manifest on the audio signal immediatly, but builds-up via the recursion.
Theres no "build-in smoothing" on that filter :neutral: It does manifest change on audio signal immediately; take my previous example, where mBuffer is 0.08, input 1.0 and mCoefficient 0.99 (so the moment where the LFO square modulation signal changes target filter's Cut Off from min to max): the next mBuffer will pass from 0.08 to 0,92 * 0,99 = 0,9108. It will click!
Ok, let's make an example.

The IIR:
Audio input:
[0.5] [0.5] [0.5] [0.5] ..
mCoefficient:
[0] [0.9] [0.9] [0.9] ..
Output:
[0] [0,45] [0,495] [0,4995] ..

The gain modulation:
Audio input:
[0.5] [0.5] [0.5] [0.5] ..
Coefficient:
[0] [0.9] [0.9] [0.9] ..
Output:
[0] [0,45] [0,45] [0,45] ..

You see that [0,45] [0,495] [0,4995], vs [0,45] [0,45] [0,45]?
Don't know how to explain it any better :scared:

I'm not talking about click due to 0-attack amp envelope (instant volume 0 -> max jump), but about noise to due processing blocks of [n]-audio-samples with a single modulation value. The "steepy" signal mutiplied onto the non-steepy signal, u remember?
The 0 -> 0.9 jump will cause click, but that is not the zipper noise.
This are 2 different problems, don't try to solve it with a single filter / function / class. It will just confuse you.
Last edited by PurpleSunray on Wed Nov 02, 2016 4:21 pm, edited 6 times in total.

Post

1 - at pulse (every 8 sample) I calc the coefficient by calling CalculateCoefficients()
2 - I calculate linear interpolation of 8 samples between mPreviousCoefficientA and mCoefficientA
3 - on the next 8 samples within the filter's Process() I use the interpolated coefficient for each t between [0..7]
4 - I store on mPreviousCoefficientA = mCoefficientA
That technique is called coefficient interpolation, and it's one way to "solve" this problem. Problems with this approach include:
- Your original mapping (for instance, exponential frequency) is now ignored between control rates, and substituted by some filter-dependent curve that technically aliases due to change of velocity
- More complex filters can behave undesirebly and/or become unstable using this approach. One-poles happens not to.
- Added expense for interpolating each sample.

Keep in mind also how processors actually work: They enjoy small, tight loops that don't change the pipeline and instruction path. Calculating coefficients each N sample may just be slower than actually running the coefficient calculation at audio rate. Keep in mind this depends hugely on loop instruction size and N, and obviously has diminishing returns.

Post

Basically, if you are worried about clicks from rapid changes in your LFO then you just need to design your LFO so it doesn't have rapid changes. Either make it a not-quite-square wave at source, or pass it through a smoothing/low-pass filter of some sort, it's up to you.

Then, if you are worried about CPU load calculating coefficients each sample, try calculating them every N samples instead. If N is not too large you won't hear any zipping/clicking. If you really need to make N large you can try interpolating the calculated coefficients but remember that this may not be reliable for all filter types.

Post Reply

Return to “DSP and Plugin Development”