Porting moog ladder filters to audio objects?

I want to emphasize how we *really* need more real listening tests.

If you're a synth person reading this thread and feeling like you don't have a lot of input about hyperbolic tangents and floating point precision, please don't feel shy. Math is important. It's ultimately the tool used, but math and tech details aren't the end goal here. We're not writing an academic paper! We're trying to create software almost anyone can use to create these classic synth sounds. The result that truly matters is sound delivered to human ears! How the sound is actually perceived is just as important as which non-linear functions were employed deep inside the code.

Especially if you have access a genuine analog Moog synth, please jump into this conversation. Don't be shy to show photos of your patches and presets... and lets talk about how to try reproducing that in the audio library. I really want to get to the point where we can truly compare the sounds. I'm not a "synth guy" and I have no access to any analog synths (though I'm considering building the Moog ladder circuit here....) so your input based on synth know-how and listening tests are a critical part of all this.
 
Thanks tomas, good to know I already knew about all these issues and have improved them in the next version, which I will post below. However, you should know that the passband signal has intentionally been boosted in that first version to avoid it dropping quite so much at self resonance, as it does in full blown models of the moog ladder. In the next version, you can access that value as a parameter called "passband_gain", which you should set to zero to be compatible with the more typical -12dB passband reduction you get with the full model.
 
Here is the filter_ladder.cpp file for the new version 1.03:

/* 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.
*/

//--------------------------------------------------------------
// Based on Huovilainen New Moog (HNM) model as per CMJ jun 2006
// Implemented as Teensy37 Audio Library compatible object
// Richard van Hoesel, v. 1.03, Feb. 14 2021
// v.1.03 adds oversampling, extended resonance,
// and exposes parameters input_drive and passband_gain
// v.1.02 now includes both cutoff and resonance "CV" modulation inputs
// please retain this header if you use this code.
//--------------------------------------------------------------

// https://forum.pjrc.com/threads/60488?p=269755&viewfull=1#post269755
// https://forum.pjrc.com/threads/60488?p=269609&viewfull=1#post269609

#include <Arduino.h>
#include "filter_ladder.h"
#include <math.h>
#include <stdint.h>
#define MOOG_PI ((float)3.14159265358979323846264338327950288)

//#define osTimes 1
//#define MAX_RESONANCE ((float)1.1)
//#define MAX_FREQUENCY ((float)(AUDIO_SAMPLE_RATE_EXACT * 0.249f))

#define osTimes 2
#define MAX_RESONANCE ((float)1.2)
#define MAX_FREQUENCY ((float)(AUDIO_SAMPLE_RATE_EXACT * 0.38f))
#define lfq 0.25

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

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

void AudioFilterLadder::frequency(float c)
{
Fbase = c;
compute_coeffs(c);
}

void AudioFilterLadder:: octaveControl(float octaves)
{
if (octaves > 7.0f) {
octaves = 7.0f;
} else if (octaves < 0.0f) {
octaves = 0.0f;
}
octaveScale = octaves / 32768.0f;
}

void AudioFilterLadder:: passband_gain(float passbandgain)
{
pbg = passbandgain;
if (pbg>0.5) pbg = 0.5f;
if (pbg<0) pbg = 0.0f;
input_drive(host_overdrive);
}

void AudioFilterLadder::input_drive(float odrv)
{
host_overdrive = odrv;
if(host_overdrive >1)
{
if(host_overdrive>4) host_overdrive = 4.0f;
overdrive = 1.0f + (host_overdrive - 1.0f) * (1.0 - pbg); // max is 4 when pbg = 0, and 2.5 when pbg is 0.5
}
else
{
overdrive = host_overdrive;
if(overdrive < 0) overdrive = 0.0f;
}
}

void AudioFilterLadder::compute_coeffs(float c)
{
if (c > MAX_FREQUENCY) {
c = MAX_FREQUENCY;
} else if (c < 1.0f) {
c = 1.0f;
}
#ifdef lfq
if(c<500) lfkmod = 1.0f + (500.0f-c)*(1.0f/500.0f) * lfq;
#endif
float wc = c * (float)(2.0f * MOOG_PI / (osTimes * AUDIO_SAMPLE_RATE_EXACT));
float wc2 = wc * wc;
alpha = 0.9892f * wc - 0.4324f * wc2 + 0.1381f * wc * wc2 - 0.0202f * wc2 * wc2;
//Qadjust = 1.0029f + 0.0526f * wc - 0.0926 * wc2 + 0.0218* wc * wc2;
Qadjust = 1.006f + 0.0536f * wc - 0.095 * wc2 ;
}

bool AudioFilterLadder::resonating()
{
for (int i=0; i < 4; i++) {
if (fabsf(z0) > 0.0001f) return true;
if (fabsf(z1) > 0.0001f) return true;
}
return false;
}

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;
}

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

