// SafeC Standard Library — General FIR / IIR Filters
//
// Streaming (one-sample-at-a-time) difference-equation filters — the
// general form biquad.h's 2nd-order sections and dsp.h's array-level
// primitives both specialize:
//
// FIR ("feedforward"): y[n] = sum_{k=0}^{M-1} b[k]*x[n-k]
// IIR ("feedback"): y[n] = sum_{k=0}^{M-1} b[k]*x[n-k]
// - sum_{k=1}^{N-1} a[k]*y[n-k] (a[0] = 1)
//
// Both keep their own history/state internally (a delay line for FIR, a
// direct-form-I input+output history for IIR) so a caller can feed them
// one real-time sample at a time — e.g. inside std::Reactor's per-task
// processing — rather than needing the whole signal in memory up front
// the way convolution.h's conv_direct does.
#pragma once
#include <std/dsp/fixed.h>
#include <std/dsp/sample_filter.h>
namespace std {
// ── FIR ──────────────────────────────────────────────────────────────────────
struct FirFilter {
const &double coeffs; // b[0..numTaps-1], caller-owned, must outlive the filter
unsigned long numTaps;
&double history; // caller-provided ring buffer, numTaps elements, zero-initialized
unsigned long pos; // ring buffer write position
// Processes one sample, returns y[n].
double process(double x);
// Resets the history to all zeros (numTaps elements).
void reset();
// Processes 'n' samples, writing to 'out' (may alias 'in').
void process_block([]const double in, []double out);
// coeffs/history as above — 'history' must have numTaps elements
// (caller-allocated, typically zero-initialized).
static struct FirFilter new([]const double coeffs, []double history); // numTaps = min(coeffs.len, history.len)
};
// Single-precision FIR, same contract as FirFilter (float taps and history).
struct FirFilter32 {
const &float coeffs;
unsigned long numTaps;
&float history;
unsigned long pos;
float process(float x);
void reset();
void process_block([]const float in, []float out);
static struct FirFilter32 new([]const float coeffs, []float history); // numTaps = min(coeffs.len, history.len)
};
struct FFirFilter {
const &Fixed coeffs;
unsigned long numTaps;
&Fixed history;
unsigned long pos;
Fixed process(Fixed x);
void reset();
void process_block([]const Fixed in, []Fixed out);
static struct FFirFilter new([]const Fixed coeffs, []Fixed history); // numTaps = min(coeffs.len, history.len)
};
// ── IIR (general order, direct form I) ────────────────────────────────────────
struct IirFilter {
const &double b; // feedforward coeffs, b[0..numB-1]
unsigned long numB;
const &double a; // feedback coeffs, a[1..numA-1] used (a[0] assumed 1 — pass your a[] with a[0]=1 anyway for clarity)
unsigned long numA;
&double xHistory; // caller-provided, numB elements, zero-initialized
&double yHistory; // caller-provided, numA elements, zero-initialized
unsigned long xPos;
unsigned long yPos;
double process(double x);
void reset();
void process_block([]const double in, []double out);
static struct IirFilter new([]const double b, []const double a,
[]double xHistory, []double yHistory);
};
// Single-precision IIR, same contract as IirFilter. Float rounding grows
// with the order; cascade Biquad32 sections for anything steep.
struct IirFilter32 {
const &float b;
unsigned long numB;
const &float a;
unsigned long numA;
&float xHistory;
&float yHistory;
unsigned long xPos;
unsigned long yPos;
float process(float x);
void reset();
void process_block([]const float in, []float out);
static struct IirFilter32 new([]const float b, []const float a,
[]float xHistory, []float yHistory);
};
struct FIirFilter {
const &Fixed b;
unsigned long numB;
const &Fixed a;
unsigned long numA;
&Fixed xHistory;
&Fixed yHistory;
unsigned long xPos;
unsigned long yPos;
Fixed process(Fixed x);
void reset();
void process_block([]const Fixed in, []Fixed out);
static struct FIirFilter new([]const Fixed b, []const Fixed a,
[]Fixed xHistory, []Fixed yHistory);
};
} // namespace std