You are using an out of date browser. It may not display this or other websites correctly.

You should upgrade or use an alternative browser.

You should upgrade or use an alternative browser.

- Thread starter martianredskies
- Start date

Hi Florian, good to hear you're enjoying the filter. On my system, I get 7% for POLY_FIR, and 5% for LINEAR. To reduce it further I suggest going to 2x oversampling. However, that requires a different set of FIR coefficients. The ones from the newer filter that already runs at 2x should be fine, but they are 32pt rather than 36 so the changes to the .h file include changing both INTERPOLATION from 4 to 2, and interpolation_taps form 36 to 32.

Hi Richard,

thank you, I changed the coefficients and corrected the bug you found in the poly update routine and now it works great with relatively low CPU usage (also 4% for 2x oversampling and linear interpolation on my system).

I will also have a look at the full model, but unfortunately, I need to economise on my CPU ressources.

Last edited:

Hello TigerBalm,

weird, I noticed too that somehow, there are different versions of the gui tool. (with different indexes? )

This here -> https://www.pjrc.com/teensy/gui/ does not include the filter, whereas

this here -> https://www.pjrc.com/teensy/gui/index2.html?info=AudioFilterLadder

has it in.

Greetings, flo

filter_ladder2.cpp

Code:

```
/* Audio Library for Teensy, Ladder Filter
* Copyright (c) 2021, Richard van Hoesel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
//-----------------------------------------------------------
// AudioFilterLadder2 for Teensy Audio Library
// Based on Huovilainen's full model, presented at DAFX 2004
// Richard van Hoesel, March. 11, 2021
// v.1.02 includes linear interpolation option
// v.1.01 Adds a "resonanceLoudness" parameter (1.0 - 10.0)
// to control resonance loudness
// v.1.0 Uses 2x oversampling. Host resonance range is 0-1.0.
//
// please retain this header if you use this code.
//-----------------------------------------------------------
#include <Arduino.h>
#include "filter_ladder2.h"
#include "arm_math.h"
#include <math.h>
#include <stdint.h>
#define MAX_RESONANCE ((float)1.015)
#define MAX_FREQUENCY ((float)(AUDIO_SAMPLE_RATE_EXACT * 0.425f))
#define fI_NUM_SAMPLES AUDIO_BLOCK_SAMPLES * INTERPOLATION
//=== 2 xOS, 32taps (88.2kHz)
static float AudioFilterLadder2 ::finterpolation_coeffs[AudioFilterLadder2::finterpolation_taps] = {
-129.6800658538650740E-6, 0.001562843504973615, 0.004914813078250063, 0.007186231102125209, 0.002728024844356039,-0.007973931496234599,-0.013255882861166815,-841.1646993462468340E-6,
0.022309967341058432, 0.027240071522569135,-0.007073857882406733,-0.056523975383091875,-0.055929860712812876, 0.042278471323346570, 0.207056768528768836, 0.335633514003638445,
0.335633514003638445, 0.207056768528768836, 0.042278471323346570,-0.055929860712812876,-0.056523975383091875,-0.007073857882406733, 0.027240071522569135, 0.022309967341058432,
-841.1646993462468340E-6,-0.013255882861166815,-0.007973931496234599, 0.002728024844356039, 0.007186231102125209, 0.004914813078250063, 0.001562843504973615,-129.6800658538650740E-6
};
void AudioFilterLadder2::initpoly()
{
if (arm_fir_interpolate_init_f32(&interpolation, INTERPOLATION, finterpolation_taps,
finterpolation_coeffs, finterpolation_state, AUDIO_BLOCK_SAMPLES)) {
polyCapable = false;
return;
}
if (arm_fir_decimate_init_f32(&decimation, finterpolation_taps, INTERPOLATION,
finterpolation_coeffs, fdecimation_state, fI_NUM_SAMPLES)) {
polyCapable = false;
return;
}
// TODO: should we fill interpolation_state & decimation_state with zeros?
polyCapable = true;
polyOn = true;
}
void AudioFilterLadder2::interpolationMethod(AudioFilterLadderInterpolation imethod)
{
if (imethod == LADDER_FILTER_INTERPOLATION_FIR_POLY && polyCapable == true) {
// TODO: if polyOn == false, clear interpolation_state & decimation_state ??
polyOn = true;
} else {
polyOn = false;
}
}
void AudioFilterLadder2::resonanceLoudness(float v) // input 1-10
{
if(v<1) v=1;
if(v>10) v=10;
VTx2 = v;
inv2VT = 1.0/VTx2;
}
static inline float fast_exp2f(float x)
{
float i;
float f = modff(x, &i);
f *= 0.693147f / 256.0f;
f += 1.0f;
f *= f;
f *= f;
f *= f;
f *= f;
f *= f;
f *= f;
f *= f;
f *= f;
f = ldexpf(f, i);
return f;
}
void AudioFilterLadder2::inputDrive(float drive)
{
if(drive<1)
overdrive = 1.0f;
else
if(drive>3)
overdrive = 3.0f;
else
overdrive = drive;
}
void AudioFilterLadder2::portamento(float pc)
{
FcPorta = pc;
}
void AudioFilterLadder2::compute_coeffs(float freq, float res) // tuned for 2x os
{
float fc, freq2, freq3;
if (res > MAX_RESONANCE) res = MAX_RESONANCE ;
if (res < 0.0f) res = 0.0f; if (freq < 5) freq = 5;
if (freq > MAX_FREQUENCY) freq = MAX_FREQUENCY;
fc = freq * FC_SCALER;
freq2 = freq*freq;
freq3 = freq2 * freq;
float qa = 0.998f + 3.5e-5f * freq - 8e-10f * freq2 - 4e-14f * freq3;
K = 4.0f * res * qa;
float fa = 1.0036f - 2e-5f *freq + 1e-9f*freq2 - 1.75e-14f * freq3;
Gx2Vt = float((1.0f - exp(-fc * fa)) * VTx2);
}
void AudioFilterLadder2::frequency(float c)
{
if (c < 1) c = 1;
if (c > MAX_FREQUENCY) c = MAX_FREQUENCY;
targetFbase = c;
//Fbase = c;
}
void AudioFilterLadder2::resonance(float res)
{
res *= MAX_RESONANCE; // normalize so host specifies 0-1 rather than 0-1.015
if (res > MAX_RESONANCE) res = MAX_RESONANCE ;
if (res < 0.0f) res = 0.0f;
Qbase = res;
}
void AudioFilterLadder2::octaveControl(float octaves)
{
if (octaves > 10.0f) { // increased range compared to previous filters to span full audio range with 0-1 envelope
octaves = 10.0f;
} else if (octaves < 0.0f) {
octaves = 0.0f;
}
octaveScale = octaves / 32768.0f;
}
static inline float fast_tanh(float x)
{
if(x>3) return 1;
if(x<-3) return -1;
float x2 = x * x;
return x * (27.0f + x2) / (27.0f + 9.0f * x2);
}
bool AudioFilterLadder2::resonating()
{
for (int i=0; i < 4; i++) {
if (fabsf(z0[i]) > 0.0001f) return true;
if (fabsf(z1[i]) > 0.0001f) return true;
}
return false;
}
void AudioFilterLadder2::update(void)
{
audio_block_t *blocka, *blockb, *blockc;
float ftot, qtot;
bool FCmodActive = true;
bool QmodActive = true;
blocka = receiveWritable(0);
blockb = receiveReadOnly(1);
blockc = receiveReadOnly(2);
if (!blocka) {
if (resonating()) {
// When no data arrives but the filter is still
// resonating, we must continue computing the filter
// with zero input to sustain the resonance
blocka = allocate();
}
if (!blocka) {
if (blockb) release(blockb);
if (blockc) release(blockc);
return;
}
for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
blocka->data[i] = 0;
}
}
if (!blockb) {
FCmodActive = false;
ftot = Fbase;
}
if (!blockc) {
QmodActive = false;
qtot = Qbase;
}
float blockOut[AUDIO_BLOCK_SAMPLES];
if (polyOn == true) {
float blockIn[AUDIO_BLOCK_SAMPLES];
float blockOS[fI_NUM_SAMPLES], blockOutOS[fI_NUM_SAMPLES];
for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++)
blockIn[i] = blocka->data[i] * overdrive * float(INTERPOLATION /32768.0f);
arm_fir_interpolate_f32(&interpolation, blockIn, blockOS, AUDIO_BLOCK_SAMPLES);
for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
Fbase = FcPorta * Fbase + (1-FcPorta) * targetFbase;
ftot = Fbase;
if (FCmodActive) {
float FCmod = blockb->data[i] * octaveScale;
ftot = Fbase * fast_exp2f(FCmod);
}
if (QmodActive) {
float Qmod = blockc->data[i] * (1.0f/32768.0f);
qtot = Qbase + Qmod;
}
compute_coeffs(ftot,qtot) ;
for(int os = 0; os < INTERPOLATION; os++)
{
float input = blockOS[i * INTERPOLATION + os];
filter_y1 = filter_y1 + Gx2Vt * (fast_tanh((input - K * filter_out) * inv2VT) - save_tan1);
save_tan1= fast_tanh(filter_y1 * inv2VT);
filter_y2 = filter_y2 + Gx2Vt * (save_tan1 - save_tan2);
save_tan2 = fast_tanh(filter_y2 * inv2VT);
filter_y3 = filter_y3 + Gx2Vt * (save_tan2 - save_tan3);
save_tan3 = fast_tanh(filter_y3 * inv2VT);
filter_y4 = filter_y4 + Gx2Vt * (save_tan3 - fast_tanh(filter_y4 * inv2VT));
filter_out = (filter_y4 + filter_y5) * 0.5f;
filter_y5 = filter_y4;
blockOutOS[i*INTERPOLATION + os] = filter_out;
}
}
arm_fir_decimate_f32(&decimation, blockOutOS, blockOut, fI_NUM_SAMPLES);
}
else {
for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
Fbase = FcPorta * Fbase + (1-FcPorta) * targetFbase;
if (FCmodActive) {
float FCmod = blockb->data[i] * octaveScale;
ftot = Fbase * fast_exp2f(FCmod);
}
if (QmodActive) {
float Qmod = blockc->data[i] * (1.0f/32768.0f);
qtot = Qbase + Qmod;
}
compute_coeffs(ftot,qtot) ;
float total = 0.0f;
float interp = 0.0f;
float input = blocka->data[i] * overdrive * (1.0f/32768.0f);
for (int os = 0; os < INTERPOLATION; os++) {
float inputOS = (interp * oldinput + (1.0f - interp) * input);
filter_y1 = filter_y1 + Gx2Vt * (fast_tanh((inputOS - K * filter_out) * inv2VT) - save_tan1);
save_tan1= fast_tanh(filter_y1 * inv2VT);
filter_y2 = filter_y2 + Gx2Vt * (save_tan1 - save_tan2);
save_tan2 = fast_tanh(filter_y2 * inv2VT);
filter_y3 = filter_y3 + Gx2Vt * (save_tan2 - save_tan3);
save_tan3 = fast_tanh(filter_y3 * inv2VT);
filter_y4 = filter_y4 + Gx2Vt * (save_tan3 - fast_tanh(filter_y4 * inv2VT));
filter_out = (filter_y4 + filter_y5) * 0.5f;
filter_y5 = filter_y4;
total += filter_out * (1.0f / (float)INTERPOLATION);
interp += (1.0f / (float)INTERPOLATION);
}
blockOut[i] = total;
oldinput = input;
}
}
float absblk;
for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
absblk = 1.25f * blockOut[i];
if(absblk<0) absblk=-absblk;
if(absblk > 1)
if(absblk > peak)
peak = absblk;
}
for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
if(peak>1){
pgain = 0.99f * pgain + 0.01f/peak; // fast attack
peak *=0.99995f;
}
blocka->data[i] = blockOut[i] * pgain * float(0.85f * float(32767.0));
}
transmit(blocka);
release(blocka);
if (blockb) release(blockb);
if (blockc) release(blockc);
}
```

