Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //Transpiled to JavaScript from ddiakopoulos/MoogLadders on GitHub…
- //== Filters.h =======================================================================================================================================================================================================
- class BiQuadBase{
- constructor(){
- this.bCoef=[0,0,0]; //b0,b1,b2
- this.aCoef=[0,0]; //a1,a2
- this.w=[0,0]; //delays
- }
- processArray(samples){
- let out=0;
- for(let s=0;s<samples.length;s++){
- out=this.bCoef[0]*samples[s]+this.w[0];
- this.w[0]=this.bCoef[1]*samples[s]-this.aCoef[0]*out+this.w[1];
- this.w[1]=this.bCoef[2]*samples[s]-this.aCoef[1]*out;
- samples[s]=out;
- }
- }
- process(s){ //Process for one sample/tick.
- let out=this.bCoef[0]*s+this.w[0];
- this.w[0]=this.bCoef[1]*s-this.aCoef[0]*out+this.w[1];
- this.w[1]=this.bCoef[2]*s-this.aCoef[1]*out;
- return out;
- }
- setBiquadCoefs(b,a){
- this.bCoef=b;
- this.aCoef=a;
- }
- }
- const FilterType={
- LOWPASS:0,
- HIGHPASS:1,
- BANDPASS:2,
- ALLPASS:3,
- NOTCH:4,
- PEAK:5,
- LOW_SHELF:6,
- HIGH_SHELF:7
- };
- class RBJFilter extends BiQuadBase{
- constructor(type=RBJFilter.FilterType.LOWPASS,cutoff=1,sampleRate=44100){
- super();
- this.sampleRate=sampleRate;
- this.t=type;
- this.Q=1;
- this.A=1;
- this.a=[0,0,0];
- this.b=[0,0,0];
- this.setCutoff(cutoff);
- }
- /*static FilterType={
- LOWPASS:0,
- HIGHPASS:1,
- BANDPASS:2,
- ALLPASS:3,
- NOTCH:4,
- PEAK:5,
- LOW_SHELF:6,
- HIGH_SHELF:7
- };*/
- updateCoefficients(){
- const cosOmega=Math.cos(this.omega),
- sinOmega=Math.sin(this.omega);
- let alpha;
- switch(this.t){
- case FilterType.LOWPASS:
- alpha=sinOmega/(2*this.Q);
- this.b[0]=(1-cosOmega)/2;
- this.b[1]=1-cosOmega;
- this.b[2]=this.b[0];
- this.a[0]=1+alpha;
- this.a[1]=-2*cosOmega;
- this.a[2]=1-alpha;
- break;
- case FilterType.HIGHPASS:
- alpha=sinOmega/(2*this.Q);
- this.b[0]=(1+cosOmega)/2;
- this.b[1]=-(1+cosOmega);
- this.b[2]=this.b[0];
- this.a[0]=1+alpha;
- this.a[1]=-2*cosOmega;
- this.a[2]=1-alpha;
- break;
- case FilterType.BANDPASS:
- alpha=sinOmega*Math.sinh(Math.log(2)/2*this.Q*this.omega/sinOmega);
- this.b[0]=sinOmega/2;
- this.b[1]=0;
- this.b[2]=-this.b[0];
- this.a[0]=1+alpha;
- this.a[1]=-2*cosOmega;
- this.a[2]=1-alpha;
- break;
- case FilterType.ALLPASS:
- alpha=sinOmega/(2*this.Q);
- this.b[0]=1-alpha;
- this.b[1]=-2*cosOmega;
- this.b[2]=1+alpha;
- this.a[0]=this.b[2];
- this.a[1]=this.b[1];
- this.a[2]=this.b[0];
- break;
- case FilterType.NOTCH:
- alpha=sinOmega*Math.sinh(Math.log(2)/2*this.Q*this.omega/sinOmega);
- this.b[0]=1;
- this.b[1]=-2*cosOmega;
- this.b[2]=1;
- this.a[0]=1+alpha;
- this.a[1]=this.b[1];
- this.a[2]=1-alpha;
- break;
- case FilterType.PEAK:
- alpha=sinOmega*Math.sinh(Math.log(2)/2*this.Q*this.omega/sinOmega);
- this.b[0]=1+(alpha*this.A);
- this.b[1]=-2*cosOmega;
- this.b[2]=1-(alpha*this.A);
- this.a[0]=1+(alpha/this.A);
- this.a[1]=this.b[1];
- this.a[2]=1-(alpha/this.A);
- break;
- case FilterType.LOW_SHELF:
- alpha=sinOmega/2*Math.sqrt((this.A+1/this.A)*(1/this.Q-1)+2);
- this.b[0]=this.A*((this.A+1)-((this.A-1)*cosOmega)+(2*Math.sqrt(this.A)*alpha));
- this.b[1]=2*this.A*((this.A-1)-((this.A+1)*cosOmega));
- this.b[2]=this.A*((this.A+1)-((this.A-1)*cosOmega)-(2*Math.sqrt(this.A)*alpha));
- this.a[0]=((this.A+1)+((this.A-1)*cosOmega)+(2*Math.sqrt(this.A)*alpha));
- this.a[1]=-2*((this.A-1)+((this.A+1)*cosOmega));
- this.a[2]=((this.A+1)+((this.A-1)*cosOmega)-(2*Math.sqrt(this.A)*alpha));
- break;
- case FilterType.HIGH_SHELF:
- alpha=sinOmega/2*Math.sqrt((this.A+1/this.A)*(1/this.Q-1)+2);
- this.b[0]=this.A*((this.A+1)+((this.A-1)*cosOmega)+(2*Math.sqrt(this.A)*alpha));
- this.b[1]=-2*this.A*((this.A-1)+((this.A+1)*cosOmega));
- this.b[2]=this.A*((this.A+1)+((this.A-1)*cosOmega)-(2*Math.sqrt(this.A)*alpha));
- this.a[0]=((this.A+1)-((this.A-1)*cosOmega)+(2*Math.sqrt(this.A)*alpha));
- this.a[1]=2*((this.A-1)-((this.A+1)*cosOmega));
- this.a[2]=((this.A+1)-((this.A-1)*cosOmega)-(2*Math.sqrt(this.A)*alpha));
- break;
- }
- //Normalize filter coefficients
- const factor=1/this.a[0],
- aNorm=[this.a[1]*factor,this.a[2]*factor],
- bNorm=[this.b[0]*factor,this.b[1]*factor,this.b[2]*factor];
- this.setBiquadCoefs(bNorm,aNorm);
- }
- //In Hertz,0 to Nyquist
- setCutoff(c){
- this.omega=this.hzToRad(c)/this.sampleRate;
- this.updateCoefficients();
- }
- getCutoff(){
- return this.omega;
- }
- //Arbitrary,from 0.01 to~20
- setQValue(q){
- this.Q=q;
- this.updateCoefficients();
- }
- getQValue(){
- return this.Q;
- }
- setType(newType){
- this.t=newType;
- this.updateCoefficients();
- }
- getType(){
- return this.t;
- }
- hzToRad(hz){
- return 2*Math.PI*hz;
- }
- }
- class PinkingFilter{
- constructor(){
- this.b0=0;this.b1=0;this.b2=0;this.b3=0;this.b4=0;this.b5=0;this.b6=0;
- }
- process(s){
- this.b0=0.99886*this.b0+s*0.0555179;
- this.b1=0.99332*this.b1+s*0.0750759;
- this.b2=0.96900*this.b2+s*0.1538520;
- this.b3=0.86650*this.b3+s*0.3104856;
- this.b4=0.55000*this.b4+s*0.5329522;
- this.b5=-0.7616*this.b5-s*0.0168980;
- const pink=(this.b0+this.b1+this.b2+this.b3+this.b4+this.b5+this.b6+(s*0.5362))*0.11;
- this.b6=s*0.115926;
- return pink;
- }
- }
- class BrowningFilter{
- constructor(){
- this.l=0;
- }
- process(s){
- const brown=(this.l+(0.02*s))/1.02;
- this.l=brown;
- return brown*3.5; //compensate for gain
- }
- }
- //== util.h ==========================================================================================================================================================================================================
- const MOOG_E=2.71828182845904523536028747135266250,
- MOOG_LOG2E=1.44269504088896340735992468100189214,
- MOOG_LOG10E=0.434294481903251827651128918916605082,
- MOOG_LN2=0.693147180559945309417232121458176568,
- MOOG_LN10=2.30258509299404568401799145468436421,
- MOOG_PI=3.14159265358979323846264338327950288,
- MOOG_PI_2=1.57079632679489661923132169163975144,
- MOOG_PI_4=0.785398163397448309615660845819875721,
- MOOG_1_PI=0.318309886183790671537767526745028724,
- MOOG_2_PI=0.636619772367581343075535053490057448,
- MOOG_2_SQRTPI=1.12837916709551257389615890312154517,
- MOOG_SQRT2=1.41421356237309504880168872420969808,
- MOOG_SQRT1_2=0.707106781186547524400844362104849039,
- MOOG_INV_PI_2=0.159154943091895;
- function moogLerp(amount,a,b){return(1-amount)*a+amount*b;} //Linear interpolation
- function moogMin(a,b){a=b-a;a+=Math.abs(a);a*=.5;a=b-a;return a;}
- function moogSaturate(input){ //Clamp without branching
- const x1=Math.abs(input+.95);
- const x2=Math.abs(input-.95);
- return .5*(x1-x2);
- }
- function clip(value,saturation,saturationInverse){ //Clipping function
- let v2=(value*saturationInverse>1)?1:
- (value*saturationInverse<-1)?-1:
- value*saturationInverse;
- return saturation*(v2-(1/3)*v2*v2*v2);
- }
- function hzToRad(f){return MOOG_PI_2*f;}
- function radToHz(omega){return MOOG_INV_PI_2*omega;}
- function fastTanh(x){
- const x2=x*x;
- return x*(27+x2)/(27+9*x2);
- }
- //== HuovilainenModel.h ==============================================================================================================================================================================================
- class LadderFilterBase{
- constructor(sampleRate){this.sampleRate=sampleRate;this.cutoff=0;this.resonance=0;}
- process(samples){throw new Error("Process method not implemented");}
- setResonance(r){this.resonance=r;}
- setCutoff(c){this.cutoff=c;}
- getResonance(){return this.resonance;}
- getCutoff(){return this.cutoff;}
- }
- class HuovilainenMoog extends LadderFilterBase{
- constructor(sampleRate){
- super(sampleRate);
- this.thermal=.000025;
- this.stage=new Array(4).fill(0);
- this.delay=new Array(6).fill(0);
- this.stageTanh=new Array(3).fill(0);
- this.setCutoff(1000);
- this.setResonance(.10);
- }
- process(samples){
- for(let s=0;s<samples.length;s++){
- //Oversample
- for(let j=0;j<2;j++){
- let input=samples[s]-this.resQuad*this.delay[5];
- this.delay[0]=this.stage[0]=this.delay[0]+this.tune*(this.tanh(input*this.thermal)-this.stageTanh[0]);
- for(let k=1;k<4;k++){
- input=this.stage[k-1];
- 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)));
- this.delay[k]=this.stage[k];
- }
- //0.5 sample delay for phase compensation
- this.delay[5]=(this.stage[3]+this.delay[4])*.5;
- this.delay[4]=this.stage[3];
- }
- samples[s]=this.delay[5];
- }
- }
- tick(x){
- for(let j=0;j<2;j++){
- let input=x-this.resQuad*this.delay[5];
- this.delay[0]=this.stage[0]=this.delay[0]+this.tune*(this.tanh(input*this.thermal)-this.stageTanh[0]);
- for(let k=1;k<4;k++){
- input=this.stage[k-1];
- 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)));
- this.delay[k]=this.stage[k];
- }
- this.delay[5]=(this.stage[3]+this.delay[4])*.5;
- this.delay[4]=this.stage[3];
- }
- return this.delay[5];
- }
- setResonance(r){
- this.resonance=r;
- this.resQuad=4*this.resonance*this.acr;
- }
- setCutoff(c){
- this.cutoff=c;
- const fc=this.cutoff/this.sampleRate,
- f=fc*.5, //oversampled
- fc2=fc*fc,
- fc3=fc*fc*fc,
- fcr=1.8730*fc3+0.4955*fc2-0.6490*fc+0.9988;
- this.acr=-3.9364*fc2+1.8409*fc+0.9968;
- this.tune=(1.0-Math.exp(-((2*Math.PI)*f*fcr)))/this.thermal;
- this.setResonance(this.resonance);
- }
- tanh(x){return Math.tanh(x);}
- }
- //== ImprovedModel.h =================================================================================================================================================================================================
- const VT=0.312; //Thermal voltage (26 milliwatts at room temperature)
- class ImprovedMoog extends LadderFilterBase{
- constructor(sampleRate){
- super(sampleRate);
- this.V=new Array(4).fill(0);
- this.dV=new Array(4).fill(0);
- this.tV=new Array(4).fill(0);
- this.drive=1;
- this.setCutoff(1000); //normalized cutoff frequency
- this.setResonance(.1); //[0,4]
- }
- process(samples){
- let dV0,dV1,dV2,dV3;
- for(let i=0;i<samples.length;i++){
- dV0=-this.g*(this.tanh((this.drive*samples[i]+this.resonance*this.V[3])/(2*VT))+this.tV[0]);
- this.V[0]+=(dV0+this.dV[0])/(2*this.sampleRate);
- this.dV[0]=dV0;
- this.tV[0]=this.tanh(this.V[0]/(2*VT));
- dV1=this.g*(this.tV[0]-this.tV[1]);
- this.V[1]+=(dV1+this.dV[1])/(2*this.sampleRate);
- this.dV[1]=dV1;
- this.tV[1]=this.tanh(this.V[1]/(2*VT));
- dV2=this.g*(this.tV[1]-this.tV[2]);
- this.V[2]+=(dV2+this.dV[2])/(2*this.sampleRate);
- this.dV[2]=dV2;
- this.tV[2]=this.tanh(this.V[2]/(2*VT));
- dV3=this.g*(this.tV[2]-this.tV[3]);
- this.V[3]+=(dV3+this.dV[3])/(2*this.sampleRate);
- this.dV[3]=dV3;
- this.tV[3]=this.tanh(this.V[3]/(2*VT));
- samples[i]=this.V[3];
- }
- }
- tick(x){
- let dV0,dV1,dV2,dV3;
- dV0=-this.g*(this.tanh((this.drive*x+this.resonance*this.V[3])/(2*VT))+this.tV[0]);
- this.V[0]+=(dV0+this.dV[0])/(2*this.sampleRate);
- this.dV[0]=dV0;
- this.tV[0]=this.tanh(this.V[0]/(2*VT));
- dV1=this.g*(this.tV[0]-this.tV[1]);
- this.V[1]+=(dV1+this.dV[1])/(2*this.sampleRate);
- this.dV[1]=dV1;
- this.tV[1]=this.tanh(this.V[1]/(2*VT));
- dV2=this.g*(this.tV[1]-this.tV[2]);
- this.V[2]+=(dV2+this.dV[2])/(2*this.sampleRate);
- this.dV[2]=dV2;
- this.tV[2]=this.tanh(this.V[2]/(2*VT));
- dV3=this.g*(this.tV[2]-this.tV[3]);
- this.V[3]+=(dV3+this.dV[3])/(2*this.sampleRate);
- this.dV[3]=dV3;
- this.tV[3]=this.tanh(this.V[3]/(2*VT));
- return this.V[3];
- }
- setResonance(r){
- this.resonance=r;
- }
- setCutoff(c){
- this.cutoff=c;
- const x=(Math.PI*this.cutoff)/this.sampleRate;
- this.g=4*Math.PI*VT*this.cutoff*(1-x)/(1+x);
- }
- tanh(x){
- return Math.tanh(x);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment