Variable knee Sigmoid function in Gen~/Codebox?
Hey peoples,
I've been digging around for some code to steal and experiment with but haven't found much that an audio engineer that only knows the basics of code and lacks plenty in the trigonometry department, can adapt to gen~...
So hoping that some guru will come down from the ether and bless me with a variable knee wave-shaping algorithm or explain to me some ways to code a sigmoid function that I can control/manage the shape of...
If I find anything myself I'll post it here :)
cheers
hey, i've been looking for this, too... found some used in visual and AI realms can be adapted to audio...
here's an example, try numbers higher than 1(like 5-15) for the curve:
that's from this post in reply to Tarik's topic here where there might be a couple more that might be useful, just have to adapt the gen code to your specific audio needs:
https://cycling74.com/forums/interpolation-methods-re-mapping-with-various-curves-exponential-logarithmic-sinusoidal-etc-add-your-tricks-here
You may also find the Ease package to be of some interest here.
Thanks peeps, thats helping me understand a little more but still not really getting anywhere...
I'm trying to make a transfer function thats linear until -/+ 0.5 and then ease into -/+1...
pretty much this except a harder knee near +/-1... https://www.desmos.com/calculator/ovynsbqgaj
Ive played around for hours trying to get it but im shooting in the dark really, this is what I have so far. Its close but i'm missing something I think...
I'm basically trying to make a clipper with a 6dB knee (so its linear until it reaches -6dB)
I could be doing this all wrong and there may be a transfer function closer to what I need that will require less messing about.
A bunch of equations here: https://en.wikipedia.org/wiki/Sigmoid_function
And more here: https://en.wikipedia.org/wiki/Activation_function
Most of these would be pretty easy to write in gen~.
The `1/1(pow(e,-x))` equation can be generalized for different sharpness by changing the value of `e`.
For bipolar output centered at 0,0: Param sharpness(2.718);
out1 = (2/(1+exp(-in1))) - 1;
A sharpness=9 is only slightly bulged between -0.5,0.5, and then tapers beyond that.
some examples can also be found here https://www.musicdsp.org/en/latest/Effects/index.html
wave shaping and clipping functions.
I've seen all of the waveshaping stuff at musicdsp.org and none of it is linear until a certain point or adjustable. Cheers though
Thanks Graham, thats much closer to what I need I think, however I'm having trouble getting it working in codebox. Console is telling me I have an undeclared identifier when I don't (at least I don't think so)...
^syntax looks off, this part in particular: "s(-in1)" looks like it treats 's' as a function rather than a variable referring to 'in2', maybe you meant to multiply like this:
"x=in1;
s=in2;
out1 = (2/(1+s*(-in1)))-1;"
Strange - so something like this won't work for you
https://www.musicdsp.org/en/latest/Effects/41-waveshaper.html
Param shape(1);
x = in1;
out1 = x*(abs(x) + shape)/(x*x + (shape-1)*abs(x) + 1);
I don't think a multiply is meant to be in there Raja... ? Graham was basically implementing this in codebox... https://en.wikipedia.org/wiki/Logistic_function
Any iteration I do of this returns zero or something weird so maybe I am missing something.
While definitely useful Testcase, its definitely not linear until a certain point. I basically want a straight line until the amplitude reaches a defined point (eg: 0.5 / -6dB) and then start rounding off to 1/-1. that way any input below 0.5/-0.5 or -6dB would have zero change in amplitude but anything above that would gradually be rounded off to 1/-1.
Graham was basically implementing this in codebox
oh i see what you're trying to do now(and i'm not sure how Graham meant to incorporate 'sharpness' into the specific codebox code he posted... but "exp(x)" = "pow(e,x)" so one could swap out 'e' for 'sharpness' to vary it in that second form of the equation)
you could codebox it like so:Param sharpness(2.718282);
out1 = (2/(1+pow(sharpness,-in1))) - 1;
or if you were trying to do that, but use 'in2' instead of the 'sharpness' param,
it'd be something closer to your code like this:x=in1;
s=in2;
out1 = (2/(1+pow(s,-x))) - 1;
ahh yep thats more like it. Thanks Raja :D
I was reading that equation wrong and I understand what the pow was doing now.
Although, the result is very much like the waveshaper that Testcase suggested and not really linear at any point...
my friend is an electronics engineer and has offered to help so I'll see what he comes up with.
Cheers peeps
no worries, happy to help.... one more suggestion, you could split off into the function for absolute-values above 0.5... when i try a value of '9' for 'in2' like Graham suggested above, this code basically gives me what you describe you need(though i'm not sure if it's as hard/soft in knee as you described in decibels):if (abs(in1) < 0.5)
{
out1 = in1;
}
else
{
out1 = 2/(1+pow(in2,-in1))-1;
}
EDIT:
it seems any other value besides '9' gives a break in the ramp, so i'd probably write that without 'in2' like this:
if(abs(in1)<0.5) { out1 = in1; }
else { out1 = 2 / (1 + pow(9, -in1)) - 1;
ahh nice! Yeah thats a bit closer to my earlier example (except much more manageable) and doing more of what I want. Definitely getting closer, here's an example...
sharpness should probably be 22, instead of 23 in that example...
Now if I can just find out the relation between sharpness and the knee & ceiling so that its adjusted automatically then its pretty much done!
I think this may be exactly what you are looking for: it is linear up to a certain value (parameter @knee) and then smoothly curves to horizontal at unity output.
It splits the input signal into two parts -- one part between -knee..knee which is passed through as-is (linear), the other part being below -knee or above +knee, which is then passed through the sigmoid. It is pre-scaled and post-scaled around the sigmoid to ensure it has the appropriate range and slope at the transition point, so there's no sudden change. Maybe there's a cleverer math way of doing this, but hey it works and there's only 2 divisions and one exp so it should be cheap. (Actually the math could be simplified a bit more but it would make the patcher harder to understand, and probably the compiler is smart enough to do that anyway).
It currently assumes the output range is limited to -1..1, but if you wanted to change that you can apply another pre/post scale factor around the whole thing. Maybe you parameterize that as @ceiling?
Note that there's no @sharpness factor, I just assume e^x here. Adding @sharpness would introduce a kink to the waveshaper. But, if you want that, you can add it! If so I suggest multiplying @sharpness by 'e' so that when @sharpness == 1 the shaper has no kink.
And if using tanh (another way to achieve the sigmoid) the patch is even simpler (and probably easier to understand):
Notice however there's aliasing being introduced -- not surprisingly, as the shaping is introducing a trail of harmonics, which is desirable -- but some of these harmonics are above Nyquist and fold back, which likely isn't desirable.
How to combat that? If the input is a sine then pre-filtering will just attenuate overall signal as a whole, and post-filtering will dull the sound but not get rid of the aliasing. There's the oversampling option but that only lifts the onset of aliasing by an octave for each 2x oversampling, which doesn't seem a great tradeoff.
Wondering if any distortion DSP experts here have ideas? Perhaps some kind of sinc interpolation between original and distorted?
no math guru here, but when making the function dynamic is too difficult, why not simply interpolate the result of 3-4 parallel fixed functions.
(@Graham, that tanh example is especially helpful to understand easily how to split off different levels of signal to different transfer/waveshaping functions... special thanks from me for that 🍻)
That's perfect Graham, very much appreciated! (Also, thx everyone else for chipping in)
I noticed that the sigmoid patch is actually more cpu efficient than the one using tanh (using CPU Usage to check) and the they show pretty much the exact same amount of aliasing (overlaying the scopes).
This is actually a good example of what I wanted this for. Clipping instruments to gain extra headroom on the Master when making heavy Electronic Music, is all the rage but more often than not you're just adding loads of aliasing. My plan was to band limit the signal going in, give some knee (3 to 6db) and upsample it at least 4 times to reduce the aliasing to manageable levels...
Another question... I've tried to find more information about the resampling filters used in poly~ when upsampling but there isn't any info anywhere. Perhaps you could tell me Graham, what filters are being used or if its something fancier... ?
PS I'm just a curious creative (Audio engineer, but not a real engineer haha) with very little programming experience (mostly just Max). I'm thinking about studying it properly though, I like geeking out on all this stuff :)
I dont know how to do sinc interpolation properly for something like this (my attempt to modify the example didnt really work lol) but using interp, history and spline6 interpolation the aliasing is only reduced by a hair and different sample rates dont do a whole lot to improve its efficiency. I'm about to try it in max for live with poly and upsampling 8 times to see what happens :P
Yeah spline6 interpolation only reduced aliasing by about 3dB but putting the clipper in a poly~ and upsampling 8x all the aliasing is below -96dB so completely usable and in line with (or exceeding) some commercial clippers I have.
Finished product peeps. Thanks for your help Graham... http://gum.co/gmaclip
4dB knee (sigmoid) with 8x Oversampling was the most ideal tradeoff, plus the pre-filtering, aliasing is pretty much a non-issue. In fact by using this in my mixes for weeks now, everything is clearer and there is no aliasing in sight.
btw I've realised by now that the filter used when upsampling is an interpolation filter which largely defined by how the signals are interpolated and is not a typical filter defined by coefficients. I've been using Bertoms EQ Curve Analyzer to inspect what things are doing and have been a bit more selective about when I'm oversampling... I love Max <3
nice! Congratulations! bet it sounds awesome, will definitely take a look soon, from the description sounds like a very detailed solution 👍