This commit is contained in:
5684185+vsariola@users.noreply.github.com
2026-01-17 23:55:37 +02:00
parent fd5e2c3bb8
commit b328cc3a07
5 changed files with 62 additions and 50 deletions

View File

@ -38,6 +38,8 @@ type (
b0, b1, b2 float32
a0, a1, a2 float32
}
SpecAnEnabled Model
)
const (
@ -56,6 +58,8 @@ const (
NumSpecChnModes
)
func (m *Model) SpecAnEnabled() Bool { return MakeEnabledBool((*simpleBool)(&m.specAnEnabled)) }
func NewSpecAnalyzer(broker *Broker) *SpecAnalyzer {
ret := &SpecAnalyzer{broker: broker}
ret.init(SpecAnSettings{})
@ -74,25 +78,29 @@ func (m *Model) BiquadCoeffs() (coeffs BiquadCoeffs, ok bool) {
f := float32(p["frequency"]) / 128
f *= f
r := float32(p["resonance"]) / 128
// in state-space, the filter has the form:
// s(n+1) = A*s(n)+B*u, where A = [1 f;-f 1-f*r-f*f] and B = [0;f]
// y(n) = C*s(n)+D*u, where
// C_low = [1 0]*z, C_band = [0 1]*z, C_high = [-1 -f-r], D_high = [1]
// The equations for the filter are:
// s1[n+1] = s1[n] + f*s2[n]
// h = u - s1[n+1] - r*s2[n]
// s2[n+1] = s2[n] + f*h = s2[n] + f*(u-s1[n]-f*s2[n]-r*s2[n]) = - f*s1[n]+(1-f*r-f*f)*s2[n] + f*u
// y_low[n] = s1[n+1], y_band[n] = s2[n+1], y_high[n] = -s1[n+1]-r*s2[n]+u
// This gives state space representation
// s(n+1) = A*s(n)+B*u, where A = [1 f;-f 1-f*r-f*f] and B = [0;f]
// y(n) = C*s(n)+D*u, where
// C_low = [z 0], C_band = [0 z], C_high = [-z -r], D_high = [1] (note we use those z:s in C to account for those 1 sample time shifts)
// The transfer function is then H(z) = C*(zI-A)^-1*B + D
// z*I-A = [z-1 -f; f z+f*r+f*f-1]
// Invert it:
// (z*I-A)^-1 = 1/det * [z+f*r+f*f-1 f; -f z-1], where det = (z-1)*(z+f*r+f*f-1)+f^2 = z*z+z*f*r+z*f*f-z-z-f*r-f*f+1+f^2 = z*z + z*(f*r+f*f-2)-f*r+1
// (z*I-A)^-1*B = 1/det * f * [f; z-1]
// Low: z * [1,0] * f * [f;z-1] / det = f*f*z / det
// Band: z * [0,1] * f * [f;z-1] / det = (f*z^2-f*z) / det
// High: [-1,-f-r] * f * [f;z-1] / det + 1 = ((-f*f-r*f)*z+r*f)/det + 1 = ((-f*f-r*f)*z+r*f+det)/det = (z^2-2*z+1)/det
// z*I-A = [z-1 -f; f z+f*r+f*f-1]
// Calculate (zI-A)^-1*B:
// (z*I-A)^-1*B = 1/det * [z+f*r+f*f-1 f; -f z-1] * [0;f] = 1/det * f * [f; z-1], where
// det = (z+f*r+f*f-1)*(z-1)+f^2 = z*z+z*f*r+z*f*f-z-z-f*r-f*f+1+f^2 = z*z + (r*f+f*f-2)*z + 1-f*r = a0*z^2 + a1*z + a2
// Low: [z 0]*f*[f;z-1] / det = f*f*z / det = b1 * z / det
// Band: [0 z]*f*[f;z-1] / det = (f*z^2-f*z) / det = (b0*z^2 + b1*z) / det
// High: [-z -r]*f*[f;z-1] / det + 1 = ((-f*f-r*f)*z+r*f)/det + 1 = ((-f*f-r*f)*z+r*f+det)/det = (z^2-2*z+1)/det = (b0*z^2 + b1*z + b2)/det
// Negative versions have only b coefficients negated
var a0 float32 = 1
var a1 float32 = r*f + f*f - 2
var a2 float32 = 1 - f*r
var b0, b1, b2 float32
if p["lowpass"] == 1 {
b1 = f * f
}
b1 += f * f * float32(p["lowpass"])
b0 += f * float32(p["bandpass"])
b1 -= f * float32(p["bandpass"])
b0 += float32(p["highpass"])