Skip to content

File fir_core.h

FileList > fir > fir_core.h

Go to the source code of this file

Direct-form FIR filter — real-tap and complex-tap variants. More...

  • #include "clib_common.h"
  • #include "dp_state.h"
  • #include "jm_perf.h"
  • #include <complex.h>
  • #include <stddef.h>

Classes

Type Name
struct fir_state_t

Public Functions

Type Name
fir_state_t * fir_create (const float complex * taps, size_t num_taps)
Create a FIR filter from complex CF32 tap coefficients. Implements a direct-form FIR convolution: y[n] = sum_kh[k] *x[n-k] . The tap array is copied at creation; the caller may free it afterward. Usefir_create_real() instead when all imaginary parts are zero — that path costs 1 FMA/tap versus 2 FMA + permute + mul here.
fir_state_t * fir_create_real (const float * taps, size_t num_taps)
Create a FIR filter from real float tap coefficients.
void fir_destroy (fir_state_t * state)
Release all heap resources owned by the filter state. Frees the tap array, delay line, and scratch buffer, then the state struct itself. Passing NULL is a no-op. The Python wrapper calls this automatically in del andexit ; call it explicitly only when you want deterministic resource release before GC.
size_t fir_execute (fir_state_t * state, const float complex * in, size_t n_in, float complex * out)
Filter n_in CF32 samples and write the results to out. Each output sample is the inner product of the tap vector with the current delay line. The delay line is updated with each input sample so state carries over across successive calls — process frames of any size without gaps or overlap. The scratch buffer is grown lazily on the first call and reused on subsequent calls of the same size.
size_t fir_execute_max_out (fir_state_t * state)
Upper bound on execute output samples (always == n_in for FIR).
int fir_get_is_real (const fir_state_t * state)
True when the filter was created with real-valued tap coefficients. Real-tap filters (fir_create_real) use a cheaper inner loop: 1 FMA/tap versus the 2 FMA + lane permute required for complex multiplication. Use this flag to confirm which constructor path was used at runtime.
size_t fir_get_num_taps (const fir_state_t * state)
Number of tap coefficients supplied at creation. This equals the filter group delay plus one, and determines the minimum input block length for which no latency is observable.
void fir_get_state (const fir_state_t * state, void * blob)
Serialize state's delay line intoblob .
void fir_reset (fir_state_t * state)
Zero the delay line; preserve taps and scratch capacity. After a reset the filter behaves identically to a freshly constructed instance of the same length, without paying the allocation cost again. Call this between unrelated signal segments to prevent inter-segment leakage through the delay line.
int fir_set_state (fir_state_t * state, const void * blob)
Restore the delay line from blob (same num_taps).
size_t fir_state_bytes (const fir_state_t * state)
Bytes fir_get_state() writes forstate (envelope + payload).
JM_FORCEINLINE JM_HOT float complex fir_step (fir_state_t * s, float complex x)
Single-sample direct-form FIR step (inline composition API).

Macros

Type Name
define FIR_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('F', 'I', 'R', '\_')
define FIR_STATE_VERSION 1u

Detailed Description

Two constructors select the tap type at creation time:

fir_create() — complex CF32 taps (general case) fir_create_real() — real float taps (1 FMA/tap; use for real-valued designs)

All execute functions accept CF32 input and write CF32 output. The internal scratch buffer (delay + input) is allocated lazily on the first execute call and grown as needed.

float taps[63] = { ... };
fir_state_t *fir = fir_create_real(taps, 63);
float complex out[4096];
fir_execute(fir, signal, 4096, out);
fir_destroy(fir);

Public Functions Documentation

function fir_create

Create a FIR filter from complex CF32 tap coefficients. Implements a direct-form FIR convolution: y[n] = sum_kh[k] *x[n-k] . The tap array is copied at creation; the caller may free it afterward. Usefir_create_real() instead when all imaginary parts are zero — that path costs 1 FMA/tap versus 2 FMA + permute + mul here.

fir_state_t * fir_create (
    const float complex * taps,
    size_t num_taps
) 

Parameters:

  • taps Array of num_taps CF32 coefficients (I+jQ each), copied.
  • num_taps Filter length (>= 1).

Returns:

Heap-allocated state, or NULL on allocation failure.

>>> import numpy as np
>>> from doppler.filter import FIR
>>> taps = np.array([0.25+0j, 0.5+0j, 0.25+0j], dtype=np.complex64)
>>> fir = FIR(taps)
>>> fir.num_taps
3
>>> fir.is_real
False


function fir_create_real

Create a FIR filter from real float tap coefficients.

fir_state_t * fir_create_real (
    const float * taps,
    size_t num_taps
) 

Real taps cost 1 FMA/tap instead of 2 FMA + permute + mul. Use for filters designed with e.g. scipy.signal.firwin.

Parameters:

  • taps Pointer to num_taps real tap coefficients (copied).
  • num_taps Filter length (>= 1).

Returns:

Heap-allocated state, or NULL on allocation failure.


function fir_destroy

Release all heap resources owned by the filter state. Frees the tap array, delay line, and scratch buffer, then the state struct itself. Passing NULL is a no-op. The Python wrapper calls this automatically in del andexit ; call it explicitly only when you want deterministic resource release before GC.

void fir_destroy (
    fir_state_t * state
) 

>>> import numpy as np
>>> from doppler.filter import FIR
>>> taps = np.array([0.25+0j, 0.5+0j, 0.25+0j], dtype=np.complex64)
>>> with FIR(taps) as fir:
...     y = fir.execute(1.0+0j)
...     y.dtype
dtype('complex64')

