Porting moog ladder filters to audio objects?

ty for the reply! I will run some experiments using some of the piecewise tanh solutions i have come across and report back. I was quite curious to try using MATLAB to smooth the function into the tanh approx i referenced. it's interestingly related to activation functions I've been working on with an FPGA project I've been plugging away at... I'm curious to see if an even faster LUT solution can't be achieved
 
I came across this paper "Non-Linear Digital Implementation of the Moog Ladder Filter" (Proceedings of DaFX04, University of Napoli) by Antti Huovilainen and found an implementation for Reason JSFX here that I converted and added.

It is the #define ANTTI. Sound nice and quite Moog'ish but loses stability with res >0.6 . It should be able to go higher so I'll have another look at it. The maths are beyond my and it uses quite a few tanh calcs - I'm sure it could be optimised.

I updated the files at GitHub if anyone is interested.

Thanks very much for that colleciton of files. I've implemented four of them now on a Teensy 4 with audio shield: the AnttiHuovilainen model, the RKSimulation, the D'Angelo, and the Krajeski models. I really like the AnttiHuovilainen model, but it does appear to need some further custom level reduction at the output for high Q below about 150Hz to keep the audio from overloading. I find the RKSimulation nearly as good, but it does warble slighlty more than the AnttiHuovilainen when sweeping at full resonance. But it doesn't seem to need further level adjustments. The D'Angelo model sounds more towards a 303 than a moog to my ears, and both it and the Krajeski models seem to be quite inaccurate in terms of resonant frq at high FC/Q settings.

Do you recomend any of the others in particualr given the ones I've tried? I might try the oberheim as you say you like it even if it's not moog-like. Is it actually a model of an oberheim filter?

On a related note, I'm new to the teensy 4 and also the AudioLibrary setup, but when I run AudioMemoryUsageMax() in a sparse main loop, the indication is that I'm only using about 5% of the teensy 4's resources. Can that be right for two bandlimited oscillators plus two concurrent moog models that are both substantially tanh based? Or am I misinerpreting the AudioMemoryUsageMax() function output value of about 4-5 ?
 
Last edited:
Obviously I've been coding too long today becasue the last part of my prevous post is nonsense. What I really need to know is how much CPU time I have used up rather than memory. Is there an easy way for me to do that?

Regardless, the Teensy 4 is really impressive because in addition to the 8 floating point tanh calls per sample in the AnttiHuovilainen model filter code, I've now also added a full audio bandwidth FC modulator that recalculates the coefficient corrections at every sample interval as per:

void AnttiMoog::compute_coeffs(float freq, float res)
{
float f_, fc_, fc2_, fc3_, fcr_, acr_;
if (freq < 1) {
freq = 1;
} else {
if (freq > filter_nylimit) freq = filter_nylimit;
}
if (res < 0) {
res = 0;
} else {
if (res > 1) res = 1;
}
filter_freq = freq;
filter_res = res;
// srate is half the actual filter sampling rate
fc_ = filter_freq * (1.0/ 44100);
f_ = 0.5 * fc_;
fc2_ = fc_ * fc_;
fc3_ = fc2_ * fc_;
// frequency & amplitude correction
fcr_ = 1.8730 * fc3_ + 0.4955 * fc2_ - 0.6490 * fc_ + 0.9988;
acr_ = -3.9364 * fc2_ + 1.8409 * fc_ + 0.9968;
filter_scl = float((1.0 - exp(-2 * AM_PI * f_ * fcr_)) / filter_z); // filter tuning
filter_r4 = 3.96 * filter_res * acr_; // filter feedback
};

And yet it's chugging along just fine....
 
hi @rvh

when I was looking at this I thought the AnttiHuovilainen was the most promising - I'm not sure I follow what you're doing with the coefficient code, i can't see what would change sample by sample?

the other one I thought was good was the Oberheim, like I said not very moog'y but nice - try just changing the #ifdef for a quick easy audition.

on the teensy 4 performance - its a screamer - almost makes it too easy!

cheers Paul
 
Last edited:
hi @rvh

when I was looking at this I thought the AnttiHuovilainen was the most promising - I'm not sure I follow what you're doing with the coefficient code, i can't see what would change sample by sample?

the other one I thought was good was the Oberheim, like I said not very moog'y but nice - try just changing the #ifdef for a quick easy audition.

