Skip to content

Instantly share code, notes, and snippets.

@mohayonao
Last active September 12, 2016 08:54
Show Gist options
  • Save mohayonao/7598b91ce875c36784b2def5e4630102 to your computer and use it in GitHub Desktop.
Save mohayonao/7598b91ce875c36784b2def5e4630102 to your computer and use it in GitHub Desktop.
BiquadFilter
"use strict";
const LOWPASS = "lowpass";
const HIGHPASS = "highpass";
const BANDPASS = "bandpass";
const LOWSHELF = "lowshelf";
const HIGHSHELF = "highshelf";
const PEAKING = "peaking";
const NOTCH = "notch";
const ALLPASS = "allpass";
module.exports = {
[LOWPASS](cutoff, resonance) {
if (1 <= cutoff) {
return [ 1, 0, 0, 0, 0 ];
}
if (cutoff <= 0) {
return [ 0, 0, 0, 0, 0 ];
}
resonance = Math.max(0, resonance);
const g = Math.pow(10, 0.05 * resonance);
const d = Math.sqrt((4 - Math.sqrt(16 - 16 / (g * g))) / 2);
const theta = Math.PI * cutoff;
const sn = 0.5 * d * Math.sin(theta);
const beta = 0.5 * (1 - sn) / (1 + sn);
const gamma = (0.5 + beta) * Math.cos(theta);
const alpha = 0.25 * (0.5 + beta - gamma);
const b0 = 2 * alpha;
const b1 = 2 * 2 * alpha;
const b2 = 2 * alpha;
const a1 = 2 * -gamma;
const a2 = 2 * beta;
return [ b0, b1, b2, a1, a2 ];
},
[HIGHPASS](cutoff, resonance) {
if (1 <= cutoff) {
return [ 0, 0, 0, 0, 0 ];
}
if (cutoff <= 0) {
return [ 1, 0, 0, 0, 0 ];
}
resonance = Math.max(0, resonance);
const g = Math.pow(10, 0.05 * resonance);
const d = Math.sqrt((4 - Math.sqrt(16 - 16 / (g * g))) / 2);
const theta = Math.PI * cutoff;
const sn = 0.5 * d * Math.sin(theta);
const beta = 0.5 * (1 - sn) / (1 + sn);
const gamma = (0.5 + beta) * Math.cos(theta);
const alpha = 0.25 * (0.5 + beta + gamma);
const b0 = 2 * alpha;
const b1 = 2 * -2 * alpha;
const b2 = 2 * alpha;
const a1 = 2 * -gamma;
const a2 = 2 * beta;
return [ b0, b1, b2, a1, a2 ];
},
[BANDPASS](frequency, Q) {
if (frequency <= 0 || 1 <= frequency) {
return [ 1, 0, 0, 0, 0 ];
}
if (Q <= 0) {
return [ 1, 0, 0, 0, 0 ];
}
const w0 = Math.PI * frequency;
const alpha = Math.sin(w0) / (2 * Q);
const k = Math.cos(w0);
const b0 = alpha;
const b1 = 0;
const b2 = -alpha;
const a0 = 1 + alpha;
const a1 = -2 * k;
const a2 = 1 - alpha;
return [ b0/a0, b1/a0, b2/a0, a1/a0, a2/a0 ];
},
[LOWSHELF](frequency, _, dbGain) {
if (frequency <= 0) {
return [ 1, 0, 0, 0, 0 ];
}
const A = Math.pow(10, dbGain / 40);
if (1 <= frequency) {
return [ A*A, 0, 0, 0, 0 ];
}
const w0 = Math.PI * frequency;
const S = 1;
const alpha = 0.5 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
const k = Math.cos(w0);
const k2 = 2 * Math.sqrt(A) * alpha;
const aPlusOne = A + 1;
const aMinusOne = A - 1;
const b0 = A * (aPlusOne - aMinusOne * k + k2);
const b1 = 2 * A * (aMinusOne - aPlusOne * k);
const b2 = A * (aPlusOne - aMinusOne * k - k2);
const a0 = aPlusOne + aMinusOne * k + k2;
const a1 = -2 * (aMinusOne + aPlusOne * k);
const a2 = aPlusOne + aMinusOne * k - k2;
return [ b0/a0, b1/a0, b2/a0, a1/a0, a2/a0 ];
},
[HIGHSHELF](frequency, _, dbGain) {
if (1 <= frequency) {
return [ 1, 0, 0, 0, 0 ];
}
const A = Math.pow(10, dbGain / 40);
if (frequency <= 0) {
return [ A*A, 0, 0, 0, 0 ];
}
const w0 = Math.PI * frequency;
const S = 1;
const alpha = 0.5 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
const k = Math.cos(w0);
const k2 = 2 * Math.sqrt(A) * alpha;
const aPlusOne = A + 1;
const aMinusOne = A - 1;
const b0 = A * (aPlusOne + aMinusOne * k + k2);
const b1 = -2 * A * (aMinusOne + aPlusOne * k);
const b2 = A * (aPlusOne + aMinusOne * k - k2);
const a0 = aPlusOne - aMinusOne * k + k2;
const a1 = 2 * (aMinusOne - aPlusOne * k);
const a2 = aPlusOne - aMinusOne * k - k2;
return [ b0/a0, b1/a0, b2/a0, a1/a0, a2/a0 ];
},
[PEAKING](frequency, Q, dbGain) {
if (frequency <= 0 || 1 <= frequency) {
return [ 1, 0, 0, 0, 0 ];
}
const A = Math.pow(10.0, dbGain / 40);
if (Q <= 0) {
return [ A*A, 0, 0, 0, 0 ];
}
const w0 = Math.PI * frequency;
const alpha = Math.sin(w0) / (2 * Q);
const k = Math.cos(w0);
const b0 = 1 + alpha * A;
const b1 = -2 * k;
const b2 = 1 - alpha * A;
const a0 = 1 + alpha / A;
const a1 = -2 * k;
const a2 = 1 - alpha / A;
return [ b0/a0, b1/a0, b2/a0, a1/a0, a2/a0 ];
},
[ALLPASS](frequency, Q) {
if (frequency <= 0 || 1 <= frequency) {
return [ 1, 0, 0, 0, 0 ];
}
if (Q <= 0) {
return [ -1, 0, 0, 0, 0 ];
}
const w0 = Math.PI * frequency;
const alpha = Math.sin(w0) / (2 * Q);
const k = Math.cos(w0);
const b0 = 1 - alpha;
const b1 = -2 * k;
const b2 = 1 + alpha;
const a0 = 1 + alpha;
const a1 = -2 * k;
const a2 = 1 - alpha;
return [ b0/a0, b1/a0, b2/a0, a1/a0, a2/a0 ];
}
};
"use strict";
const BiquadFilterCoefficients = require("./BiquadFilterCoefficients");
const BiquadFilterKernel = require("./BiquadFilterKernel");
class BiquadFilterDSP {
constructor() {
this.sampleRate = 48000;
this.type = "lowpass";
this.frequency = 350;
this.detune = 0;
this.Q = 1;
this.gain = 0;
this._coefficients = [ new Float32Array(128), new Float32Array(128), new Float32Array(128), new Float32Array(128), new Float32Array(128) ];
}
computeCoefficients() {
const nyquist = this.sampleRate * 0.5;
const normalizedFrequency = (this.frequency / nyquist) * Math.pow(2, this.detune / 1200);
const coefficients = BiquadFilterCoefficients[this.type](normalizedFrequency, this.Q, this.gain);
for (let i = 0; i < 5; i++) {
this._coefficients[i].fill(coefficients[i]);
}
}
getFrequencyResponse(frequencyHz, magResponse, phaseResponse) {
const n = frequencyHz.length;
const nyquist = this.sampleRate * 0.5;
const frequency = new Float32Array(n);
for (let i = 0; i < n; i++) {
frequency[i] = Math.max(0, Math.min(frequencyHz[i] / nyquist, 1));
}
const kernel = new BiquadFilterKernel();
kernel.getFrequencyResponse(this._coefficients, frequency, magResponse, phaseResponse);
}
}
module.exports = BiquadFilterDSP;
const dsp = new BiquadFilterDSP();
const f = new Float32Array([ 440, 880, 1760, 3520 ]);
const m = new Float32Array(f.length);
const p = new Float32Array(f.length);
// dsp.frequency = 1000;
// dsp.Q = 2;
dsp.computeCoefficients();
dsp.getFrequencyResponse(f, m, p);
console.log(Array.from(f));
console.log(Array.from(m));
console.log(Array.from(p));
// a = new AudioContext();
// b = a.createBiquadFilter(); f = new Float32Array([ 440, 880, 1760, 3520 ]); m = new Float32Array(f.length); p = new Float32Array(f.length); b.getFrequencyResponse(f, m, p);
// firefox
// m: [ 0.6958974599838257, 0.1681499034166336, 0.03989575430750847, 0.009583186358213425 ]
// p: [ -1.9868215322494507, -2.683311939239502, -2.9293694496154785, -3.0388479232788086 ]
// chrome
// m: [0.7923383116722107, 0.17283505201339722, 0.04013980180025101, 0.009596988558769226]
// p: [-2.0489423274993896, -2.7435009479522705, -2.9599316120147705, -3.0539188385009766]
"use strict";
class BiquadFilterKernel {
constructor() {
this._x1 = 0;
this._x2 = 0;
this._y1 = 0;
this._y2 = 0;
}
process(coefficients, input, output, inNumSamples) {
const b0 = coefficients[0];
const b1 = coefficients[1];
const b2 = coefficients[2];
const a1 = coefficients[3];
const a2 = coefficients[4];
let x0;
let x1 = this._x1;
let x2 = this._x2;
let y0;
let y1 = this._y1;
let y2 = this._y2;
for (let i = 0; i < inNumSamples; i++) {
x0 = input[i];
y0 = (b0[i] * x0) + (b1[i] * x1) + (b2[i] * x2) - (a1[i] * y1) - (a2[i] * y2);
x2 = x1;
x1 = x0;
y2 = y1;
y1 = y0;
output[i] = y0;
}
this._x1 = x1;
this._x2 = x2;
this._y1 = y1;
this._y2 = y2;
}
getFrequencyResponse(coefficients, frequency, magResponse, phaseResponse) {
const b0 = coefficients[0][0];
const b1 = coefficients[1][0];
const b2 = coefficients[2][0];
const a1 = coefficients[3][0];
const a2 = coefficients[4][0];
const n = frequency.length;
for (let i = 0; i < n; i++) {
const w1 = Math.PI * frequency[i];
const w2 = w1 * 2;
const cosw1 = Math.cos(w1);
const cosw2 = Math.cos(w2);
const sinw1 = Math.sin(w1);
const sinw2 = Math.sin(w2);
const cosb = b0 + b1 * cosw1 + b2 * cosw2;
const sinb = b1 * sinw1 + b2 * sinw2;
const cosa = 1 + a1 * cosw1 + a2 * cosw2;
const sina = a1 * sinw1 + a2 * sinw2;
magResponse[i] = Math.sqrt((cosb * cosb + sinb * sinb) / (cosa * cosa + sina * sina));
phaseResponse[i] = Math.atan2(sina, cosa) - Math.atan2(sinb, cosb);
}
}
}
module.exports = BiquadFilterKernel;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment