import type { IReset } from "@thi.ng/api" ; import { unsupported } from "@thi.ng/errors/unsupported" ; import { PI , SQRT2 , SQRT2_2 } from "@thi.ng/math/api" ; import { clamp05 } from "@thi.ng/math/interval" ; import type { BiquadType , FilterConfig , IFilter } from "./api.js" ; import { AProc } from "./aproc.js" ; import { dbMag } from "./convert.js" ; export const biquad = ( type , fc , q ? , gain ? ) => new Biquad ( type , fc , q , gain ) ; export const biquadLP = ( fc , q ? ) => new Biquad ( "lp" , fc , q ) ; export const biquadHP = ( fc , q ? ) => new Biquad ( "hp" , fc , q ) ; export const biquadBP = ( fc , q ? ) => new Biquad ( "bp" , fc , q ) ; export const biquadNotch = ( fc , q ? ) => new Biquad ( "notch" , fc , q ) ; export const biquadPeak = ( fc , q ? , gain = 6 ) => new Biquad ( "peak" , fc , q , gain ) ; export const biquadLoShelf = ( fc , gain = - 6 ) => new Biquad ( "loshelf" , fc , undefined , gain ) ; export const biquadHiShelf = ( fc , gain = - 6 ) => new Biquad ( "hishelf" , fc , undefined , gain ) ; export class Biquad extends AProc < number , number > implements IReset , IFilter { protected _a0 ! : number ; protected _a1 ! : number ; protected _a2 ! : number ; protected _b1 ! : number ; protected _b2 ! : number ; protected _z1 ! : number ; protected _z2 ! : number ; constructor ( protected _type , protected _freq , protected _q = SQRT2_2 , protected _gain = 0 ) { super ( 0 ) ; this . reset ( ) ; this . calcCoeffs ( ) ; } reset ( ) { this . _z1 = this . _z2 = this . _val = 0 ; return this ; } next ( x ) { const out = x * this . _a0 + this . _z1 ; this . _z1 = x * this . _a1 + this . _z2 - this . _b1 * out ; this . _z2 = x * this . _a2 - this . _b2 * out ; return ( this . _val = out ) ; } freq ( ) { return this . _freq ; } q ( ) { return this . _q ; } gain ( ) { return this . _gain ; } set ( fc , q , gain ) { this . _freq = clamp05 ( fc ) ; this . _q = q ; this . _gain = gain ; this . calcCoeffs ( ) ; } setFreq ( fc ) { this . _freq = clamp05 ( fc ) ; this . calcCoeffs ( ) ; } setQ ( q ) { this . _q = q ; this . calcCoeffs ( ) ; } setGain ( g ) { this . _gain = g ; this . calcCoeffs ( ) ; } filterCoeffs ( ) { return { zeroes : [ this . _a0 , this . _a1 , this . _a2 ] , poles : [ 1 , this . _b1 , this . _b2 ] , } ; } protected calcCoeffs ( ) { const k = Math . tan ( PI * this . _freq ) ; const k2 = k * k ; const k22 = 2 * ( k2 - 1 ) ; const kq = k / this . _q ; const k2kqp1 = 1 + kq + k2 ; const k2kqm1 = 1 - kq + k2 ; const ksqrt2 = k * SQRT2 ; const v = dbMag ( Math . abs ( this . _gain ) ) ; const kvq = k * ( v / this . _q ) ; const ksqrt2v = k * Math . sqrt ( 2 * v ) ; let norm = 1 / k2kqp1 ; switch ( this . _type ) { case "lp" : this . _a0 = k2 * norm ; this . _a1 = 2 * this . _a0 ; this . _a2 = this . _a0 ; this . _b1 = k22 * norm ; this . _b2 = k2kqm1 * norm ; break ; case "hp" : this . _a0 = norm ; this . _a1 = - 2 * this . _a0 ; this . _a2 = this . _a0 ; this . _b1 = k22 * norm ; this . _b2 = k2kqm1 * norm ; break ; case "bp" : this . _a0 = kq * norm ; this . _a1 = 0 ; this . _a2 = - this . _a0 ; this . _b1 = k22 * norm ; this . _b2 = k2kqm1 * norm ; break ; case "notch" : this . _a0 = ( 1 + k2 ) * norm ; this . _a1 = k22 * norm ; this . _a2 = this . _a0 ; this . _b1 = this . _a1 ; this . _b2 = k2kqm1 * norm ; break ; case "peak" : { const z1 = 1 + kvq + k2 ; const z2 = 1 - kvq + k2 ; if ( this . _gain >= 0 ) { this . _a0 = z1 * norm ; this . _a1 = k22 * norm ; this . _a2 = z2 * norm ; this . _b1 = this . _a1 ; this . _b2 = k2kqm1 * norm ; } else { norm = 1 / z1 ; this . _a0 = k2kqp1 * norm ; this . _a1 = k22 * norm ; this . _a2 = k2kqm1 * norm ; this . _b1 = this . _a1 ; this . _b2 = z2 * norm ; } break ; } case "loshelf" : { const z1 = 1 + ksqrt2 + k2 ; const z2 = 1 - ksqrt2 + k2 ; const vk2 = v * k2 ; const y1 = 1 + ksqrt2v + vk2 ; const y2 = 1 - ksqrt2v + vk2 ; const vk22 = 2 * ( vk2 - 1 ) ; if ( this . _gain >= 0 ) { norm = 1 / z1 ; this . _a0 = y1 * norm ; this . _a1 = vk22 * norm ; this . _a2 = y2 * norm ; this . _b1 = k22 * norm ; this . _b2 = z2 * norm ; } else { norm = 1 / y1 ; this . _a0 = z1 * norm ; this . _a1 = k22 * norm ; this . _a2 = z2 * norm ; this . _b1 = vk22 * norm ; this . _b2 = y2 * norm ; } break ; } case "hishelf" : { const z1 = 1 + ksqrt2 + k2 ; const z2 = 1 - ksqrt2 + k2 ; const y1 = v + ksqrt2v + k2 ; const y2 = v - ksqrt2v + k2 ; const vk2 = 2 * ( k2 - v ) ; if ( this . _gain >= 0 ) { norm = 1 / z1 ; this . _a0 = y1 * norm ; this . _a1 = vk2 * norm ; this . _a2 = y2 * norm ; this . _b1 = k22 * norm ; this . _b2 = z2 * norm ; } else { norm = 1 / y1 ; this . _a0 = z1 * norm ; this . _a1 = k22 * norm ; this . _a2 = z2 * norm ; this . _b1 = vk2 * norm ; this . _b2 = y2 * norm ; } break ; } default : unsupported ( `invalid filter type: ${ this . _type }` ) ; } } }