Skip to content

Getting Started

Requirements

  • A C++17 compiler
  • CMake 3.14 or newer if consuming via CMake.

constfilt is header-only. consteig and gcem are vendored under include/constfilt/vendor/ and are pulled in automatically by constfilt's own headers; no separate installation is needed.

Consuming the library

add_subdirectory

add_subdirectory(third_party/constfilt)
target_link_libraries(your_target PRIVATE constfilt::constfilt)

FetchContent

include(FetchContent)
FetchContent_Declare(
    constfilt
    GIT_REPOSITORY https://github.com/MitchellThompkins/constfilt
    GIT_TAG        main
)
FetchContent_MakeAvailable(constfilt)

target_link_libraries(your_target PRIVATE constfilt::constfilt)

constfilt::constfilt is an INTERFACE target that propagates the include path and the consteig / gcem dependencies transitively.

Header-only, no CMake

Add the constfilt, consteig, and gcem include directories to your compiler's include path, then #include <constfilt/constfilt.hpp>.

Hello, filter

#include <constfilt/constfilt.hpp>

int main()
{
    // 4th-order Butterworth lowpass, 100 Hz cutoff at 1 kHz sample rate.
    // All coefficient math happens at compile time.
    static constexpr constfilt::Butterworth<double, 4> bw(100.0, 1000.0);

    // Batch filtering.
    double input[8]{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
    double output[8]{};
    bw(input, output);

    // Real-time sample-by-sample filtering (mutates internal state).
    constfilt::Butterworth<double, 4> rt(100.0, 1000.0);
    double y = rt(0.5);
}

Supported filters

  • Butterworth: lowpass, highpass
  • Elliptic: lowpass, highpass

All filter types support Tustin (bilinear, default), ZOH, and MatchedZ discretization.

The library imposes no upper bound on the filter order. The practical upper bound is set by double precision: as N grows, the magnitudes of polynomial coefficients and pole separations stretch over many orders of magnitude and round-off begins to dominate. The committed regression tests cover orders 1 through 8 because that is the range that is comfortably verifiable against Octave's reference implementations.

Selecting variants

// Lowpass / highpass (template parameter 4):
constfilt::Butterworth<double, 4> lp(100.0, 1000.0);
constfilt::Butterworth<double, 4, constfilt::Tustin, constfilt::HighPass> hp(100.0, 1000.0);

// Discretization method (template parameter 3):
constfilt::Butterworth<double, 4, constfilt::ZOH> zoh(100.0, 1000.0);
constfilt::Butterworth<double, 4, constfilt::MatchedZ> mz(100.0, 1000.0);

// Elliptic takes (cutoff_hz, passband_ripple_dB, stopband_atten_dB, sample_rate_hz):
constfilt::Elliptic<double, 4> el(100.0, 0.5, 60.0, 1000.0);
constfilt::Elliptic<double, 4, constfilt::Tustin, constfilt::HighPass> el_hp(100.0, 0.5, 60.0, 1000.0);

// Convenience aliases for first-order RC equivalents:
constfilt::FirstOrderLowPass<double> first_lp(100.0, 1000.0);
constfilt::FirstOrderHighPass<double> first_hp(100.0, 1000.0);

Accessing coefficients

The discretized transfer function is exposed as coeffs_b() and coeffs_a() on the underlying Filter, both constexpr:

static constexpr auto bw = constfilt::Butterworth<double, 2>(100.0, 1000.0);
// coeffs_b() and coeffs_a() return const T* that can be indexed:
constexpr double b0 = bw.coeffs_b()[0];
constexpr double a1 = bw.coeffs_a()[1];

Stability checking

AnalogFilter (the base of Butterworth and Elliptic) exposes a CheckStab template parameter (fourth parameter, default true) for stability checking (not yet implemented). To skip the check, pass CheckStab = false.