filter_ladder2.h

Code:

```
/* Audio Library for Teensy, Ladder Filter
* Copyright (c) 2021, Richard van Hoesel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
//-----------------------------------------------------------
// AudioFilterLadder2 for Teensy Audio Library
// Based on Huovilainen's full model, presented at DAFX 2004
// Richard van Hoesel, March. 11, 2021
// v.1.02 includes linear interpolation option
// v.1.01 Adds a "resonanceLoudness" parameter (1.0 - 10.0)
// to control resonance loudness
// v.1.0 Uses 2x oversampling. Host resonance range is 0-1.0.
//
// please retain this header if you use this code.
//-----------------------------------------------------------
// https://forum.pjrc.com/threads/60488?p=269609&viewfull=1#post269609
#ifndef filter_ladder2_h_
#define filter_ladder2_h_
#include "Arduino.h"
#include "AudioStream.h"
#include "arm_math.h"
#include <Audio.h>
#define MOOG_PI ((float)3.14159265358979323846264338327950288)
class AudioFilterLadder2: public AudioStream
{
public:
AudioFilterLadder2() : AudioStream(3, inputQueueArray) { initpoly(); };
void frequency(float FC);
void resonance(float reson);
void octaveControl(float octaves);
void inputDrive(float drive);
void portamento(float pcf);
void compute_coeffs(float freq, float res);
void initpoly();
void interpolationMethod(AudioFilterLadderInterpolation im);
void resonanceLoudness(float v);
virtual void update(void);
private:
static const int INTERPOLATION = 2;
static const int finterpolation_taps = 32;
static const float FC_SCALER = 2.0f * MOOG_PI / (INTERPOLATION * AUDIO_SAMPLE_RATE_EXACT);
float finterpolation_state[(AUDIO_BLOCK_SAMPLES-1) + finterpolation_taps / INTERPOLATION];
arm_fir_interpolate_instance_f32 interpolation;
float fdecimation_state[(AUDIO_BLOCK_SAMPLES*INTERPOLATION-1) + finterpolation_taps];
arm_fir_decimate_instance_f32 decimation;
static float finterpolation_coeffs[finterpolation_taps];
float Gx2Vt, K, filter_res, filter_y1, filter_y2, filter_y3, filter_y4, filter_y5, filter_out;
float VTx2 = 5;
float inv2VT = 1.0/VTx2;
float Fbase=1000, Qbase=0.5;
float overdrive=1;
float ftot=Fbase, qtot=Qbase;
bool resonating();
bool polyCapable = false;
bool polyOn = false; // FIR is default after initpoly()
float octaveScale = 1.0f/32768.0f;
float z0[4] = {0.0, 0.0, 0.0, 0.0};
float z1[4] = {0.0, 0.0, 0.0, 0.0};
float pbg=0;
float save_tan1=0;
float save_tan2=0;
float save_tan4=0;
float save_tan3=0;
float peak=1.0;
float pgain=1.0;
float targetFbase = 1000;
float FcPorta = 0;
float oldinput=0;
audio_block_t *inputQueueArray[3];
};
#endif
```