void AudioFilterLadder::update(void)
{
audio_block_t *blocka, *blockb, *blockc;
float Ktot;
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 = 0;
}
}
if (!blockb) {
FCmodActive = false;
}
if (!blockc) {
QmodActive = false;
Ktot = K;
}
for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
float input = blocka->data * (1.0f/32768.0f) * overdrive;
if (FCmodActive) {
float FCmod = blockb->data * octaveScale;
float ftot = Fbase * fast_exp2f(FCmod);
if (ftot > MAX_FREQUENCY) ftot = MAX_FREQUENCY;
compute_coeffs(ftot);
}
if (QmodActive) {
float Qmod = blockc->data * (1.0f/32768.0f);
Ktot = K + 4.0f * Qmod;
}
#ifdef lfq
if(Ktot > MAX_RESONANCE * 4 * lfkmod) Ktot = MAX_RESONANCE * 4 * lfkmod;
#else
if(Ktot > MAX_RESONANCE * 4 ) Ktot = MAX_RESONANCE * 4;
#endif
if (Ktot < 0.0f) Ktot = 0.0f;
float total = 0;
for(int os = 0; os < osTimes; os++)
{
float u = input - (z1[3] - pbg * input) * Ktot;
u = fast_tanh(u);
float stage1 = LPF(u, 0);
float stage2 = LPF(stage1, 1);
float stage3 = LPF(stage2, 2);
float stage4 = LPF(stage3, 3);
total += stage4 * 1/osTimes;
}
blocka->data = total * 32767.0f ;
}
transmit(blocka);
release(blocka);
if (blockb) release(blockb);
if (blockc) release(blockc);
}
 
And the filter_ladder.h file:

/* 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.
*/

//--------------------------------------------------------------
// Based on Huovilainen New Moog (HNM) model as per CMJ jun 2006
// Implemented as Teensy Audio Library compatible object
// Richard van Hoesel, v. 1.03, Feb. 14 2021
// v.1.03 adds oversampling, extended resonance,
// and exposes parameters input_drive and passband_gain
// v.1.02 now includes both cutoff and resonance "CV" modulation inputs
// please retain this header if you use this code.
//--------------------------------------------------------------

// https://forum.pjrc.com/threads/60488?p=269609&viewfull=1#post269609

#ifndef filter_ladder_h_
#define filter_ladder_h_

#include "Arduino.h"
#include "AudioStream.h"

class AudioFilterLadder: public AudioStream
{
public:
AudioFilterLadder() : AudioStream(3, inputQueueArray) {};
void frequency(float FC);
void resonance(float reson);
void octaveControl(float octaves);
void passband_gain(float passbandgain);
void input_drive(float drv);
virtual void update(void);
private:
float LPF(float s, int i);
void compute_coeffs(float fc);
bool resonating();
float alpha = 1.0;
float beta[4] = {0.0, 0.0, 0.0, 0.0};
float z0[4] = {0.0, 0.0, 0.0, 0.0};
float z1[4] = {0.0, 0.0, 0.0, 0.0};
float K = 2.8;
float Fbase = 800;
float Qadjust = 1.0f;
float octaveScale = 1.0f/32768.0f;
float pbg = 0.0f;
float overdrive = 1.0f;
float host_overdrive = 1.0f;
float lfkmod = 1.0;
audio_block_t *inputQueueArray[3];
};

#endif
 
Here's a short example demonstrating the extended resonance and cutoff range in this version, with cutoff swept over pretty much full range at max resonance. If you change the input_gain value to say 3 (4 is max), you'll hear what an overdriven version sounds like. A value of 1 is unity gain at the input, 0 nulls the audio input.

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioFilterLadder filter1; // v1.03 teensy lib submitted adds passband gain, drv, and (fixed) LF-Qboost
AudioSynthWaveformModulated waveform1;
AudioSynthWaveformModulated waveform2;
AudioSynthWaveformModulated waveform3;
AudioSynthWaveform lfo1;
AudioSynthWaveform lfo2;
AudioSynthWaveform sineFM;
AudioSynthWaveform sineAM;
AudioMixer4 mixer1;
AudioMixer4 mixer2;
AudioEffectMultiply multiply1;
AudioEffectMultiply multiply2;
AudioOutputI2S i2s1;
AudioOutputUSB usb1;
AudioControlSGTL5000 sgtl5000_1;

AudioConnection patchCord1(waveform1,0,mixer1,0);
AudioConnection patchCord2(waveform2,0,mixer1,1);
AudioConnection patchCord3(waveform3,0,mixer1,2);
AudioConnection pcNMoogIn1(mixer1, 0, filter1, 0);
AudioConnection pcNMoogFCmod1(lfo1, 0, filter1, 1); // FC mod
AudioConnection pcNMoogQmod1(lfo2, 0, filter1, 2); // Qmod
AudioConnection pcNMoogOut10(filter1, 0, i2s1, 0);
AudioConnection pcNMoogOut11(filter1, 0, i2s1, 1);
AudioConnection patchCordUSB1(filter1, 0, usb1, 0);
AudioConnection patchCordUSB2(filter1, 0, usb1, 1);

float FC = 800;
float FcDep = 0.99;
float QDep = 0.0; // resonance modulation not used in this example
float Q = 1.2; // total reosnance in the filter is limted to 1.2, inclsive of additive modulation
float FoctRange = 4.5;
float InputDrive = 1; // limited between 0-4. 0 means no audio input, 1=unity input gain, 4 = max saturaion/compression
float PassBandGain = 0.0; // limited between 0-0.5 where 0 is no-passband compensation at high resonance, and 0.5 is the max increase

void setup()
{
Serial.begin(9600);
AudioMemory(20);
sgtl5000_1.enable();
sgtl5000_1.volume(0.4);
sgtl5000_1.lineOutLevel(1);
AudioNoInterrupts();
waveform1.frequency(70.01);
waveform2.frequency(105.0);
waveform1.amplitude(0.5);
waveform2.amplitude(0.4);
waveform1.begin(WAVEFORM_BANDLIMIT_SAWTOOTH);
waveform2.begin(WAVEFORM_BANDLIMIT_SAWTOOTH);
lfo1.frequency(0.1);
lfo1.amplitude(FcDep);
lfo2.frequency(0.25);
lfo2.amplitude(QDep);
lfo1.begin(WAVEFORM_SINE);
lfo2.begin(WAVEFORM_SINE);
filter1.resonance(Q );
filter1.frequency(FC);
filter1.octaveControl(FoctRange);
filter1.passband_gain(PassBandGain);
filter1.input_drive(InputDrive);
AudioInterrupts();

}

void loop() {
Serial.print("CPU Usage: ");
Serial.print(" Filter1 CPU Usage: ");
Serial.print(filter1.processorUsageMax());
Serial.print("%, Total CPU Usage: ");
Serial.print(AudioProcessorUsageMax());
Serial.println("%");
filter1.processorUsageMaxReset();
AudioProcessorUsageMaxReset();
delay(1000);
}
 
Sorry, I did not want to sound harsh, I realised that I was too quick to post feedback, so I deleted it as it requires more time. I will run newest code and do more tests.

UPDATE: Now I am running your newest code and I can enjoy increased frequency range :)
 
Last edited:
Sorry, I did not want to sound harsh, I realised that I was too quick to post feedback, so I deleted it as it requires more time. I will run newest code and do more tests.

No problem at all. As I said, good to know that I heard the same issues.
 
I noticed that you are using copy of the same input sample when oversampling and averaging output. You may consider using linear interpolation on input and dropping on output.
 
I noticed that you are using copy of the same input sample when oversampling and averaging output. You may consider using linear interpolation on input and dropping on output.

Yes, I tried interpolating input samples too but it seemed to make no difference perceptually, at least for an oversampling factor of 2. And yes, perhaps I don't need to average output when down-sampling either.
 
I merged the new version from msg #103 & #104

https://github.com/PaulStoffregen/Audio/commit/701da3ddca64fd4bb8c8f32f1e7656a1a0322511

I took one small liberty to add a "Kmax" variable, to make the code a little clearer and pave the way for treating Kmax as a constant for the scenarios where we know it can't change.

Code:
                float input = blocka->data[i] * (1.0f/32768.0f) * overdrive;
                if (FCmodActive) {
                        float FCmod = blockb->data[i] * octaveScale;
                        float ftot = Fbase * fast_exp2f(FCmod);
                        if (ftot > MAX_FREQUENCY) ftot = MAX_FREQUENCY;
                        compute_coeffs(ftot);
                }
                if (QmodActive) {
                        float Qmod = blockc->data[i] * (1.0f/32768.0f);
                        Ktot = K + 4.0f * Qmod;
                }
                #ifdef lfq
                [B]Kmax = MAX_RESONANCE * 4.0f * lfkmod;[/B]
                #else
                [B]Kmax = MAX_RESONANCE * 4.0f;[/B]
                #endif
                if (Ktot > [B]Kmax[/B]) {
                        Ktot = [B]Kmax[/B];
                } else if (Ktot < 0.0f) {
                        Ktot = 0.0f;
                }

