Adapting the EQ filters from the Audio EQ Cookbook, to be pasted and reused in other projects.
Log in to post a comment.
input.type = 0; // min=0, max=8, step=1 (LPF, HPF, resonance, BPF, notch, APF, peak (use dBgain), lowShelf (use dBgain), highShelf (use dBgain)) input.f0 = 2000; // min=20, max=20000, step=1 input.Q = 3; // min=0.1, max=15, step=0.01 input.dBgain = 6; // min=-12, max=12, step=0.1 // ========== Audio EQ filters ========== const cos = Math.cos, sin = Math.sin, PI = Math.PI, sqrt = Math.sqrt; // Second-order section in Transposed Direct Form II // https://ccrma.stanford.edu/~jos/filters/Transposed_Direct_Forms.html class TDF2 { constructor(a1,a2,b0,b1,b2) { this.set_coefs(a1,a2,b0,b1,b2); this.s1 = 0; this.s2 = 0; } set_coefs(a1,a2,b0,b1,b2){ this.a1 = a1; this.a2 = a2; this.b0 = b0; this.b1 = b1; this.b2 = b2; } process(xn) { var yn = this.s1 + this.b0 * xn; this.s1 = this.s2 - this.a1 * yn + this.b1 * xn; this.s2 = - this.a2 * yn + this.b2 * xn; return yn; } } const biquad_types = ["LPF", "HPF", "resonance", "BPF", "notch", "APF", "peak", "lowShelf", "highShelf"]; // Biquad filter coefficients from the Audio EQ Cookbook // https://www.w3.org/TR/audio-eq-cookbook/ function calc_biquad_coefs(opt) { var w0 = 2*PI*opt.f0*ditty.dt; var cw0 = cos(w0); var alpha = sin(w0)/(2*opt.Q); if(opt.type == "LPF") { // Low pass filter var b0 = (1 - cw0)/2, b1 = 1 - cw0, b2 = (1 - cw0)/2, a0 = 1 + alpha, a1 = -2*cw0, a2 = 1 - alpha; } else if (opt.type == "HPF") { // High pass filter var b0 = (1 + cw0)/2, b1 = -1 - cw0, b2 = (1 + cw0)/2, a0 = 1 + alpha, a1 = -2*cw0, a2 = 1 - alpha; } else if(opt.type == "resonance") { // Band pass filter (constant skirt gain, peak gain = Q) var b0 = opt.Q*alpha, b1 = 0, b2 = -opt.Q*alpha, a0 = 1 + alpha, a1 = -2*cw0, a2 = 1 - alpha; } else if(opt.type == "BPF") { // Band pass filter (constant 0 dB peak gain) var b0 = alpha, b1 = 0, b2 = -alpha, a0 = 1 + alpha, a1 = -2*cw0, a2 = 1 - alpha; } else if(opt.type == "notch") { // Notch filter (removes frequency f0) var b0 = 1, b1 = -2*cw0, b2 = 1, a0 = 1 + alpha, a1 = -2*cw0, a2 = 1 - alpha; } else if(opt.type == "APF") { // All pass filter (constant gain, phase shift around f0) var b0 = 1 - alpha, b1 = -2*cw0, b2 = 1 + alpha, a0 = 1 + alpha, a1 = -2*cw0, a2 = 1 - alpha; } else if(opt.type == "peak") { // Peaking EQ (requires dBgain) var A = 10**(opt.dBgain/40); var b0 = 1 + alpha*A, b1 = -2*cw0, b2 = 1 - alpha*A, a0 = 1 + alpha/A, a1 = -2*cw0, a2 = 1 - alpha/A; } else if(opt.type == "lowShelf") { // Low shelf (requires dBgain) var A = 10**(opt.dBgain/40); var b0 = A*( (A+1) - (A-1)*cw0 + 2*sqrt(A)*alpha ), b1 = 2*A*( (A-1) - (A+1)*cw0), b2 = A*( (A+1) - (A-1)*cw0 - 2*sqrt(A)*alpha ), a0 = (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha, a1 = -2*( (A-1) + (A+1)*cw0), a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha; } else if(opt.type == "highShelf") { // High shelf (requires dBgain) var A = 10**(opt.dBgain/40); var b0 = A*( (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha ), b1 = -2*A*( (A-1) + (A+1)*cos(w0) ), b2 = A*( (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha ), a0 = (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha, a1 = 2*( (A-1) - (A+1)*cos(w0) ), a2 = (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha; } else { debug.error("Unknown filter type"); } return [a1/a0, a2/a0, b0/a0, b1/a0, b2/a0]; } class BiquadFilter { constructor(opt) { var c = calc_biquad_coefs(opt); this.ops = [new TDF2(...c), new TDF2(...c)]; } process(x, opt) { return [this.ops[0].process(x[0]),this.ops[1].process(x[1])]; } } // Create a Dittytoy-compatible filter. // Only for filters with constant coefficients! const biquad = filter.def(BiquadFilter); class BiquadFilterVar { constructor(opt) { var c = calc_biquad_coefs(opt); this.ops = [new TDF2(...c), new TDF2(...c)]; } process(x, opt) { var c = calc_biquad_coefs(opt); this.ops[0].set_coefs(...c); this.ops[1].set_coefs(...c); return [this.ops[0].process(x[0]),this.ops[1].process(x[1])]; } } // Use this only if you want to change filter type, f0, Q, or dBgain frequently (heavier computations!) const biquadVar = filter.def(BiquadFilterVar); const noise = synth.def( (phase, env) => (Math.random()*2-1) * env.value * ((phase%1) < 0.5 ? 1 : 0), {env:one, duration:1000, amp:0.1}); loop( (i) => { noise.play(hz_to_midi(50)); sleep(1000); }) .connect(biquad.create({type:'LPF', f0:8000, Q:0.5})) //.connect(biquad.create({type:'LPF', f0:2500, Q:10})) //.connect(biquad.create({type:'lowShelf', f0:1000, Q:1, dBgain:-5})) .connect(biquadVar.create({type: () => biquad_types[input.type], f0: () => input.f0, Q: () => input.Q, dBgain: () => input.dBgain}));