Tabula Sonora 0.1.0
A native C++20 implementation of the Roland Sound Canvas VA synth voice
Loading...
Searching...
No Matches
ts::StateVariableFilter Class Reference

The engine's filter: a Chamberlin state-variable filter, with all four responses taken as different taps of the same two-integrator loop. More...

#include <tabulasonora/state_variable_filter.hpp>

Public Member Functions

double low () const noexcept
 The lowpass integrator's current value.
double band () const noexcept
 The bandpass integrator's current value.
void reset () noexcept
 Clears the filter state.
double process (double input, double f, double q, FilterTap tap) noexcept
 Advances the filter by one sample and returns the selected tap.

Detailed Description

The engine's filter: a Chamberlin state-variable filter, with all four responses taken as different taps of the same two-integrator loop.

Read from the disassembly rather than inferred: there is no prescale, no oversampling and no reordering. Per sample,

low  += f * band;
high  = input - (q * band + low);
band += f * high;

Both coefficients come from tables, so no fitted constant appears anywhere in this path. Note that q is Chamberlin's reciprocal-Q: smaller means more resonant, and the neutral resonance byte gives exactly 1.0.

The order of those three lines is load-bearing and so is the grouping in the middle one. Reassociating input - (q*band + low) into input - q*band - low is a different sequence of IEEE operations, and the build disables FMA contraction so the compiler cannot fuse them either.

Verified against the reference, not just transcribed. Driving this recurrence with the engine's own filter-input buffer and its own f/q — both read live out of the DLL — reproduces its filter output to a mean relative error of about 1e-5, which is float32 round-off. A long investigation into drum tone 1946 passed through here twice on the way to the real cause, which turned out to be in the wave decode; see WaveRom. Nothing in this file needed changing, and a q clamp proposed along the way would have been a curve fit over an unrelated bug.

Member Function Documentation

◆ low()

double ts::StateVariableFilter::low ( ) const
inlinenodiscardnoexcept

The lowpass integrator's current value.

◆ band()

double ts::StateVariableFilter::band ( ) const
inlinenodiscardnoexcept

The bandpass integrator's current value.

◆ reset()

void ts::StateVariableFilter::reset ( )
inlinenoexcept

Clears the filter state.

◆ process()

double ts::StateVariableFilter::process ( double input,
double f,
double q,
FilterTap tap )
inlinenodiscardnoexcept

Advances the filter by one sample and returns the selected tap.

f is the frequency coefficient, 2*sin(pi*fc/fs); q is the reciprocal-Q damping.


The documentation for this class was generated from the following file: