Custom Filter Designs
  • Fellow Audulators.. further to my learnings about how filters work:
    http://forum.audulus.com/discussion/391/biquad-filters-and-eq#Item_12
    http://forum.audulus.com/discussion/504/morphing-filters#Item_5

    I'm now trying to work out how to make alternative filter designs..

    First up is a State Variable Filter:
    image
    This differs to a biquad filter in that it provides simultaneous output of Lowpass, Highpass, Bandpass and Band Reject filters. It is also supposed to be better at filter sweeps but I'll let your ears be the judge, it certainly has a different tonal characteristic than the built in Audulus filter (which is a Biquad).

    Enjoy :)
    Screenshot 2015-01-25 16.14.43.png
    674 x 389 - 97K
    afta8.SVFilter.audulus
    28K
  • Your welcome @biminiroad.. anyway I changed the thread title as I'm experimenting with a number of filter designs..

    Next up we have a Ladder Filter design, sounds more analogue than the biquads and you can get it to whistle with high resonance settings.. It also shows yet another use for the xfade node!
    image
    Screenshot 2015-01-26 21.18.29.png
    751 x 258 - 87K
    afta8.LadderFilter.audulus
    24K
  • Yay! You're using the unit delay :)
  • Yeah finally figured it out :)
    Fairly essential for filter design ain't it!
  • Yep! Maybe we should talk about how it is important in the documentation? All I've got is:

    "The UnitDelay node is a single-sample delay. Like the #FeedbackDelay node, the UnitDelay can be used to determine where a delay occurs in a feedback loop.

    The presence of a UnitDelay node in the patch causes Audulus to switch into single-sample processing mode. This requires considerably more CPU. We are working on a way to improve that."
  • Yeah I think I asked about this a while back but I still don't know exactly what it's useful for?
  • @Taylor, yes I think the doc could be improved and what you have added helps. Still it took me a while to figure it all out and I find pictures can help a lot... so here's a short tutorial I have put together. @biminiroad and anyone else might find this useful in trying to understand what the unit delay does and also how it can be used to construct a simple lowpass filter.

    --

    So to start with lets revisit how digital audio is constructed. In contrast to analogue systems digital audio is made up of many discrete values and the number of these per second is dictated by the sample rate, in most cases this is 44100 samples per second and by samples we mean a value between -1 and 1.

    So when you are playing a waveform on a digital system you are really proceeding through 44100 values between -1 and 1 per second. When this is recorded you can see this as a waveform like so:
    image
    You can imagine the yellow line in this as the playback head which rapidly proceeds along the waveform to give you audio. If we zoom into the waveform we can see the individual samples that make up the waveform:
    image
    You can see in this the playback head is labelled y[n] which is a terminology we will use to indicate the current sample position. Ordinarily when we create nodes in Audulus they are operating on the current sample position (y[n]). What the Unit Delay node does is give you access to the sample position that precedes the current sample position, this is referred to as y[n-1] and you can see this labelled in the image above.

    Having access to y[n-1] is what lets us build digital filters.

    So lets consider the equation for a simple low pass filter: y[n] = a*y[n] + b*y[n-1]

    We can build this in Audulus using the maths expression node like so:
    image
    In this the variables 'a' and 'b' would be described as the filter coefficients and their values will dictate the sound of the filter, in this case they will affect the filter cutoff. To turn this into a basic low pass filter in Audulus we would wire it up like this:
    image
    Note how the Unit Delay node is used to get y[n-1]. You can also see that the values of 'a' and 'b' are related, as you increase 'a' you decrease 'b' and vice versa. If you build this in Audulus you will have a basic low pass filter.

    Taking this a step further we can further simplify the patch to look like this:
    image
    You can see here that 'a' and 'b' have been replaced with a single control for filter cutoff. We can again simply this further to look like this:
    image
    Now if you consider the formula in the maths expression node you can see that it is essentially mixing the two inputs 'a' and 'b' together. As you increase the amount of 'a' you decrease the amount of 'b' and vice versa. The constant node lets you set the balance between the two inputs and this is exactly the same behaviour of the Crossfade node. Therefore you could replace the maths expression node here with a Crossfade node like so:
    image
    This is how you can use a Crossfade node to build a simple lowpass filter. You can see 4 of these used in serial in the ladder filter in an earlier post. Incidently this configuration is exactly the same as the lowpass filter node provided with Audulus, the two are equivalent:
    image
    1.waveformview.jpg
    763 x 291 - 46K
    2.sampleview.jpg
    768 x 286 - 40K
    3.simpleLP.jpg
    516 x 403 - 17K
    4.simpleLP.jpg
    1173 x 518 - 52K
    5.simpleLP.jpg
    1077 x 439 - 44K
    6.simpleLP.jpg
    1007 x 479 - 40K
    7.simpleLP.jpg
    938 x 427 - 35K
    8.LPequivalent.jpg
    637 x 307 - 23K
  • Ohhhhh. Unit delay == get y[n-1]. That makes it click for me. Thanks, @afta8!
  • This is excellent, @afta8. Very helpful. Thanks.
  • @jjthrash and @Al_Thumbs, thanks for the feedback and glad you find it useful.. Go forth and make ze filters :)
  • Very clear explanation and an elegant solution. Thanks.

    I'm trying to wrap my head around this. So what you're saying is the UnitDelay gives you access to the immediate future, to the sample that will play next. Can I use it to make a compressor?

  • @JDRaoul, @afta8 will have a better explanation, but I'll try, just to help my own understanding advance.

    I think the unit delay gives you one sample in the immediate *past*. But that means the current sample is the immediate future from the unit delay's perspective.

    So, with 1 sample of latency, I think you could build a compressor to modify the output of the unit delay, based on the output of the input signal.

    E.g.
    input -> unit delay -> compressor -> output
    v---------------------^

    Or something like that.
  • @JDRaoul.. Thanks!

    The Unit Delay gives you access to the immediate past as @jjthrash has described. A sample in the immediate future would be described y[n+1] using the same terminology.

    Regarding a compressor I think you would need to use one or two envelope followers, maybe coupled with slew limiters to smooth the signals. Compressors work on signal levels over longer time periods and not at a single sample level so if you do something like what @jjthrash has mentioned then you would actually be doing waveshaping/distortion.
  • Incredibly thorough! Thank you - as soon as I saw that upclose picture of the waveform you made I slapped my forehead and went OF COURSE! Thank you! This is a great post for posterity.
  • "Can I use it to make a compressor" is my default question.
  • @JDRaoul, can't you use a crossfade to make a compressor somehow? ;)
  • @JDRaoul hahahaha yesss! Speaking of which can someone build a brick wall limiter to prevent ear damage with headphones?
  • @biminiroad a simple distorting limiter would be a math node with expression "abs(x) > 1 ? abs(x)/x : x"

    If you wanted to limit to a specific value, you could use "abs(x) > lim ? abs(x)/x*lim : x"

    But I'm guessing you're thinking of something significantly more sophisticated.
  • @jjthrash naw not something for audio processing something merely for safety - I've zapped myself a couple times on accident prematurely hooking up a really hot oscillator to the output. Will the math node do that then?
  • I was thinking: what if you could do unit delays within the Expr node? What would the syntax be?
  • @Taylor

    @ - unit delay happens "at" this point
    ; - unit (tittle) is delayed (comma)

    only single-character ones I could think of
  • lol @ JDRaoul... But you have got me thinking

    @biminiroad, I'm pretty sure the range node will give you a hard limiter/clip distortion, you can also stick everything through a sine node which will give you foldback distortion on overloaded signals

    @Taylor, how about using square brackets to select a position in the buffer of a particular input variable, therefore the expression for the simple LP would be: ((1-Fc)*input)+(Fc*input[-1]) this way you could also implement a Biquad using the expression node
  • +1 to above!
  • @afta8 I think it would be (1-Fc)*input + Fc*output[-1], right?
  • @Taylor, yes you're right, my bad... so you would still have to wire the output back into an input.. Hmm, not sure if that's any better than what we have now..

    Btw question about unit delay node if I have two in serial will that give me y[n-2] ?
  • @afta8, I'm afraid not. I just tested it at the code level and it didn't work :-\. It's a bug, because you should be able to chain two unit delays and get y[n-2].
  • @Taylor...."Expression Node? What would the syntax be?".....
    Just as the variable "e" (= 2.718) is a pre-assigned variable in Audulus, why not do the same with z^(-n), where that entire sub-expression is pre-defined. This would then also account for higher orders of z, i.e., cascade delays, as well as being a mathematically correct notation.
  • @BTL, that's problematic because currently z^(-n) is a valid expression.
  • .....and with the same thought in mind, why not also allow the user to select the order, "-n", of z when selecting the unit delay node?
  • But how about if it was restricted....is "e" not restricted?.....I'll check.
  • You could use a function. E.g. z(-2), z(-1). Non-negative inputs could just return 0.
  • @Taylor,
    If I create a math exp. Node of simply "e", I get the expected result, 2.718, at a connected value node. If I then create an expression of "d+e" , I do not get an input for "e" on the node, only for d, and if I set default of d=1 on the input, I get the expected 3.718 at the output.
    We could apply the same logic....when z^(-n) is present in a patch, all "z" variables of that form are restricted to that meaning.....this is not uncommon.
  • @BTL, yes, e is a reserved word. The problem is making z also reserved will break existing patches.
  • jjthrash's idea is also good, showing in a sense, "z as a function of the negative nth order", and that construct is not currently defined in Audulus.
  • ....non-negative inputs should return an expression error, as we can not predict the future....;-).
  • @Taylor,
    The more I think about it, the more I like jjthrash's idea. If you later decide to add functions, f(x), to Audulus, the z(n) function would already be reserved and, therefore, not done in hindsight where it would break patches.
  • @BTL, @jjthrash, yeah z(n) is good because the parser knows how to distinguish between a function named z and a variable named z. Could make for some confusing expressions, like z(-1) + z but at least it's fully backwards compatible.
  • @Taylor...sounds good....and how about allowing the user to select the order of n when selecting the Unit Delay Node....is that doable?
  • @Taylor, would it be possible to have single sample mode only run within a sub patch where a unitdelay node is present? Or even better, have it only apply to it's input signal? I'm guessing that this wouldn't work, but I don't exactly know why. Is it because it needs to receive a single sample at a time and therefore every signal in it's input network needs to be single sample? If so can single sample mode only apply to it's input network?
  • @BTL, yeah that should be doable (good point!). I'll put it on the list.

    @ceilidhshipley, good question! I think implementing single-sample in sub-paches only would be hard to implement, given the current architecture. Also, it might not be good to have a sub patch which consists just of a unit delay not act as a unit delay in the outer patch.

    When I first implemented the UnitDelay, I tried to write an optimization where only a minimal part of the patch is executed in single-sample mode. This turned out to be really tricky, so I compromised and just ran the entire patch in single-sample. But the plan is to work on that again.

    Eventually, I'd like all feedback loops to be delayed by just one sample, but that might cause performance problems, even with the above optimization.
  • Just found this thread and tried some things out in Audulus 3:
    _FilterDesign2.audulus
    716K
    _FilterDesign3.audulus
    386K
  • Transistor ladder module:
    _FilterDesign_ladder.audulus
    924K
  • @Taylor: Revisiting the question about syntax for unit delay, perhaps borrow from Faust: A single quote indicates a unit delay and an @ followed-by-an-expression represents multiple units. Simply putting x' would represent a unit delay. x@2 would represent a delay of 2 samples. x@n would be a delay of n samples.

    From the Faust reference: "Time expressions are used to express delays. The notation x@10 represent the signal x delayed by 10 samples. The notation x’ represents the signal x delayed by one sample and is therefore equivalent to x@1." (page 24 of http://faust.grame.fr/images/faust-quick-reference.pdf)

    I think that's straight-forward, elegant, and would not conflict with existing expressions.
  • @intrinsic - thanks for the suggestion - would love to do unit delays in-line like that.
  • What an amazingly great example, and how simple an explanation for what a LP traditional filter is. Even simpler than explaining a passive RC. Thanks for the great explanation, very well explained and an eye opener. Custom filters is what gives synths character, and this opens up a huge number of options to shape sounds.
  • As a learning curiosity, would a HP filter then calculate the low pass value (Afta example), and instead of just sending that, would send "a" minus the calculated LP value? I am guessing that would delete the slower moving waves.