on the teensy 4 performance - its a screamer - almost makes it too easy!

cheers Paul

Hi Paul,

I've extended my AnttiHuovilainen audio-object update routine to include cutoff frequency modualtion at full sample rate. So because that model corrects the coefficients whenever Fc changes, I need to call the coefficient correction routine at sample rate. The correction is needed becasue FC and resonance interact in these digital 'moog' filters, and you can quickly run into stability issues if it's not done right.

I've written a quick and dirty routine to measure CPU usage by counting near empty loop cycles and comparing that with and without audio interupts enabled. When I run two bandwidth limited oscillators into my FC-modulated version of hte model using a Teensy 4.0, it seems to take up a little over 30% of the max CPU processing power. If I disable my FC-modulation (i.e. no calls ot the correction code in the update), it reduces to about 20%. And if I use the fast-tanh approxiamtion from Utils.h, it drops all the way down to 12% and still sounds just as good. Even with full audio BW Fc modualtion enabled, but using fast_tanh, CPU usage is close to 15% using my rough measure.

I can't easily auditoin all 8 models you have listed because I've only translated 4 of them into audio-library compatible objects so far. On your recommendation, I'll translate the Oberheim just for fun, but I think I pretty much have the 'Moog' filter I want with the AnttiHuovilainen setup I have now.

Cheers,
Richard
 
hi @rvh

when I was looking at this I thought the AnttiHuovilainen was the most promising - I'm not sure I follow what you're doing with the coefficient code, i can't see what would change sample by sample?

the other one I thought was good was the Oberheim, like I said not very moog'y but nice - try just changing the #ifdef for a quick easy audition.

on the teensy 4 performance - its a screamer - almost makes it too easy!

cheers Paul

I implemented a simplified version of the 'oberheim' today which produces only the 4-pole LPF response. I still have a small issue to resolve, but it certainly sounds very 'fat', uses only a single tanh, and decouples FC and resonance very nicely (so no need for those pesky corrections). I did find that you can get the 'AnttiHuovilainen moog' model to also sound more like the 'oberheim' by adding some overdrive to the first input stage. Perhaps the 'oberheim' model can similarly be made to sound more like the 'AnttiHuovilainen' model by altering its front-end non-linearity to be less aggressive.

I found a pdf that helped me understand the 'oberheim' model better here: https://www.researchgate.net/public...ilter_Algorithms_for_Virtual_Analog_Synthesis. So it turns out that this model too is authored by AnttiHuovilainen (and Valimaki?). The 'oberheim' name of the code we have here presumably relates to the fact that you can add the filter stages in different ways to get HPF, BPF, LPF responses, as was mentione din the paper to be inspired by the oberheim expander.
 
I rewrote the 'oberheim' model code so it follows the above paper more precisely, and it now has noticeably improved FC tracking that's very similar to the full AnttiHuovilainen model. Very nice indeed.
 
Hi Paul,

I've extended my AnttiHuovilainen audio-object update routine to include cutoff frequency modualtion at full sample rate. So because that model corrects the coefficients whenever Fc changes, I need to call the coefficient correction routine at sample rate. The correction is needed becasue FC and resonance interact in these digital 'moog' filters, and you can quickly run into stability issues if it's not done right.

I've written a quick and dirty routine to measure CPU usage by counting near empty loop cycles and comparing that with and without audio interupts enabled. When I run two bandwidth limited oscillators into my FC-modulated version of hte model using a Teensy 4.0, it seems to take up a little over 30% of the max CPU processing power. If I disable my FC-modulation (i.e. no calls ot the correction code in the update), it reduces to about 20%. And if I use the fast-tanh approxiamtion from Utils.h, it drops all the way down to 12% and still sounds just as good. Even with full audio BW Fc modualtion enabled, but using fast_tanh, CPU usage is close to 15% using my rough measure.

I can't easily auditoin all 8 models you have listed because I've only translated 4 of them into audio-library compatible objects so far. On your recommendation, I'll translate the Oberheim just for fun, but I think I pretty much have the 'Moog' filter I want with the AnttiHuovilainen setup I have now.

Cheers,
Richard

