Last active
September 12, 2016 08:54
-
-
Save mohayonao/7598b91ce875c36784b2def5e4630102 to your computer and use it in GitHub Desktop.
BiquadFilter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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 ]; | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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