In the case where FCmodActive and QmodActive are both false, K, Ktot, & Kmax should be constants, right? Maybe we can somehow avoid this conditional testing on every audio sample?

In the case where FCmodActive is true but QmodActive is false, I'm wondering how Kmax and Ktot are affected? If the coefficient computation causes Kmax to temporarily become a lower limit, then Ktot is reduced to fit within the limit. But then what becomes of Ktot after another call to compute_coeffs() raises Kmax? As nearly as I can see, we keep the restricted Ktot for the rest of the samples in this block, possibly losing some of the resonance we should have allowed.
 
Maybe this helps, here are some Videos with sound examples:

about the MiniMoog Filter
https://www.youtube.com/watch?v=yPHJA092a9w

about self oscillation
https://www.youtube.com/watch?v=dVgIf71uWB4

hear here why self oscillation is crucial
https://www.youtube.com/watch?v=JZXpF1zpJkc (BTW Marco has many more sound demos to compare)

and here more Filter Sounds from 18:45 on
https://www.youtube.com/watch?v=f3dEAptsg_A

Pay attention that you have to consider the Oscillators contribute to the Moog Sound, also as they have slighlty skewed waveforms. So its pretty hard to compare Filter Sounds as no one feeds the filters with a standard signal to compare. At least i dont know of such a comparison.
 
Nothing musical or scientific but a quick video of the code from #103.

A little Moog Werkstatt with a simple saw wave through the Moog filter and through the teensy at various levels of resonance.

https://youtu.be/tvZ_xyXUmZQ

Teensy 4.0 with and audioboard, no additional processing. Input Drive is 1 throughout and Passgain is 0.

I think the new filter sounds pretty good.

Cheers Paul
 
Last edited:
With max resonance you can clearly hear that analog filter has some "gritty" quality to it (2:52 of your video), while digital is sterile and lacks this effect completely.
 
Nothing musical or scientific but a quick video of the code from #103.

A little Moog Werkstatt with a simple saw wave through the Moog filter and through the teensy at various levels of resonance.

https://youtu.be/tvZ_xyXUmZQ

Teensy 4.0 with and audioboard, no additional processing. Input Drive is 1 throughout and Passgain is 0.

I think the new filter sounds pretty good.

Cheers Paul

Thank Paul,
That's very helpful. It may well be the case that setting input_drive to '1' is not a good match to what's happening in the analog filter in terms of the degree of saturation/compression. Do the the two sound more similar if you push that value up a bit, maybe to 2 or so? I'm assuming also you have passband_gain dialed back to 0 for this comparison?
Cheers, Richard.
 
Hi Richard, yes passband_gain =0. I'll put a couple of pots on input_drive and passband_gain and have a play with that. Cheers Paul
 
Also, I think we can probably allow max-resonance to go a bit higher than the current limit of 1.2. Around 1.3 perhaps?
 
I would like to consider the names of these 2 new functions, before adding their documentation to the design tool. Would just "gain" work instead of "passband_gain"? We already use "gain" as a function name in other objects. While the use isn't perfectly analogous, my gut feeling is a simpler name and clear explanation in the design tool is probably easiest for people to use.

Is input_drive the final name we really want? The general goal with function names throughout the library is a word which expresses the value or action users wish to achieve.
 
With max resonance you can clearly hear that analog filter has some "gritty" quality to it (2:52 of your video), while digital is sterile and lacks this effect completely.

I wouldn't say completey. If you compare this filter with the the state-variable filter at self-resonance, you will hear a large difference. But I do agree the analog filter in the video has more of a narrowband noise-like self resonance. Adding tanh to every stage in my filter doens't seem to change things much in that direction, nor did detuning the four filter stages. I'm certainly open to suggestions for what else to try.

I've tried a few other models too and one that I seem to recall to have more noise-like resonance was the d'angleo (2012), which includes a differential expression each stage. But using the code for that model from this forum I could't get it's cutoff to go above about 2.5 khz for some reason. I never figured out why (maybe a bug), but it may be worth going back to for another look. If anyone has that model up and running, your input would be most welcome.
 
Last edited:
I would like to consider the names of these 2 new functions, before adding their documentation to the design tool. Would just "gain" work instead of "passband_gain"? We already use "gain" as a function name in other objects. While the use isn't perfectly analogous, my gut feeling is a simpler name and clear explanation in the design tool is probably easiest for people to use.

Is input_drive the final name we really want? The general goal with function names throughout the library is a word which expresses the value or action users wish to achieve.

'gain' usually means linear level scaling to me, which is not what the parameter does. You could use just drive, or overdrive if input_drive is too verbose (although overdrive to me implies a level of 0 rather than 1 would be unity gain). There are two reasons I can see why a user would want to control this. The first is to determine whether you have a cleaner or grungier sound (range 1-4). The other would be to attenuate the input (below 1) relative to the self resonance.
 
In a moog ladder filter, the passband signal is attenuated when you go to high resonance compared to low resonance. In the current model, the passband_gain parameter allows you to compensate for that somewhat. When it is set to 0, the filter behaves the usual way with about 12dB passband attenuation. I haven't checked this in my filter btw, but that's what's meant to happen in the original paper I derived this from, and what I hear when varying the parameter sounds about right. Setting it to 0.5 boosts the passband signal so it's attenuated only 6dB. You could go higher, but it doesn't sound very good to me, hence the limit. Also, when you set it to 0.5, the apparent amount of resonance compared to signal will be less. My suggestion therefore is to set it to 0 by default.
 
I merged the new version from msg #103 & #104

https://github.com/PaulStoffregen/Audio/commit/701da3ddca64fd4bb8c8f32f1e7656a1a0322511

I took one small liberty to add a "Kmax" variable, to make the code a little clearer and pave the way for treating Kmax as a constant for the scenarios where we know it can't change.

Code:
                float input = blocka->data[i] * (1.0f/32768.0f) * overdrive;
                if (FCmodActive) {
                        float FCmod = blockb->data[i] * octaveScale;
                        float ftot = Fbase * fast_exp2f(FCmod);
                        if (ftot > MAX_FREQUENCY) ftot = MAX_FREQUENCY;
                        compute_coeffs(ftot);
                }
                if (QmodActive) {
                        float Qmod = blockc->data[i] * (1.0f/32768.0f);
                        Ktot = K + 4.0f * Qmod;
                }
                #ifdef lfq
                [B]Kmax = MAX_RESONANCE * 4.0f * lfkmod;[/B]
                #else
                [B]Kmax = MAX_RESONANCE * 4.0f;[/B]
                #endif
                if (Ktot > [B]Kmax[/B]) {
                        Ktot = [B]Kmax[/B];
                } else if (Ktot < 0.0f) {
                        Ktot = 0.0f;
                }

In the case where FCmodActive and QmodActive are both false, K, Ktot, & Kmax should be constants, right? Maybe we can somehow avoid this conditional testing on every audio sample?

In the case where FCmodActive is true but QmodActive is false, I'm wondering how Kmax and Ktot are affected? If the coefficient computation causes Kmax to temporarily become a lower limit, then Ktot is reduced to fit within the limit. But then what becomes of Ktot after another call to compute_coeffs() raises Kmax? As nearly as I can see, we keep the restricted Ktot for the rest of the samples in this block, possibly losing some of the resonance we should have allowed.

I doubt the filter would be used much without FCmod, but possibly without Qmod. Perhaps we can revisit optimization once the filter is functionally finalized?
 
Perhaps we can revisit optimization once the filter is functionally finalized?

Yes, of course. I mistakenly thought we were at the final form or getting really close. Maybe not?

Also, I should mention, someone else reached out on Facebook and then private email to contribute a completely different implementation. The code is currently sitting in my inbox. I hope to adapt it to the audio library and commit a first version soon. It'll be a completely different file. Not sure what the call this other version, maybe "ladder2"?

FWIW, this other code has a very interesting lookup-based approach to tanhf...
 
Yes, of course. I mistakenly thought we were at the final form or getting really close. Maybe not?

Also, I should mention, someone else reached out on Facebook and then private email to contribute a completely different implementation. The code is currently sitting in my inbox. I hope to adapt it to the audio library and commit a first version soon. It'll be a completely different file. Not sure what the call this other version, maybe "ladder2"?

FWIW, this other code has a very interesting lookup-based approach to tanhf...

I think the specific tanh implementation is not so important. I hear no difference for example between double precision tanh and a simple 3rd order polynomial. It's the architecture/algorithm more generally that will make models sound different. Do you know what the implementation is based on (presumably something published)?
 
Back
Top