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