All files / src biquad.js

100% Statements 50/50
100% Branches 0/0
100% Functions 11/11
100% Lines 49/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110        327x 327x 327x 327x 327x 327x 327x 327x 327x       311x       151x 151x       78x 78x       20x 20x                       160x 160x       37x 37x 37x                     16x 16x 16x 16x                     16x         35757x 35757x 35757x 35757x 35757x 35757x 35757x 35757x 35757x       50578x 50578x 50578x 72750337x 72750337x 72750337x 72750337x 72750337x 72750337x 72750337x   50578x 50578x 50578x 50578x      
// Coefficients from the RBJ Audio EQ Cookbook: the second-order prototypes
// under a bilinear transform prewarped so the corner lands on `frequency`.
export class Biquad {
    constructor(b0, b1, b2, a1, a2) {
        this.b0 = b0;
        this.b1 = b1;
        this.b2 = b2;
        this.a1 = a1;
        this.a2 = a2;
        this.x1 = 0;
        this.x2 = 0;
        this.y1 = 0;
        this.y2 = 0;
    }
 
    static _fromUnnormalised(b0, b1, b2, a0, a1, a2) {
        return new Biquad(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0);
    }
 
    static _prototype(sampleRate, frequency, q) {
        const w0 = (2 * Math.PI * frequency) / sampleRate;
        return { alpha: Math.sin(w0) / (2 * q), cosW0: Math.cos(w0) };
    }
 
    static lowPass(sampleRate, frequency, q) {
        const { alpha, cosW0 } = Biquad._prototype(sampleRate, frequency, q);
        return Biquad._fromUnnormalised((1 - cosW0) / 2, 1 - cosW0, (1 - cosW0) / 2, 1 + alpha, -2 * cosW0, 1 - alpha);
    }
 
    static highPass(sampleRate, frequency, q) {
        const { alpha, cosW0 } = Biquad._prototype(sampleRate, frequency, q);
        return Biquad._fromUnnormalised(
            (1 + cosW0) / 2,
            -(1 + cosW0),
            (1 + cosW0) / 2,
            1 + alpha,
            -2 * cosW0,
            1 - alpha,
        );
    }
 
    /** A single RC high-pass: 6 dB per octave below the corner. */
    static firstOrderHighPass(sampleRate, frequency) {
        const k = Math.tan((Math.PI * frequency) / sampleRate);
        return Biquad._fromUnnormalised(1, -1, 0, 1 + k, k - 1, 0);
    }
 
    static peaking(sampleRate, frequency, q, gainDb) {
        const a = 10 ** (gainDb / 40);
        const { alpha, cosW0 } = Biquad._prototype(sampleRate, frequency, q);
        return Biquad._fromUnnormalised(
            1 + alpha * a,
            -2 * cosW0,
            1 - alpha * a,
            1 + alpha / a,
            -2 * cosW0,
            1 - alpha / a,
        );
    }
 
    static highShelf(sampleRate, frequency, q, gainDb) {
        const a = 10 ** (gainDb / 40);
        const { alpha, cosW0 } = Biquad._prototype(sampleRate, frequency, q);
        const root = 2 * Math.sqrt(a) * alpha;
        return Biquad._fromUnnormalised(
            a * (a + 1 + (a - 1) * cosW0 + root),
            -2 * a * (a - 1 + (a + 1) * cosW0),
            a * (a + 1 + (a - 1) * cosW0 - root),
            a + 1 - (a - 1) * cosW0 + root,
            2 * (a - 1 - (a + 1) * cosW0),
            a + 1 - (a - 1) * cosW0 - root,
        );
    }
 
    static gain(factor) {
        return new Biquad(factor, 0, 0, 0, 0);
    }
 
    /** Magnitude response at `frequency`, as a linear factor. */
    magnitudeAt(sampleRate, frequency) {
        const w = (2 * Math.PI * frequency) / sampleRate;
        const { b0, b1, b2, a1, a2 } = this;
        const cos1 = Math.cos(w);
        const sin1 = Math.sin(w);
        const cos2 = Math.cos(2 * w);
        const sin2 = Math.sin(2 * w);
        const numerator = Math.hypot(b0 + b1 * cos1 + b2 * cos2, b1 * sin1 + b2 * sin2);
        const denominator = Math.hypot(1 + a1 * cos1 + a2 * cos2, a1 * sin1 + a2 * sin2);
        return numerator / denominator;
    }
 
    process(buffer, offset, length) {
        const { b0, b1, b2, a1, a2 } = this;
        let { x1, x2, y1, y2 } = this;
        for (let i = offset; i < offset + length; ++i) {
            const x = buffer[i];
            const y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
            x2 = x1;
            x1 = x;
            y2 = y1;
            y1 = y;
            buffer[i] = y;
        }
        this.x1 = x1;
        this.x2 = x2;
        this.y1 = y1;
        this.y2 = y2;
    }
}