Guest User

MoogLadders (WIP).js

a guest
Jan 14th, 2025
117
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //Transpiled to JavaScript from ddiakopoulos/MoogLadders on GitHub…
  2. //== Filters.h =======================================================================================================================================================================================================
  3. class BiQuadBase{
  4.     constructor(){
  5.         this.bCoef=[0,0,0]; //b0,b1,b2
  6.         this.aCoef=[0,0]; //a1,a2
  7.         this.w=[0,0]; //delays
  8.     }
  9.     processArray(samples){
  10.         let out=0;
  11.         for(let s=0;s<samples.length;s++){
  12.             out=this.bCoef[0]*samples[s]+this.w[0];
  13.             this.w[0]=this.bCoef[1]*samples[s]-this.aCoef[0]*out+this.w[1];
  14.             this.w[1]=this.bCoef[2]*samples[s]-this.aCoef[1]*out;
  15.             samples[s]=out;
  16.         }
  17.     }
  18.     process(s){ //Process for one sample/tick.
  19.         let out=this.bCoef[0]*s+this.w[0];
  20.         this.w[0]=this.bCoef[1]*s-this.aCoef[0]*out+this.w[1];
  21.         this.w[1]=this.bCoef[2]*s-this.aCoef[1]*out;
  22.         return out;
  23.     }
  24.     setBiquadCoefs(b,a){
  25.         this.bCoef=b;
  26.         this.aCoef=a;
  27.     }
  28. }
  29. const FilterType={
  30.     LOWPASS:0,
  31.     HIGHPASS:1,
  32.     BANDPASS:2,
  33.     ALLPASS:3,
  34.     NOTCH:4,
  35.     PEAK:5,
  36.     LOW_SHELF:6,
  37.     HIGH_SHELF:7
  38. };
  39. class RBJFilter extends BiQuadBase{
  40.     constructor(type=RBJFilter.FilterType.LOWPASS,cutoff=1,sampleRate=44100){
  41.         super();
  42.         this.sampleRate=sampleRate;
  43.         this.t=type;
  44.         this.Q=1;
  45.         this.A=1;
  46.         this.a=[0,0,0];
  47.         this.b=[0,0,0];
  48.         this.setCutoff(cutoff);
  49.     }
  50.     /*static FilterType={
  51.         LOWPASS:0,
  52.         HIGHPASS:1,
  53.         BANDPASS:2,
  54.         ALLPASS:3,
  55.         NOTCH:4,
  56.         PEAK:5,
  57.         LOW_SHELF:6,
  58.         HIGH_SHELF:7
  59.     };*/
  60.     updateCoefficients(){
  61.         const cosOmega=Math.cos(this.omega),
  62.               sinOmega=Math.sin(this.omega);
  63.         let alpha;
  64.         switch(this.t){
  65.             case FilterType.LOWPASS:
  66.                 alpha=sinOmega/(2*this.Q);
  67.                 this.b[0]=(1-cosOmega)/2;
  68.                 this.b[1]=1-cosOmega;
  69.                 this.b[2]=this.b[0];
  70.                 this.a[0]=1+alpha;
  71.                 this.a[1]=-2*cosOmega;
  72.                 this.a[2]=1-alpha;
  73.                 break;
  74.             case FilterType.HIGHPASS:
  75.                 alpha=sinOmega/(2*this.Q);
  76.                 this.b[0]=(1+cosOmega)/2;
  77.                 this.b[1]=-(1+cosOmega);
  78.                 this.b[2]=this.b[0];
  79.                 this.a[0]=1+alpha;
  80.                 this.a[1]=-2*cosOmega;
  81.                 this.a[2]=1-alpha;
  82.                 break;
  83.             case FilterType.BANDPASS:
  84.                 alpha=sinOmega*Math.sinh(Math.log(2)/2*this.Q*this.omega/sinOmega);
  85.                 this.b[0]=sinOmega/2;
  86.                 this.b[1]=0;
  87.                 this.b[2]=-this.b[0];
  88.                 this.a[0]=1+alpha;
  89.                 this.a[1]=-2*cosOmega;
  90.                 this.a[2]=1-alpha;
  91.                 break;
  92.             case FilterType.ALLPASS:
  93.                 alpha=sinOmega/(2*this.Q);
  94.                 this.b[0]=1-alpha;
  95.                 this.b[1]=-2*cosOmega;
  96.                 this.b[2]=1+alpha;
  97.                 this.a[0]=this.b[2];
  98.                 this.a[1]=this.b[1];
  99.                 this.a[2]=this.b[0];
  100.                 break;
  101.             case FilterType.NOTCH:
  102.                 alpha=sinOmega*Math.sinh(Math.log(2)/2*this.Q*this.omega/sinOmega);
  103.                 this.b[0]=1;
  104.                 this.b[1]=-2*cosOmega;
  105.                 this.b[2]=1;
  106.                 this.a[0]=1+alpha;
  107.                 this.a[1]=this.b[1];
  108.                 this.a[2]=1-alpha;
  109.                 break;
  110.             case FilterType.PEAK:
  111.                 alpha=sinOmega*Math.sinh(Math.log(2)/2*this.Q*this.omega/sinOmega);
  112.                 this.b[0]=1+(alpha*this.A);
  113.                 this.b[1]=-2*cosOmega;
  114.                 this.b[2]=1-(alpha*this.A);
  115.                 this.a[0]=1+(alpha/this.A);
  116.                 this.a[1]=this.b[1];
  117.                 this.a[2]=1-(alpha/this.A);
  118.                 break;
  119.             case FilterType.LOW_SHELF:
  120.                 alpha=sinOmega/2*Math.sqrt((this.A+1/this.A)*(1/this.Q-1)+2);
  121.                 this.b[0]=this.A*((this.A+1)-((this.A-1)*cosOmega)+(2*Math.sqrt(this.A)*alpha));
  122.                 this.b[1]=2*this.A*((this.A-1)-((this.A+1)*cosOmega));
  123.                 this.b[2]=this.A*((this.A+1)-((this.A-1)*cosOmega)-(2*Math.sqrt(this.A)*alpha));
  124.                 this.a[0]=((this.A+1)+((this.A-1)*cosOmega)+(2*Math.sqrt(this.A)*alpha));
  125.                 this.a[1]=-2*((this.A-1)+((this.A+1)*cosOmega));
  126.                 this.a[2]=((this.A+1)+((this.A-1)*cosOmega)-(2*Math.sqrt(this.A)*alpha));
  127.                 break;
  128.             case FilterType.HIGH_SHELF:
  129.                 alpha=sinOmega/2*Math.sqrt((this.A+1/this.A)*(1/this.Q-1)+2);
  130.                 this.b[0]=this.A*((this.A+1)+((this.A-1)*cosOmega)+(2*Math.sqrt(this.A)*alpha));
  131.                 this.b[1]=-2*this.A*((this.A-1)+((this.A+1)*cosOmega));
  132.                 this.b[2]=this.A*((this.A+1)+((this.A-1)*cosOmega)-(2*Math.sqrt(this.A)*alpha));
  133.                 this.a[0]=((this.A+1)-((this.A-1)*cosOmega)+(2*Math.sqrt(this.A)*alpha));
  134.                 this.a[1]=2*((this.A-1)-((this.A+1)*cosOmega));
  135.                 this.a[2]=((this.A+1)-((this.A-1)*cosOmega)-(2*Math.sqrt(this.A)*alpha));
  136.                 break;
  137.         }
  138.         //Normalize filter coefficients
  139.         const factor=1/this.a[0],
  140.               aNorm=[this.a[1]*factor,this.a[2]*factor],
  141.               bNorm=[this.b[0]*factor,this.b[1]*factor,this.b[2]*factor];
  142.         this.setBiquadCoefs(bNorm,aNorm);
  143.     }
  144.     //In Hertz,0 to Nyquist
  145.     setCutoff(c){
  146.         this.omega=this.hzToRad(c)/this.sampleRate;
  147.         this.updateCoefficients();
  148.     }
  149.     getCutoff(){
  150.         return this.omega;
  151.     }
  152.     //Arbitrary,from 0.01 to~20
  153.     setQValue(q){
  154.         this.Q=q;
  155.         this.updateCoefficients();
  156.     }
  157.     getQValue(){
  158.         return this.Q;
  159.     }
  160.     setType(newType){
  161.         this.t=newType;
  162.         this.updateCoefficients();
  163.     }
  164.     getType(){
  165.         return this.t;
  166.     }
  167.     hzToRad(hz){
  168.         return 2*Math.PI*hz;
  169.     }
  170. }
  171. class PinkingFilter{
  172.     constructor(){
  173.         this.b0=0;this.b1=0;this.b2=0;this.b3=0;this.b4=0;this.b5=0;this.b6=0;
  174.     }
  175.     process(s){
  176.         this.b0=0.99886*this.b0+s*0.0555179;
  177.         this.b1=0.99332*this.b1+s*0.0750759;
  178.         this.b2=0.96900*this.b2+s*0.1538520;
  179.         this.b3=0.86650*this.b3+s*0.3104856;
  180.         this.b4=0.55000*this.b4+s*0.5329522;
  181.         this.b5=-0.7616*this.b5-s*0.0168980;
  182.         const pink=(this.b0+this.b1+this.b2+this.b3+this.b4+this.b5+this.b6+(s*0.5362))*0.11;
  183.         this.b6=s*0.115926;
  184.         return pink;
  185.     }
  186. }
  187. class BrowningFilter{
  188.     constructor(){
  189.         this.l=0;
  190.     }
  191.     process(s){
  192.         const brown=(this.l+(0.02*s))/1.02;
  193.         this.l=brown;
  194.         return brown*3.5; //compensate for gain
  195.     }
  196. }
  197. //== util.h ==========================================================================================================================================================================================================
  198. const MOOG_E=2.71828182845904523536028747135266250,
  199.       MOOG_LOG2E=1.44269504088896340735992468100189214,
  200.       MOOG_LOG10E=0.434294481903251827651128918916605082,
  201.       MOOG_LN2=0.693147180559945309417232121458176568,
  202.       MOOG_LN10=2.30258509299404568401799145468436421,
  203.       MOOG_PI=3.14159265358979323846264338327950288,
  204.       MOOG_PI_2=1.57079632679489661923132169163975144,
  205.       MOOG_PI_4=0.785398163397448309615660845819875721,
  206.       MOOG_1_PI=0.318309886183790671537767526745028724,
  207.       MOOG_2_PI=0.636619772367581343075535053490057448,
  208.       MOOG_2_SQRTPI=1.12837916709551257389615890312154517,
  209.       MOOG_SQRT2=1.41421356237309504880168872420969808,
  210.       MOOG_SQRT1_2=0.707106781186547524400844362104849039,
  211.       MOOG_INV_PI_2=0.159154943091895;
  212. function moogLerp(amount,a,b){return(1-amount)*a+amount*b;} //Linear interpolation
  213. function moogMin(a,b){a=b-a;a+=Math.abs(a);a*=.5;a=b-a;return a;}
  214. function moogSaturate(input){ //Clamp without branching
  215.     const x1=Math.abs(input+.95);
  216.     const x2=Math.abs(input-.95);
  217.     return .5*(x1-x2);
  218. }
  219. function clip(value,saturation,saturationInverse){ //Clipping function
  220.     let v2=(value*saturationInverse>1)?1:
  221.            (value*saturationInverse<-1)?-1:
  222.             value*saturationInverse;
  223.     return saturation*(v2-(1/3)*v2*v2*v2);
  224. }
  225. function hzToRad(f){return MOOG_PI_2*f;}
  226. function radToHz(omega){return MOOG_INV_PI_2*omega;}
  227. function fastTanh(x){
  228.     const x2=x*x;
  229.     return x*(27+x2)/(27+9*x2);
  230. }
  231. //== HuovilainenModel.h ==============================================================================================================================================================================================
  232. class LadderFilterBase{
  233.     constructor(sampleRate){this.sampleRate=sampleRate;this.cutoff=0;this.resonance=0;}
  234.     process(samples){throw new Error("Process method not implemented");}
  235.     setResonance(r){this.resonance=r;}
  236.     setCutoff(c){this.cutoff=c;}
  237.     getResonance(){return this.resonance;}
  238.     getCutoff(){return this.cutoff;}
  239. }
  240. class HuovilainenMoog extends LadderFilterBase{
  241.     constructor(sampleRate){
  242.         super(sampleRate);
  243.         this.thermal=.000025;
  244.         this.stage=new Array(4).fill(0);
  245.         this.delay=new Array(6).fill(0);
  246.         this.stageTanh=new Array(3).fill(0);
  247.         this.setCutoff(1000);
  248.         this.setResonance(.10);
  249.     }
  250.     process(samples){
  251.         for(let s=0;s<samples.length;s++){
  252.             //Oversample
  253.             for(let j=0;j<2;j++){
  254.                 let input=samples[s]-this.resQuad*this.delay[5];
  255.                 this.delay[0]=this.stage[0]=this.delay[0]+this.tune*(this.tanh(input*this.thermal)-this.stageTanh[0]);
  256.                 for(let k=1;k<4;k++){
  257.                     input=this.stage[k-1];
  258.                     this.stage[k]=this.delay[k]+this.tune*((this.stageTanh[k-1]=this.tanh(input*this.thermal))-(k!==3?this.stageTanh[k]:this.tanh(this.delay[k]*this.thermal)));
  259.                     this.delay[k]=this.stage[k];
  260.                 }
  261.                 //0.5 sample delay for phase compensation
  262.                 this.delay[5]=(this.stage[3]+this.delay[4])*.5;
  263.                 this.delay[4]=this.stage[3];
  264.             }
  265.             samples[s]=this.delay[5];
  266.         }
  267.     }
  268.     tick(x){
  269.         for(let j=0;j<2;j++){
  270.             let input=x-this.resQuad*this.delay[5];
  271.             this.delay[0]=this.stage[0]=this.delay[0]+this.tune*(this.tanh(input*this.thermal)-this.stageTanh[0]);
  272.             for(let k=1;k<4;k++){
  273.                 input=this.stage[k-1];
  274.                 this.stage[k]=this.delay[k]+this.tune*((this.stageTanh[k-1]=this.tanh(input*this.thermal))-(k!==3?this.stageTanh[k]:this.tanh(this.delay[k]*this.thermal)));
  275.                 this.delay[k]=this.stage[k];
  276.             }
  277.             this.delay[5]=(this.stage[3]+this.delay[4])*.5;
  278.             this.delay[4]=this.stage[3];
  279.         }
  280.         return this.delay[5];
  281.     }
  282.     setResonance(r){
  283.         this.resonance=r;
  284.         this.resQuad=4*this.resonance*this.acr;
  285.     }
  286.     setCutoff(c){
  287.         this.cutoff=c;
  288.         const fc=this.cutoff/this.sampleRate,
  289.               f=fc*.5, //oversampled
  290.               fc2=fc*fc,
  291.               fc3=fc*fc*fc,
  292.               fcr=1.8730*fc3+0.4955*fc2-0.6490*fc+0.9988;
  293.         this.acr=-3.9364*fc2+1.8409*fc+0.9968;
  294.         this.tune=(1.0-Math.exp(-((2*Math.PI)*f*fcr)))/this.thermal;
  295.         this.setResonance(this.resonance);
  296.     }
  297.     tanh(x){return Math.tanh(x);}
  298. }
  299. //== ImprovedModel.h =================================================================================================================================================================================================
  300. const VT=0.312; //Thermal voltage (26 milliwatts at room temperature)
  301. class ImprovedMoog extends LadderFilterBase{
  302.     constructor(sampleRate){
  303.         super(sampleRate);
  304.         this.V=new Array(4).fill(0);
  305.         this.dV=new Array(4).fill(0);
  306.         this.tV=new Array(4).fill(0);
  307.         this.drive=1;
  308.         this.setCutoff(1000); //normalized cutoff frequency
  309.         this.setResonance(.1); //[0,4]
  310.     }
  311.     process(samples){
  312.         let dV0,dV1,dV2,dV3;
  313.         for(let i=0;i<samples.length;i++){
  314.             dV0=-this.g*(this.tanh((this.drive*samples[i]+this.resonance*this.V[3])/(2*VT))+this.tV[0]);
  315.             this.V[0]+=(dV0+this.dV[0])/(2*this.sampleRate);
  316.             this.dV[0]=dV0;
  317.             this.tV[0]=this.tanh(this.V[0]/(2*VT));
  318.             dV1=this.g*(this.tV[0]-this.tV[1]);
  319.             this.V[1]+=(dV1+this.dV[1])/(2*this.sampleRate);
  320.             this.dV[1]=dV1;
  321.             this.tV[1]=this.tanh(this.V[1]/(2*VT));
  322.             dV2=this.g*(this.tV[1]-this.tV[2]);
  323.             this.V[2]+=(dV2+this.dV[2])/(2*this.sampleRate);
  324.             this.dV[2]=dV2;
  325.             this.tV[2]=this.tanh(this.V[2]/(2*VT));
  326.             dV3=this.g*(this.tV[2]-this.tV[3]);
  327.             this.V[3]+=(dV3+this.dV[3])/(2*this.sampleRate);
  328.             this.dV[3]=dV3;
  329.             this.tV[3]=this.tanh(this.V[3]/(2*VT));
  330.             samples[i]=this.V[3];
  331.         }
  332.     }
  333.     tick(x){
  334.         let dV0,dV1,dV2,dV3;
  335.         dV0=-this.g*(this.tanh((this.drive*x+this.resonance*this.V[3])/(2*VT))+this.tV[0]);
  336.         this.V[0]+=(dV0+this.dV[0])/(2*this.sampleRate);
  337.         this.dV[0]=dV0;
  338.         this.tV[0]=this.tanh(this.V[0]/(2*VT));
  339.         dV1=this.g*(this.tV[0]-this.tV[1]);
  340.         this.V[1]+=(dV1+this.dV[1])/(2*this.sampleRate);
  341.         this.dV[1]=dV1;
  342.         this.tV[1]=this.tanh(this.V[1]/(2*VT));
  343.         dV2=this.g*(this.tV[1]-this.tV[2]);
  344.         this.V[2]+=(dV2+this.dV[2])/(2*this.sampleRate);
  345.         this.dV[2]=dV2;
  346.         this.tV[2]=this.tanh(this.V[2]/(2*VT));
  347.         dV3=this.g*(this.tV[2]-this.tV[3]);
  348.         this.V[3]+=(dV3+this.dV[3])/(2*this.sampleRate);
  349.         this.dV[3]=dV3;
  350.         this.tV[3]=this.tanh(this.V[3]/(2*VT));
  351.         return this.V[3];
  352.     }
  353.     setResonance(r){
  354.         this.resonance=r;
  355.     }
  356.     setCutoff(c){
  357.         this.cutoff=c;
  358.         const x=(Math.PI*this.cutoff)/this.sampleRate;
  359.         this.g=4*Math.PI*VT*this.cutoff*(1-x)/(1+x);
  360.     }
  361.     tanh(x){
  362.         return Math.tanh(x);
  363.     }
  364. }
Tags: DSP audio Filter
Advertisement
Add Comment
Please, Sign In to add comment