Hi Richard
- I didn't realise that you were modulating the cutoff - makes sense now
- Easy audition - just in case you missed the intent of the files on GitHub - it is set up so that you change the #define in MusicDSP_3.ino to select the model (e.g. #define RKSM) to quickly try them out (el_supremos work I just put them up there and added the AnttiHuovilainen model

Sound like great progress and thanks for sharing paper which I'll get a read at over the weekend. If you were willing to share your modified moog object any time that would be much appreciated

Cheers Paul
 
Sound like great progress and thanks for sharing paper which I'll get a read at over the weekend. If you were willing to share your modified moog object any time that would be much appreciated
Cheers Paul

No problem. Here's my first draft. Not optimised or carefully checked yet, but sounds pretty good to me and is light on CPU.

//-----------------------------------------------------------
// Huovilainen New Moog (HNM) model as per CMJ jun 2006
// Implemented as Teensy Audio Library compatible object
// Richard van Hoesel, Feb. 5 2021, v.1.0
// please retain this header if you use this code.
//-----------------------------------------------------------
#include <math.h>
#include <stdint.h>

class HNMoog : public AudioStream
{
public:
HNMoog() : AudioStream(1,inputQueueArray) {};
void SetCutoff(float FC);
void SetResonance(float reson);
void SetDrive(float drive);
double LPF(double s, int i);
virtual void update(void);

private:
double alpha = 1.0;
double beta[4] = {0};
double z0[4] = {0};
double z1[4] = {0};
double K;
double Q=3;
double overdrive = 1.0;
double cutoff, Fbase;
audio_block_t *inputQueueArray[1];
};

void HNMoog::SetDrive(float drive)
{
overdrive = drive;
}

double HNMoog:: LPF(double s, int i)
{
float ft = s * (1/1.3) + (0.3/1.3) * z0 - z1 ;
ft = ft * alpha + z1;
z1= ft;
z0=s;
return ft;
}

void HNMoog::SetCutoff(float c)
{
cutoff = c;
double wc = 2.0 * MOOG_PI * cutoff / 44100;
double wc2 = wc*wc;
double G = 0.9892 * wc - 0.4324 * wc2 + 0.1381 * wc * wc2 - 0.0202*wc2*wc2;
alpha = G;
};

void HNMoog::SetResonance(float res)
{ // maps resonance = 0->1 to K = 0 -> 4
K = 4.0 * res;
};

void HNMoog::update(void)
{
audio_block_t *block;
float FCmod, filter_out;

block = receiveWritable(0);
if (!block)
return;

for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++)
{
float input = block->data * (1.0/32768) ;
double u = input - (z1[3] - 0.5*input) * K;
u = fast_tanh(overdrive * u);
double stage1 = LPF(u,0);
double stage2 = LPF(stage1,1);
double stage3 = LPF(stage2,2);
double stage4 = LPF(stage3,3);
block->data = stage4 * 32767.0 ;
}
transmit(block);
release(block);
}
 
Two performance suggestions:
1. Use f for constants: like 0.9892f instead of 0.9892. Constants by default are double. Float is twice as fast even on hardware FPU
2. Use float instead of double (for audio 32bit float offers enough resolution and dynamic range)
 
Two performance suggestions:
1. Use f for constants: like 0.9892f instead of 0.9892. Constants by default are double. Float is twice as fast even on hardware FPU
2. Use float instead of double (for audio 32bit float offers enough resolution and dynamic range)

Thanks, I will take those suggestions onboard. But even as it stands, it is a very low computational load for a filter that sounds as good as this. On a Teensy 4.0 it takes only about 5% (and maybe less) of the available processing power. In contrast the full Huovilainen moog model also described in this thread takes around 30%. To my ears the simplified one is a good approximation when you don't need very high Q (approaching full self resonance). It also has the advantage of being very well behaved level-wise. The full model needs quite a bit of post-processing to keep levels bound in my experience.
 
Here's my first draft. Not optimised or carefully checked yet, but sounds pretty good to me and is light on CPU.

Should I merge this into the audio library? Maybe with all the 64 bit double changed to 32 bit float?

Is there another file for the fast_tanh() function?
 
Should I merge this into the audio library? Maybe with all the 64 bit double changed to 32 bit float?

Is there another file for the fast_tanh() function?

Sure, feel free to clean this up and add it to the library (I'm sure you can tell I'm not really a programmer). The fast_tanh funciton is not mine. It's from utils.h, which you can find e.g. here: https://github.com/houtson/Teensy-4-Test-Area/tree/master/src. But it's just the standard approximation. Here's the utils.h code for it:

inline double fast_tanh(double x)
{
double x2 = x * x;
return x * (27.0 + x2) / (27.0 + 9.0 * x2);
}
 
A couple more quick questions...

Should the final version have signal "CV" inputs to control the frequency or other parameters? Or is just setting them from the Arduino code good enough?

When this is in the design tool, is "ladder" a good word to use? Or will people really only know this by "Moog" or "Moog Ladder"? (I'm an electronics & software guy, not a musician or synth guy) I'm a little concerned about naming it "Moog" because of trademarks.
 
Note also that:

(1) this code is for a fixed sample rate of 44100. You may want to change 44100 everywhere in the code to whatever the variable is that describes that in the library.

(2) it might be good to add limits to what values are passed into the parameters.
E.g. SetResonance(0 to 1), SetCutoff( 1 -> 0.49 * samplerate), and SetDrive(1 to 3).

It might in fact more in line with the other filters if you left the drive parameter out altogether. I've so far seen little need to set it to anything other than 1.
 
A couple more quick questions...

Should the final version have signal "CV" inputs to control the frequency or other parameters? Or is just setting them from the Arduino code good enough?

When this is in the design tool, is "ladder" a good word to use? Or will people really only know this by "Moog" or "Moog Ladder"? (I'm an electronics & software guy, not a musician or synth guy) I'm a little concerned about naming it "Moog" because of trademarks.

CV for frequency would probably suffice for most people, but you will likely want to do the modulation at sample rate, which increases the processing load. And even when done at full samplerate, you need to limit the modulation rate to avoid excessive aliasing.

If you use 'ladder' as the name, I think many musicians will still need something like 'moog-style' or 'moog-like' in the description.
 
Here's revision 1.01 which I believe is pretty much self contained now. Everything is now limited to floats, and I added a cutoff frequency "CV" modulation input which expects data in the audio-lbrary's 128-pt block format so it can be driven by the standard waveform generators.

I removed the overdrive gain because it degraded audio quality imo when it was greater than 1. I also added some range checks on the resonance, and also the cutoff freq in the coefficient calc. I also found what the sampling rate is called in the audio library so it should still work now if the sampling rate is changed. If you decide to disable the max resonance limit of 1, be sure to watch out for aliasing at higher Fc values.

Even with the modulation added, this filter + three band-limited audio oscillators + a sine LFO modulator, seem to use up just 4% of a Teensy 4.0's available processing power.

The code seems to be working fine to me, but it would be good if one or two people could verify this before it is merged into the libraray.

//-----------------------------------------------------------
// Huovilainen New Moog (HNM) model as per CMJ jun 2006
// Implemented as Teensy Audio Library compatible object
// Richard van Hoesel, Feb. 9 2021
// v.1.01 now includes FC "CV" modulation input
// please retain this header if you use this code.
//-----------------------------------------------------------
#include <math.h>
#include <stdint.h>
#define MOOG_PI 3.14159265358979323846264338327950288

class HNMoog : public AudioStream
{
public:
HNMoog() : AudioStream(2,inputQueueArray) {};
void SetCutoff(float FC);
void SetResonance(float reson);
float LPF(float s, int i);
void compute_coeffs(float fc);
virtual void update(void);

private:
float alpha = 1.0;
float beta[4] = {0};
float z0[4] = {0};
float z1[4] = {0};
float K;
float Fbase = 1000;
float overdrive = 1.0;
audio_block_t *inputQueueArray[2];
};

float HNMoog:: LPF(float s, int i)
{
float ft = s * (1/1.3) + (0.3/1.3) * z0 - z1 ;
ft = ft * alpha + z1;
z1= ft;
z0=s;
return ft;
}

void HNMoog::SetResonance(float res)
{ // maps resonance = 0->1 to K = 0 -> 4
if(res>1)
res=1;
else if(res<0)
res = 0;
K = 4.0 * res;
}

void HNMoog::SetCutoff(float c)
{
Fbase = c;
compute_coeffs(c);
}

void HNMoog::compute_coeffs(float c)
{
if(c > 0.49 * AUDIO_SAMPLE_RATE_EXACT )
c= 0.49 * AUDIO_SAMPLE_RATE_EXACT ;
else if(c < 1)
c=1;

float wc = c * ( 2.0 * MOOG_PI / AUDIO_SAMPLE_RATE_EXACT) ;
float wc2 = wc*wc;
alpha = 0.9892f * wc - 0.4324f * wc2 + 0.1381f * wc * wc2 - 0.0202f * wc2 * wc2;
}


inline float fast_tanh(float x)
{
float x2 = x * x;
return x * (27.0 + x2) / (27.0 + 9.0 * x2);
}

void HNMoog::update(void)
{
audio_block_t *blocka, *blockb;
float ftot, FCmod;
bool FCmodActive = true;

blocka = receiveWritable(0);
blockb = receiveReadOnly(1);
if (!blocka) {
if (blockb) release(blockb);
return;
}
if (!blockb)
FCmodActive = false;
for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++)
{
float input = blocka->data * (1.0/32768) ;
ftot = Fbase;
if(FCmodActive)
{
FCmod = blockb->data * (1.0/32768) ;
ftot += Fbase * FCmod;
if( FCmod != 0 )
compute_coeffs(ftot);
}
float u = input - (z1[3] - 0.5 * input) * K;
u = fast_tanh(u);
float stage1 = LPF(u,0);
float stage2 = LPF(stage1,1);
float stage3 = LPF(stage2,2);
float stage4 = LPF(stage3,3);
blocka->data = stage4 * 32767.0 ;
}
transmit(blocka);
release(blocka);
if(blockb)
release(blockb);
}
 
Last edited:
Paul, you'll see I've made the cutoff modulation range relative to the fixed filter cutoff setting. So e.g at Fc=1000, a full range modulator signal varies the cutoff between 0 (actually 1Hz) and 2kHz. If that's different to how you do it in the existing state variable filter, feel free to alter it here.
 
"synth guy" here:

A usefull filter should have "CV" for Cutoff and Resonance preferably up to audio rate for FM. "Ladder" should be sufficient, also to avoid copyright infringements.
 
"synth guy" here:

A usefull filter should have "CV" for Cutoff and Resonance preferably up to audio rate for FM. "Ladder" should be sufficient, also to avoid copyright infringements.

I show just FC modulation for now because it's the most commonly modulated filter control, and that also keeps it in line with the state variable filter object that's already in the library. But you can add CV for any other parameter you want in the same way by adding extra audio blocks to the object. And because the cutoff is modulated at sample rate it does indeed allow audio band modulation. I had some concerns about stability and aliasing at high modulation rates, but it actually seems to be pretty well behaved. Why not try it out and see what you think?
 
I show just FC modulation for now because it's the most commonly modulated filter control, and that also keeps it in line with the state variable filter object that's already in the library. But you can add CV for any other parameter you want in the same way by adding extra audio blocks to the object. And because the cutoff is modulated at sample rate it does indeed allow audio band modulation. I had some concerns about stability and aliasing at high modulation rates, but it actually seems to be pretty well behaved. Why not try it out and see what you think?

For me its perfectly OK, but i know how these hardcore geeks are: a Low Frequency Generator has to go from slower than a galcier up to 20khz, an analog VCO has to track withing +-2ct and everything has to be able to be modulated at audio rates, preferably up to 20khz as well.....

From my perspective, if you have 1 Cutoff an 1 Resonance Input its sufficient, everybody should be able to mix CV signals so theres no need for a Cutoff AND an additional FM input. I guess this comes from modular synthesis, where having 2 input saves you a mixer, thus precious real estate in your rack.
 
A few more questions...

1: @rvh - I need to ask your permission to add MIT license header? Of course I'll also keep the comments you have, and use your name in the MIT license copyright. The rest of the audio library is MIT license, so this is a small but important formality to merge this code. I want to merge the code from msg #43 with only minor formatting changes, and then as it's refined the changes from this point forward will be in the github commit history.

2: I'd like to create at least 1 good example program to include with the library. But I'm not a synth guy... so I need some suggestions. Here's a commercial modular synth Moog-style ladder filter I found with a quick google search. On the bottom of that page is a "How does it sound?" section with 3 video demos. Any suggestions on how the "Sequence with 3 x VCO's, Square Waveforms" patch is working and what I might do to try recreating it?

Or any other suggestions for example programs to demonstrate using this filter?

3: Regarding a resonance input (full audio bandwidth rather than limited update rate from Arduino code), I'm happy to have it in the library, if it can be done similarly to the frequency modulation input where the not-used case adds virtually no CPU overhead. But I also want to come up with an example program which demonstrates its usage to achieve interesting sounds, which couldn't be done well by just repeatedly altering the setting from Arduino code. There too, I'm not a synth guy. I can do quite a bit on the code size, but I'm really depending on feedback here for how to create compelling examples that demonstrate resonance modulation.
 
A few more questions...(snip)

2: I'd like to create at least 1 good example program to include with the library. But I'm not a synth guy... so I need some suggestions. Here's a commercial modular synth Moog-style ladder filter I found with a quick google search. On the bottom of that page is a "How does it sound?" section with 3 video demos. Any suggestions on how the "Sequence with 3 x VCO's, Square Waveforms" patch is working and what I might do to try recreating it?

Or any other suggestions for example programs to demonstrate using this filter?

The cult around the Moog Filter is all about its iconic sound. For comparison simple filter sweeps of an rectangle Waveform with different frequencies and resonance settings should be sufficient. Most interesting to synth-guys is the behavior with high resonance settings, and if it can nicely go into self resonance, producing a nice clean sine. Because this is where all the digital filters fail.

Personally, i have never heard a digital Filter that nails the Moog sound (which i personally dont care for), but every manufacturer that claimed for that specific sound and (obviously) failed was grilled, because of conjuring up expectations of some epic sound most of the ones that grill never heared "naked". For that reason i'd like to recommend being extremely cautions labeling anything "Moog".


3: Regarding a resonance input (full audio bandwidth rather than limited update rate from Arduino code), I'm happy to have it in the library, if it can be done similarly to the frequency modulation input where the not-used case adds virtually no CPU overhead. But I also want to come up with an example program which demonstrates its usage to achieve interesting sounds, which couldn't be done well by just repeatedly altering the setting from Arduino code. There too, I'm not a synth guy. I can do quite a bit on the code size, but I'm really depending on feedback here for how to create compelling examples that demonstrate resonance modulation.

I cant think of any usefull thing to do with modulating resonance at audio rate, but thats just me. Many others in "modular land" insist producing harmonics that way. Just to filter them away then....
 
I put the code on github and make one small fix and adapted the names to be similar to the other filters.

https://github.com/PaulStoffregen/Audio

If anyone wants to give this a try, please grab the latest from github. If you're not familiar with using git, the simplest way is to just download the ZIP file and extract it in {Documents}/Arduino/libraries. Just keep in mind anything to put there overrides all the other locations, so remember to delete this test copy when you later want to use the audio library the Teensyduino installer puts into your copy of Arduino.

Since there aren't any test programs yet, I wrote this very simple program which drives the filter with bursts of a sawtooth waveform.

Code:
#include <Audio.h>

AudioSynthWaveform       waveform1;
AudioFilterLadder        filter1;
AudioOutputMQS           mqs1;
AudioConnection          patchCord1(waveform1, 0, filter1, 0);
AudioConnection          patchCord2(filter1, 0, mqs1, 0);
AudioConnection          patchCord3(filter1, 0, mqs1, 1);

void setup() {
  AudioMemory(40);
  filter1.resonance(1.0);
  filter1.frequency(440);
  waveform1.begin(WAVEFORM_SAWTOOTH);
  waveform1.frequency(100);
}

void loop() {
  waveform1.amplitude(0.9);
  delay(500);
  waveform1.amplitude(0);
  delay(1500);
}

I'm not a synth guy, so I really don't know what this filter is supposed to sound like. But here's the output I see with my oscilloscope at the end of the sawtooth burst.

file.png

I'm not 100% confident it's really correct. As the test repeats every 2 seconds, the amount of 440 Hz superimposed on the sawtooth seems to vary quite a lot. Maybe later I'll try to capture a time-lapse video of my scope screen....

Really, I'm depending on everyone here with an ear for what the Moog sound is supposed to be to say whether they're happy with this, or if more work is needed before releasing it.

Also, still need a clear confirmation on the MIT license before this can be released.
 
Back
Top