My new programming language project: neetlisp
After some more tweaking here are my final results:
- Use a hard clipper (-1.0 to 1.0 range) on first integrator
- Resonance gain correction is (to my surprise) a simple linear function:
You can either use
factor = 10 * fc / fs + 1or
factor = 1 + f * PI(don’t ask me why … but both are accurate and provide self oscillation on all ‘fc’ at
- Currently the filter runs at 512kHz sample rate and gets FIR downsampled to the output rate. I recommend an output rate of 96kHz for full HiFi-analogue feeling (on 44.1kHz the resonance at high cutoff values might jitter to much and sound a bit harsh)
… this thing just sounds awesome … here’s another audio demo (using my calculated spline based resonance gain correction and not the method described at ‘2.’).
You might want to download the WAV for this sample because soundcloud’s audio encoding doesn’t provide the best sound quality for this purpose.
… stay tuned …
I’m on a quest … the quest for my perfect (audio lowpass) filter.
All of the standard (IIR) filter architectures (e.g. biquad, SVF, integrator) lack one thing: analogue sounding resonance.
So, what is 'analogue sounding' for me? If you ever played around with a good analogue synthesizer featuring fully analogue filters you should know what I mean:
- analogue resonance never sounds harsh
- self oscillation
- it doesn’t matter which cutoff settings you use: the resonance still kicks
Why do the standard filter architectures suck when it comes to resonance? Because of the evil thing called: unit delay.
In the digital world feedback is implemented by feeding the previous output to the input stage … but the last output was calculated one sample ago.
An analogue feedback path doesn’t have any unit delay. In fact it does have no delay (well, of course there’s a delay, but electrons are really fast so this can be neglected).
Awesome ASCII art of a simplified lowpass filter with feedback:
_____ --input--(+)--|LPF|---+---output-- | ~~~~~ | | | -----(x)----- <- feedback path
What happens here in the analogue domain is: The output 'value' is instantly available at the input summer. It feels like knowing the output value before it gets calculated/generated.
And this is exactly the key point for eliminating the unit delay from our feedback paths: Knowing the output value before it gets calculated.
Of course we can’t look into the future, but there’s another way. Currently I’m working again on some brute force integrator lowpass filter with zero-delay feedback path … and the first experiments are sounding great.
Here’s my recipe:
- Filter basis is the standard integrator building block:
buf += f * (in - buf)
tan(PI * fc/fs)
- Chaining 4 integrators equals a 4 pole lowpass filter
So we get (for our LPF):
buf0 += f * (input - buf0) buf1 += f * (buf0 - buf1) buf2 += f * (buf1 - buf2) buf3 += f * (buf2 - buf3)
buf3 now contains the output.
To add feedback we change the first line to:
buf0 += f * (input - output * r - buf0)
r is the feedback strength.
Doing this the old way would introduce a unit delay and ruin the resonance, so we implement some brute force iteration:
output = lastOutput DO copyBuffersToFilterBuffers() tmp = LPF(input - output * r) IF abs(tmp - output) <= EPSILON THEN copyFilterBuffersToBuffers() output = lastOutput = tmp BREAK ENDIF output = tmp LOOP
EPSILON of 1E-5 or less is recommended. As you can see the integrator states
get reset on each iteration (if we would not do this, then we would change the sampling rate
the filter runs on).
This structure already self oscillates (if
r is big enough, try it with a Dirac delta function)
but it will explode on self oscillation. As this is a 'real' feedback path, it behaves like
a real feedback path: ever moved a switched-on microphone to a speaker which outputs the
mic-input? This nice sine wave gets louder and louder … and this would happen with the
An analogue filter always has some kind of 'clipping' build in. This is no extra circuit but comes from component specifications, control voltages, … so we also need a 'clipper'. My first choice for a nice clipper is: tanh. So we change the first line in our LPF to:
buf0 = tanh(buf0 + f * (input - output * r - buf0))
Now the filter can’t explode on self oscillation and will behave nicely.
Another thing we sadly must do is: oversample like hell. I’m using an oversampling factor of at least 10x and a FIR filter for downsampling. Oversampling really enhances the resonance quality and also keeps the number of iterations down. More than 12x oversampling should not be necessary (assuming 44.1kHz output rate and a maximum cutoff of around 18-20kHz).
The current filter already sounds alright but still has some flaws:
- resonance peak is not constant for various cutoff frequencies (drops slightly on the highs and noticeable on the lows).
- the more resonance, the lower the output gain
- using a tanh clipper results in insane
rvalues for strong resonance (0.0->20.0) which then nearly eliminates the input signal
- oversampling, tanh and iteration eats up the CPU (I’m using a simple tanh approximation and have an average CPU (core) load of about 8% using 10x oversampling on an email@example.comGHz.
Right now I don’t have a clue why the resonance peak is not constant … but I guess it’s related to phase shifts and delays in the filter. This can be (slightly) compensated by using some function which maps from cutoff to a resonance-multiplier.
The second point can also be fixed quite easily by adjusting the gain for higher resonance values.
The tanh-clipper is a design thing, it can be replaced by any other clipper.
Another performance gain could be achieved by using a better initial value for the feedback iteration. Some nifty sample prediction algorithm might be used here … I’m still researching on this. It might also be a good idea to limit the maximum number of iterations.
After all, this filter needs some serious parameter tweaking to fit my needs, but it’s already a nice basis …
… maybe I will finally finish my quest for the perfect filter … stay tuned.
Edit: here’s a sample filter sweep (using the tanh clipper, you can hear the slight distortion on the low frequencies):
Edit2: When using a hard clipper [-1.0,1.0] instead of tanh and a simple resonance gain correction table this filter goes into self oscillation at an
r value of exactly
This resonance gain correction table can be done automatically. This is the 'hack* I use:
Calculate the maximum filter output peak for a given set of frequencies using only self-oscillation (and some really high
r, like 20). Then normalize this table (divide all values by maximum value) and do a 1/x on all values. After this you can simply lookup your scaling factor by using the cutoff frequency and e.g. Catmull-Rom spline interpolation. Now I also get constant resonance gain when using the hard clipper.
Another neet8 update … it’s evolving slowly^^
- Dialogs implemented (Yiiieeeha! … finally^^)
- Sample editor started
- Song config started
- Disk panel started
- Fixed some more concurrency issues
- Audio property file added
- Basic track editing working
- Link (follow) mode done
- Added peak output and CPU usage (audio thread) visualization. Peak output has ‘Clip’ indicators.
- Added a cool status message for pattern edit: You see the note number (hex), the sound and effect name for the current cursor position. (Very helpful)
- The pattern editor now remembers the last entered sound and uses this as a default for every new note (that was really neccessary)
- Added ringmod option to sample sounds (so you can ringmod sample voices)
- Added post-processing compressor (limiter) after song volume gets applied
- Neat IO helper functions
- Started neet8 standard sample library (some handy samples in n8wf format, like: GB noise (7&14bit), GB init wave, sine waves, …)
- Rewrote synth render methods -> more stable CPU usage and a slight increase in performance
- I will add a ‘Draw’ mode to the sample editor (like in FT2)
- The tracker GUI colors will be customizable (using ‘color.props’). I won’t add a WYSIWIG color editor (most of the GUI gets prerendered anyway)
- Some more keyboard shortcuts for the pattern editor (like Ctrl+Up/Down to exchange a line with the one above/below) (nice for correcting a ‘live recorded’ track)
- The drum (wave) table editor needs a refactoring (entering semi-tone offsets is a little buggy)
- MIDI input will also be implemented (no multi-track stuff)
I will hopefully finish most of the panels this (long) weekend (except the sample editor I think). Disk IO really needs to be done … and the audio config.
… and I am still listening to ‘ensnare - Stacked’ … what an awesome tune^^
Here’s a simple demo loop meant as a little teaser for neet8/trk.
… stay tuned …
Everything is developing slower than I want it to be … well, that’s the way it is^^
Nevertheless here’s a short summary of what changed during the last days:
- Finalized the sample playback
- Removed FIR from delay
- Song IO done
- WAV sample importer
- Drum sound playback
- Various player bugfixes
- Major changes on the audio API
- Pattern editor working (not fully done yet)
- Sound editor nearly finished (copy/paste/clear is missing)
- Started sample editor
- Fixed a concurrency issue on repeatable components
- … adding a post-processing low and high pass filter
- … adding ‘extended²’ effects ($EExx)
There’s still so much work to do … … and I really, really need to finish the disk panel … save and load would be damn great for testing^^
… stay tuned …