Is there a better way to get the ladder filter into my Arduino 1.8.13 environment?

I did finally got it to work by temporarily changing the name of the ladder filter Audio.h to temp_Audio,h and used #include temp_Audio.h in the sketch. I thought that if I put it in {Documents}/Arduino/libraries it was supposed to take precedence, but I guess that was an incorrect assumption.

If, for example, I run the four stage ladder filter AudioFilterLadder::update() at a 88000 Hz rate and if I modify the ladder filter code to eliminate any interpolation, is that equivalent to running the ladder filter at 44000 Hz with INTERPOLATION = 2?

I'm from the Axoloti world and just received my Teensy 4.0 board with the Audio Shield. Having fun testing out the capabilities and I'm impressed so far. I'd like to "port" a project of mine that's a Moog Taurus Bass Pedal emulator. I started this project as a Pure-Data model a few years back and I did use a model of Antti Huovilainen's Moog filter. It is excellent! Now, imagine my excitement when I discovered your thread!

Now, I've played with the "Ladder" filter that's included in the Audio library but I can't seem to figure out how can I use the updated (full model) version of this filter. Maybe I'm using the wrong dev tools? I'm using the basic Arduino/Teensyduino IDE. Thanks in advance guys and sorry for reviving an old thread.

Marc aka Khorus

