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 for state (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.
Parameters:
tapsArray of num_taps CF32 coefficients (I+jQ each), copied.num_tapsFilter 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.
Real taps cost 1 FMA/tap instead of 2 FMA + permute + mul. Use for filters designed with e.g. scipy.signal.firwin.
Parameters:
tapsPointer to num_taps real tap coefficients (copied).num_tapsFilter 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.
>>> 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:
stateFilter state (delay line + taps).inInput array of n_in CF32 samples.n_inNumber of input samples to process.outOutput 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).
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.
>>> 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.
>>> 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 .
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.
>>> 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).
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).
function fir_step¶
Single-sample direct-form FIR step (inline composition API).
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:
sReal-tap filter state (fir_create_real). Must be non-NULL.xOne input sample.
Returns:
The filtered output sample.
Macro Definition Documentation¶
define FIR_STATE_MAGIC¶
define FIR_STATE_VERSION¶
The documentation for this class was generated from the following file native/inc/fir/fir_core.h