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        355x 355x 355x 355x 355x 355x 355x 355x 355x       339x       158x 158x       85x 85x       20x 20x                       181x 181x       37x 37x 37x                     16x 16x 16x 16x                     16x         35757x 35757x 35757x 35757x 35757x 35757x 35757x 35757x 35757x       59142x 59142x 59142x 84173441x 84173441x 84173441x 84173441x 84173441x 84173441x 84173441x   59142x 59142x 59142x 59142x      
// 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;
    }
}