A Walk Around the Block - An Introduction to gen~ and filtering
One of the features of gen~ that has interested Max users from the beginning is the ability to create patches that process audio input on a per-sample basis (as opposed to MSP, which bases its processing model on groups of samples, called the signal vector size). Single-sample processing in gen~ means that the signal vector size no longer set the shortest allowable delay time in samples to the current signal vector size, which lets users experiment with things like pitched/ringing delays. You can find a tutorial exploring these features here.
Another area in which gen~ users have ventured involves using gen~ to explore and implement various varieties of audio filters. While there are a lot of examples of audio filters that are specified using mathematical formulas...
...audio filters are also often depicted using block diagrams rather than mathematical equations. You can use those block diagrams in your gen~-patching practice to create those filters using standard gen~ operators, even if you're just getting started in your Gen journey. In this brief tutorial, I’m going to
Show you a couple of simple examples of filters implemented in gen~, and show you a neat trick that’s helped me make sense of using block diagrams to create gen~ patches, and work through a couple of simple examples.
Give you a little advice about what to watch out for as you explore this area further (to avoid some of the problems that bedeviled me along the way, to be honest).
Let's get started!
A First Trip Around the Block (Exercising Your Biquad Muscles)
Let’s start with an example of a digital filter that every MSP user knows and loves: the biquad (short for biquadratic) filter. People love this general-purpose digital filter because you can set it up for practically any configuration by doing nothing more than adjusting the parameters (or coefficients) for the filter.
In the MSP world, the biquad~ object is an implementation of this incredibly versatile filter, and Max/MSP helpfully includes a couple of external objects that provide a graphic interface that outputs filter coefficients to the biquad~ object (the filtergraph~ object), as well as a non- graphic object for outputting those coefficient values (the filtercoeff~ object).
In fact, it may be the very first glimpse you've ever had of a filter block diagram! In fact, if you take a look at the coefficients tab of the biquad~ object’s help file, you'll find a block diagram of a biquad filter,.
Spoiler: This block diagram has an error in it. We'll talk about that in a minute.
Here’s a standard block diagram of one version of a biquad filter from the Wikipedia article on biquadratic filters:
I'm showing you both block diagrams to make a point: It's often the case that the same block diagram is depicted in slightly different ways, so it's helpful to see if you can find more than one example of any filter (or physical model or waveguide) block diagram you want to use to start from.
You can think of this block diagram as a map of the flow of data, and — as with any map — you always need to start by getting the lay of the land. In terms of the overall input and output, the flow of data is left (starting at x[n]) to right (ending with y[n]), with arrows generally demonstrating the order in which calculations are done. Several graphic items show up and are repeated several times
• A box with Z^(-1) in it
• Three big circles with a cross in them
• Smaller hollow triangles that are labeled
Decoding them will help us to see what’s going on – because each of those symbols will be replaced by a gen~ operator (or operators, sometimes) when we create the filter patch. Let’s look at each one:
Blocks to Operators
The Z boxes are a convention used to represent single sample values. If Z is the current sample, then Z^(-1) is the previous sample.
Some filter block diagrams use variants of this to indicate “N samples ago” by Z^(-N), where N is the number of samples’ worth of delay, or — in slightly more rare occasions — Z^(-M) to indicate a moving sample value rather than a fixed number of samples. In gen~, we can represent “the previous sample” (Z^(-1)) using a history operator.
You’ll notice several circles with a cross in them. It's not a cross at all — it’s actually a gigantic plus sign rather than a graphic of some sort. The arrows helpfully indicate what things are being added together and where the result of that addition is routed in the block diagram.
The third and final item is a group of five triangles, all of which are labeled (b0/b1/b2/-a1/-a2). If you take a look at the output of the filtergraph~ object as you manipulate the graphic interface, you’ll notice that the left outlet sends a list of five numbers as its output:
If you’ve looked at the input labels for the filtercoeff~ object, you’ll notice that there are also 5 outputs sent to the right 5 inlets of the biquad~ object.
Each of those five labeled triangles correspond to the parameters we use as multipliers to configure the biquad~ object to respond as the kind of filter we want. We can use the gen~ param operator to represent them.
So we know what gen~ operators we’ll use to patch our biquad filter, but how are they hooked together?
Turning Heads
I’m going to show you a really simple and elegant way to help you translate this filter diagram to a gen~ patcher. I learned it from Jon Christopher Nelson, who gave a workshop on physical modeling using gen~ that I attended. I hope it’s as great a help to you as it has been for me.
Jon did something interesting when it came time to do his patching: he took the block diagram you see above, and he rotated it 90 degrees, and redrew the labels. Here’s the result:
For me, that simple transformation suddenly oriented the diagram so that the information flowed from top to bottom… like the ordinary Max patching I do all the time. To this day, I’m a little embarrassed and fascinated about why that made understanding things so much clearer to me, but it’s true. I started by laying out the history, param, and + operators in a way that corresponded to my block diagram, and then started thinking about their interconnection.
Making Connections
I’m going to point something out to you that I didn’t notice at first when I started creating this patch. Yes, that’s right. I completed what I thought was a version of the patch and it promptly blew up as soon as I turned the audio on. It was a simple thing error that it took me a couple of minutes to find, and it's also the error in the block diagram in the coefficients tab of the biquad~ object's help file:
Take a look at the labels for a1 and a2 in the block diagram. Notice the minus sign preceding them? I didn’t notice it the first time around. That parameter is a negative number, which means that adding a negative number is actually subtracting that value.
The other part of my patch conversion was connected to noticing that all of those + operators in a row were actually summing the results, which meant that I could replace those three + operators with a single – (minus) operator and take advantage of the fact that multiple inputs to the inlet of any gen~ + or – operator are summed. The outputs of param operators b0, b1, and b2 are all connected to the left inlet of a – operator, and the outputs of param operators a1 and a2 are summed by their connection to the right inlet of the – operator. Neat trick, eh?
Finally - having blown up my filter after making a mistake with the patching, I decided that I might save my ears and my speakers by the simple act of adding a clip operator to my patch at the very end to slip the output to a friendlier output range of -1 to 1. It's a good thing to add to any gen~ patch, actually — for me, anyway.
Once I’d sussed that out, it was just a matter of connecting the operators to one another as shown in my block diagram. Here’s what I wound up with:
It was time to test out my port of the block diagram. I grabbed a copy of the biquad~ help file and modified it so that the parameter messages matched my patch, plugged my gen~ patch in where the biquad~ object should be, and turned on the audio....
Success (pause for a well-deserved celebratory libation and a bad imitation of Zorba the Greek dancing….)!
Now that I’ve got a working filter I built from a block diagram, let’s compare it to the biquad filter contained in the Gen examples folder (gen~.biquad.maxpat)
Try It Yourself
That wasn't so hard, was it? Okay - here's an example you can try turning to a gen~ patch yourself. It's a variation of the standard biquad filter with a little different topology:
See if you can do the "rotate and replace" trick and create your own gen~ version [remember to keep an eye on those a and b labels and watch out for minus signs!].
...and no peeking until you've taken a run at it. The biquad_exercise.maxpat patcher in the patches folder contains a solution to the problem.
Let's Try Another One
We're on a roll, so let's try a little more complex example. Like the biquad filter, this block diagram describes an example of a delay-based effect that can be produce varied output (vibrato, flanger, chorus, and doubling) based on how its input parameters are set. This bit of digital signal processing was first described in this paper by Jon Dattorro (whose name you may know from the Dattorro reverb). Here's what it looks like, and we've included a listing of how the inputs can be varied to produce different output effects:
Although you may never have seen this bit of patching before, you may have heard it. This patch forms the basis of the original Pluggo Generic Effect plug-in, which has also been ported to Max for Live as a part of the Pluggo series (the Max for Live device shares a common ancestor with the above patch, but is implemented differently).
Okay. Let's start by doing our "rotate the block diagram" trick and looking at what we have in terms of top-to-bottom signal flow:
With one exception, this block diagram is pretty easy, now that we know how to translate the triangles and the "cross in a circle" blocks: each triangle is a param operator, and each circle is replaced by a + operator.
But what about the Z^(-N+M) box — how might we patch it? As I mentioned earlier, Z^(-N) refers to a delay value which may be greater than the single sample delay we used the history operator to represent. Our other hint comes from the input to the block in the diagram labeled modulator.
Let's look at the settings associated with various effects again:
Instead of working with single sample delays, we will be using a standard gen~ delay operator with two taps (outputs) One of them will be a delay time in samples (the Delay column in the original block diagram) set as a fixed number of samples. That number of samples to delay our input by will be altered further by adding a value in samples that is derived from a modulated sine wave whose output is scaled to a specific range in samples (described in the Modulator column).
Our delay module in the block diagram is outputting two different delay values - the fixed delay value (the Z^(-N) portion of what happens in the delay block) is used for Feedback (the FB column in the original block diagram), which the summed fixed delay and modulated delay value are sent on be scaled by the Feedforward control (the FF column in the original block diagram).
Here's what the result looks like - for the sake of ease of reading, I've outlined the Z^(-N+M) portion of the block diagram. The feedback (FB), feedforward (FF) and blend (BL) portions of the gen~ patch are simple substitutions and summing operations. The delay portion of our block diagram takes the -N and M values in milliseconds in the tables, but those need to be translated to a number of samples at the current MSP's current sample rate. The mstosamps operator handles that for us in both cases.
To manage the delay, we're using a 2-tap delay (set by the second argument to the delay operator). The left output of the delay operator outputs the fixed sample delay value for the feedback calculation and summing (set by the value sent to the in 2 operator. The right inlet's value (in samples) is derived by summing the fixed delay input and whatever the current sample count from our sine function is sent to the in 3 operator.
It's really not that difficult to figure out, once you get the hang of it.
The dattorro_effects.maxpat patch provides you with an example to play with that also includes a lovely way to deal with modulating the input to that third inlet to the gen~ object (especially useful for those chorus and doubling effects), courtesy of our friend and benefactor Jon Christopher Nelson. The jons_cool_modulation patcher lets you set frequency and amplitude for the modulating sine function, but also to set a degree of randomness to the output that smoothly transitions from setting to setting. It's worth a little careful study.
Exploring The Next Block(s) Over
There are several other places where DSP programming uses block diagrams for explanation/demonstration.
Here are a couple of links you can use to begin your explorations:
As you explore these techniques and go off in search of block diagrams you might like to try implementing in gen~, I’ve got a few suggestions you might want to keep in mind.
Diagrams vs. Specifications
I've discovered along the way that it's often not enough to just copy a block diagram and start patching from there. Part of my frustrations wind up stemming from a simple observation: block diagrams for filter design are often meant to provide conceptual models rather than a starting point for implementation.
One obvious place you may notice this is a situation where you realize that your block diagram has absolutely no delay for feedback — that's simply not possible in the digital world (a caveat: Some filter algorithms can be rearranged to have better frequency or phase response, while preserving 0 delay in the main path through. The trapezoidal SVF is an example of one of those).
Here's a real-world example of this from the Max Forum: In this thread, the paper showed a diagram of what a circuit should conceptually be, and then the text explained how this is impossible due to zero delay feedback. If you read on, you'll find how they solve the zero delay feedback problem by re-arranging the math.
You may also find — particularly in the case of academic papers — that there'll be another kind of error in the diagram that's corrected in the text of the paper. These aren't things that happen all the time, but it never hurts to read to the end of the paper you're grabbing your block diagram from (ask me how I know this....).
It's also sometimes the case that the way that someone characteristically describes signal flow in a block diagram isn't actually the way that most people do the calculation — the amazing and wonderful Waveguides website describes and example of that pretty succinctly in their section on Implementation Details, for example.
That said, there are a few block diagram labels you're likely to encounter that don't need to scare you off at all. Blocks in a diagram that correspond to filters can easily be substituted by grabbing examples of those filters from the gen~.filters.maxpat examples file and just dropping it in, along with some param operators, for example. Here are a few others:
Sigmas and Crosses
From time to time, you’re likely to see a Greek letter sigma (∑) in a part of the block diagram. Here's an example of it taken from a block diagram you might even recognize - it's that same generalized effects block diagram we just worked with (in fact, it's the illustration the author himself uses in his paper on the subject):
Since you're familiar with the block diagram you just worked with, you'll probably notice right away that it’s the same position as the the + operator substitution we made for the “plus sign in the circle” in our previous examples. You can just substitute the + operator whenever you see a sigma like that and you’ll be fine.
Integrators (Leaky and Otherwise)
One of the other symbols you're likely to run across is the Integral symbol ( ∫ ). That can be a tricky one to encounter, since it might mean a simple integrator (which would be a simple substitution using the += operator), but it might also mean that you need a "leaky integrator" (would would be a history operator, a + operator, and a * operator for the leak factor). It’s not always easy to trust the diagrams, and you may need to go beyond the block diagram into the text to figure out what you need.
Learning To Block With The Problems You Tackle
As you experiment with building filters, you may find something interesting related to looping delay lines. You'll probably find yourself in a situation where the output of your filter vanishes when you crank up the feedback. It's not you - there's a name for what's happening: DC offset.
It happens as a result of averaging process — very slight positive or negative average offsets get amplified inside the loop until the signal gets messed up. In the filtering world, the usual solution is to add a 30Hz. highpass filter into the loop. The dcblock operator in gen~ is a simple solution that handles that for you without the need to copy and paste 30Hz. highpass filters into your patch.
Watch This Space....
I hope this has helped to demystify those block diagrams for those of you starting out with your gen~ programming.
By the way, writing this article reminded me once again of the amazing workshop that Jon Christopher Nelson presented at a previous SEAMUS conference. I got in touch with him to thank him once again for his patch rotation hack, and asked if he'd be willing to submit his physical modeling gen~ patches as a Max Package Manager submission. The good news is that he said, "Yes!"
So keep your eyes peeled for its appearance in the Package Manager. It's a great example of "content you need."
by Gregory Taylor on February 2, 2021