PS: For the curious mind, here's a video of my current Taurus emulator that runs on Axoloti Core. https://www.facebook.com/727063099/videos/250096457328671/

Cheers,

Richard

Have you ever looked into the following paper, written by

Stefan D'Angelo (Arturia's VA plugins & Neural DSP) and the legendary Vesa Välimäki?

An improved virtual analog model of the Moog ladder filter

Abstract:

In this paper we derive a novel circuit-based model for the Moog filter and discretize it using the bilinear transform. The proposed nonlinear digital filter compares favorably against Huovilainen's model, which is the best previous white-box model for the Moog filter. The harmonic distortion characteristics of the proposed model match closely with those of a SPICE simulation. Furthermore, the novel model realistically enters the self-oscillation mode and maintains it. The proposed model requires only 12 more basic operations per output sample than Huovilainen's model, but includes the same number of nonlinear functions, which dominate the computational load. The novel nonlinear digital filter is applicable in virtual analog music synthesis and in musical audio effects processing.

Richard, I'm using your filter and it sounds amazing. It growls gracefully, my respect!

Have you ever looked into the following paper, written by

Stefan D'Angelo (Arturia's VA plugins & Neural DSP) and the legendary Vesa Välimäki?

An improved virtual analog model of the Moog ladder filter

Abstract:

Yes, I've looked at Stefan's papers in the past. I was hoping to get around to trying out some of his ideas but haven't yet, and they're not trivial to implement if I remember correctly.