My asymmetrical waveshaper.

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

use taylor expansion for diode equation. wasted a lot of time on exponent and lambert, and worried about it being driven to hard. I bet much easier to find ADAA too.


I use boost math for lambert w as it seems pretty fast still trying to figure out simpler way with diode clipping equation but that explodes fairly quickly.


you can get some cool shapes and a bonus wavefolder but you have to add an extra saturation stage for the wavefolder.

Not sure If my non linear high pass implementation is the best or correct but it works.




https://downloads.bzarvin.com/aasymm.mp4


uses bias signal then highpass

Code: Select all

 template <typename FloatType>
       class ASym {
       public:
           ASym() {
               
           }
           void init(FloatType srIn){
               sr = srIn;
               isr = 1.0/sr;
               s = 0;
           }
           
           void setParams(FloatType cutoffIn, FloatType symIn) {
               cutoffIn=std::clamp(cutoffIn, static_cast<FloatType>(10.0), static_cast<FloatType>(22049.0));
               FloatType w = M_PI*cutoffIn*isr;
               FloatType co = sin(w*0.5);
               co = 1-2*(co*co);
               g = sin(w)/co;
               sym = 4*symIn-2; // -2 to 2 offset could add more maybe?
           }
           
           FloatType process(FloatType vin) {
               
               //Single serge wavefolding cell used as clipper
               //If you add vin-2*sign*nVt*boost::math::lambert_w0(R*Is/nVt*exp(arg));
               //you will get wavefolding, but you need an extra saturator for the output
               
               //https://www.mdpi.com/2076-3417/7/12/1328
               
               FloatType nVt = 25.864e-3 * 1.752;
               FloatType Is  = 2.52e-9;
               FloatType R   = 100.0e3;
               
               vin = vin+sym;
               
               FloatType Von = 0.1; //Voltage Knee at which diode starts;
               
               vin = std::clamp(vin,-1.0/nVt,1.0/nVt); //clamp or explode.
               
               FloatType sign = vin < 0 ? -1.0 : 1.0;

               FloatType arg = (sign*vin-Von)/nVt;
               
               vin = vin-sign*nVt*boost::math::lambert_w0(R*Is/nVt*exp(arg));
               
               
               //Art of VA filter design non linear highpass
               
               FloatType gk = 0;
               
               FloatType v1 = vin < 0 ? 0.5*vin : 2*vin;
               
               FloatType v2 = (s+g*vin)/(1+g);
               
               for(int i = 0; i < 50; i++) {
                   
                   FloatType x = v2;
                   FloatType fk = x < 0 ? 0.5*x : 2*x;
                   FloatType gk = fk < 0 ? 0.5 : 2.0;
                   FloatType gkeq = fk-gk*x;
                   
                   FloatType nx = (-g*gkeq+s+g*v1)/(1+g*gk);
                   
                   if(abs(nx-v2)<1e-6) {
                       break;
                   }
                   
                   v2 = nx;
                   
               }
               
               s = 2*v2-s;
               
               v2 = vin-v2;
               
               return v2;
               
           }
       private:
           FloatType sym;
           FloatType sr{44100.0};
           FloatType isr{1.0/44100.0};
           FloatType g{0.0};
           FloatType s =0;
       };
    

Another variation how Art of Va Filter Design actually describes.

uses nonlinear cutoff gain

Code: Select all

template <typename FloatType>
       class ASym2{
       public:
           ASym2() {
               
           }
           void init(FloatType srIn){
               sr = srIn;
               isr = 1.0/sr;
               s = 0;
           }
           
           void setParams(FloatType cutoffIn,FloatType ) {
               cutoffIn=std::clamp(cutoffIn, static_cast<FloatType>(10.0), static_cast<FloatType>(22049.0));
               FloatType w = M_PI*cutoffIn*isr;
               FloatType co = sin(w*0.5);
               co = 1-2*(co*co);
               g = sin(w)/co;
           }
           
           FloatType process(FloatType vin) {
               
               
               //Art of VA filter design non linear highpass
               
               FloatType gk = 0;
               
               FloatType v1 = vin < 0 ? 2.0*vin : 0.5*vin;
               
               FloatType v2 = (s+g*vin)/(1+g);
               
               for(int i = 0; i < 50; i++) {
                   
                   FloatType x = v2;
                   FloatType fk = x < 0 ? 2*x : 0.5*x;
                   FloatType gk = fk < 0 ? 2 : 0.5;
                   FloatType gkeq = fk-gk*x;
                   
                   FloatType nx = (-g*gkeq+s+g*v1)/(1+g*gk);
                   
                   if(abs(nx-v2)<1e-6) {
                       break;
                   }
                   
                   v2 = nx;
                   
               }
               
               s = 2*v2-s;
               
               vin = vin-v2;
               
               //Single serge wavefolding cell used as clipper
               //If you add vin-2*sign*nVt*boost::math::lambert_w0(R*Is/nVt*exp(arg));
               //you will get wavefolding, but you need an extra saturator for the output
               
               //https://www.mdpi.com/2076-3417/7/12/1328
               
               FloatType nVt = 25.864e-3 * 1.752;
               FloatType Is  = 2.52e-9;
               FloatType R   = 100.0e3;
               
               
               
               FloatType Von = 0.12; //Voltage Knee at which diode starts;
               
               vin = std::clamp(vin,-1.0/nVt,1.0/nVt); //clamp or explode.
               
               FloatType sign = vin < 0 ? -1.0 : 1.0;

               FloatType arg = (sign*vin-Von)/nVt;
               
               vin = vin-sign*nVt*boost::math::lambert_w0(R*Is/nVt*exp(arg));
               
               
               
               
               return vin;
               
           }
       private:
           FloatType sym;
           FloatType sr{44100.0};
           FloatType isr{1.0/44100.0};
           FloatType g{0.0};
           FloatType s =0;
       };