function fir_execute

Filter n_in CF32 samples and write the results to out. Each output sample is the inner product of the tap vector with the current delay line. The delay line is updated with each input sample so state carries over across successive calls — process frames of any size without gaps or overlap. The scratch buffer is grown lazily on the first call and reused on subsequent calls of the same size.

size_t fir_execute (
    fir_state_t * state,
    const float complex * in,
    size_t n_in,
    float complex * out
) 

Parameters:

  • state Filter state (delay line + taps).
  • in Input array of n_in CF32 samples.
  • n_in Number of input samples to process.
  • out Output buffer; caller must provide space for n_in CF32 values.

Returns:

Number of output samples written (always == n_in).

>>> import numpy as np
>>> from doppler.filter import FIR
>>> taps = np.array([0.25+0j, 0.5+0j, 0.25+0j], dtype=np.complex64)
>>> fir = FIR(taps)
>>> x = np.array([1+0j, 0+0j, 0+0j], dtype=np.complex64)
>>> y = fir.execute(x)
>>> y.dtype
dtype('complex64')
>>> y.shape
(3,)
>>> [round(float(v.real), 4) for v in y]
[0.25, 0.5, 0.25]


function fir_execute_max_out

Upper bound on execute output samples (always == n_in for FIR).

size_t fir_execute_max_out (
    fir_state_t * state
) 

Used by the generated ext.c to size the output buffer. Returns 0 at creation time (n_in unknown); buffer grows on first call.


function fir_get_is_real

True when the filter was created with real-valued tap coefficients. Real-tap filters (fir_create_real) use a cheaper inner loop: 1 FMA/tap versus the 2 FMA + lane permute required for complex multiplication. Use this flag to confirm which constructor path was used at runtime.

int fir_get_is_real (
    const fir_state_t * state
) 

>>> import numpy as np
>>> from doppler.filter import FIR
>>> taps = np.array([0.25+0j, 0.5+0j, 0.25+0j], dtype=np.complex64)
>>> FIR(taps).is_real
False

function fir_get_num_taps

Number of tap coefficients supplied at creation. This equals the filter group delay plus one, and determines the minimum input block length for which no latency is observable.

size_t fir_get_num_taps (
    const fir_state_t * state
) 

>>> import numpy as np
>>> from doppler.filter import FIR
>>> taps = np.array([0.25+0j, 0.5+0j, 0.25+0j], dtype=np.complex64)
>>> FIR(taps).num_taps
3

function fir_get_state

Serialize state's delay line intoblob .

void fir_get_state (
    const fir_state_t * state,
    void * blob
) 


function fir_reset

Zero the delay line; preserve taps and scratch capacity. After a reset the filter behaves identically to a freshly constructed instance of the same length, without paying the allocation cost again. Call this between unrelated signal segments to prevent inter-segment leakage through the delay line.

void fir_reset (
    fir_state_t * state
) 

>>> import numpy as np
>>> from doppler.filter import FIR
>>> taps = np.array([0.25+0j, 0.5+0j, 0.25+0j], dtype=np.complex64)
>>> fir = FIR(taps)
>>> x = np.array([1+0j, 0+0j, 0+0j], dtype=np.complex64)
>>> _ = fir.execute(x)
>>> fir.reset()
>>> y = fir.execute(x)
>>> [round(float(v.real), 4) for v in y]
[0.25, 0.5, 0.25]

function fir_set_state

Restore the delay line from blob (same num_taps).

int fir_set_state (
    fir_state_t * state,
    const void * blob
) 

Returns:

DP_OK, or DP_ERR_INVALID if the blob's envelope rejects.


function fir_state_bytes

Bytes fir_get_state() writes forstate (envelope + payload).

size_t fir_state_bytes (
    const fir_state_t * state
) 


function fir_step

Single-sample direct-form FIR step (inline composition API).

JM_FORCEINLINE  JM_HOT float complex fir_step (
    fir_state_t * s,
    float complex x
) 

Filters one sample and advances the delay line: returns y = sum_k h[k] * x[n-k] and shifts x into the length-num_taps-1 delay line (dropping the oldest sample). This is the per-sample counterpart to fir_execute() — a tracking receiver inlines it into its own sample loop (e.g. a matched filter feeding a symbol-timing loop) where fir_execute()'s block interface cannot. It mirrors fir_execute()'s real-tap scalar accumulation term for term, so a fir_step() stream matches fir_execute() to within floating-point rounding: equal in exact arithmetic; a contracted FMA can differ by ~1 ULP across translation units, and fir_execute() on a multi-sample block can differ a little more from SIMD reassociation. Cost is num_taps MACs plus an O(num_taps) delay-line shift per sample.

Note:

Real-tap filters only (fir_create_real). Pulse-shape matched filters — RRC, raised-cosine, integrate-and-dump — are real-valued, which is the streaming use case this serves; a complex-tap variant would add a complex MAC branch and is left until a consumer needs it.

Parameters:

  • s Real-tap filter state (fir_create_real). Must be non-NULL.
  • x One input sample.

Returns:

The filtered output sample.


Macro Definition Documentation

define FIR_STATE_MAGIC

#define FIR_STATE_MAGIC `DP_FOURCC ('F', 'I', 'R', '_')`

define FIR_STATE_VERSION

#define FIR_STATE_VERSION `1u`


The documentation for this class was generated from the following file native/inc/fir/fir_core.h