you can modify the bias here if you alter 2.0 and 0.5 with a parameter like symmetry.

Here sym is a variable 0.0 - 1.0;

it looks like full wave rectification at the extremes

Code: Select all


 FloatType v1 = vin < 0 ? (2.0-1.5*sym)*vin : (0.5+1.5*sym)*vin;
               
               FloatType v2 = (s+g*vin)/(1+g);
               
               for(int i = 0; i < 50; i++) {
                   
                   FloatType x = v2;
                   FloatType fk = x < 0 ? (2.0-1.5*sym)*x : (0.5+1.5*sym)*x;
                   FloatType gk = fk < 0 ? (2.0-1.5*sym) : (0.5+1.5*sym);
                   FloatType gkeq = fk-gk*x;

Last edited by Marvinh on Thu May 21, 2026 12:41 am, edited 2 times in total.

Post

nice. Lambert W on diode clipping is the right move when you need exact, but it does explode near hard clip if input drives past the convergence radius. one thing to watch with bias: tanh-style asymmetric shapers create a DC offset proportional to bias even at silence. if you're not subtracting tanh(bias) at the output (or running a high-pass after), you'll hear thumps at parameter changes.

the other thing i learned the hard way: any modulated parameter (drive, bias, mix) needs sample-accurate smoothing if you're using ADAA. block-rate updates leave a discontinuity in the antiderivative which clicks at every block boundary. drive smoothing alone isn't enough. bias smoothing matters just as much.

Lambert W from boost is fine cpu-wise. if you ever drop boost, a 4-iteration newton on `w*exp(w) = x` with seed `log(1+x)` converges in float in about 12 ns/sample on apple silicon.

Post

Nice thank you for the info I recently started clamping exponential newton raphson

I even clamp lambert at -1/vt 1/vt

Vt here is thermal voltage what I divide by the unknown or x

exp(x/vt)



I had that issue with ADAA I never resolved so I stopped trying

my favorite is the second one here when you get to higher octaves the distortion kind of turns off

Post

If you are interested in parameter smoothing, feel free to take a look at this Blogpost that I wrote some time ago.
https://darkpalace.studio/2025/04/18/dsp-smoothing.html

Post

Thanks I definitely smooth parameters after defining controls but never while developing why I think that caught me

Sample accurate smoothing vs regular smoothing was not something I ever paid much attention to.

Post

Using this equation as the seed for a diode clipper i have not been able to make it break.

its faster than previous value seed as well i think while not blowing up.

When I say blowing up i mean driving the input by a 1000 times.

Code: Select all


 double sign = in < 0.0 ? -1.0 : 1.0;
//double vt1 = (25.864e-3 * 1.752);
//double R = 1.0/(100e3);
//double is1 = 2.52e-9;
//The seed
if(abs(in)>1.0){
v = sign*log((abs(in))*R/is1)*vt1;
}else{
v = previous_output
}
                

Clipping code from cytomic i adapted to try this seed.

https://cytomic.com/files/dsp/adc-2020- ... slides.pdf
Last edited by Marvinh on Mon May 18, 2026 1:23 pm, edited 1 time in total.

Post

if abs(in)>1.0
then in cannot be 0.0
We are the KVR collective. Resistance is futile. You will be assimilated. Image
My MusicCalc is served over https!!

Post

BertKoor wrote: Mon May 18, 2026 1:18 pm if abs(in)>1.0
then in cannot be 0.0
Yes wrote that but after double checking cpu usage when under 1 -1

You can ignore the check

Post Reply

Return to “DSP and Plugin Development”