File ddc_core.h¶
FileList > ddc > ddc_core.h
Go to the source code of this file
Digital Down-Converter — composes LO + RateConverter cascade. More...
#include <complex.h>#include <stddef.h>#include "lo/lo_core.h"#include "RateConverter/RateConverter_core.h"#include "resamp/resamp_core.h"#include "hbdecim/hbdecim_core.h"#include "cic/cic_core.h"#include "fir/fir_core.h"#include "resample/resample_core.h"
Classes¶
| Type | Name |
|---|---|
| struct | ddc_extra_t |
| struct | ddcr_extra_t |
Public Types¶
| Type | Name |
|---|---|
| typedef struct ddc_state | ddc_state_t |
| typedef struct ddcr_state | ddcr_state_t |
Public Functions¶
| Type | Name |
|---|---|
| ddc_state_t * | ddc_create (double norm_freq, double rate) Create a complex-input Digital Down-Converter. Allocates internal state for the LO and RateConverter cascade. The RateConverter selects the cheapest multi-stage decimation chain (CIC + optional halfband + polyphase resampler) for the given rate. |
| void | ddc_destroy (ddc_state_t * state) Free all resources held by a DDC instance. Releases the RateConverter and LO substructures, then the struct itself. Passing NULL is a no-op. |
| size_t | ddc_execute (ddc_state_t * state, const float complex * x, size_t x_len, float complex * out, size_t max_out) Mix and resample a block of CF32 samples. Multiplies each input sample by the current LO phasor (advancing the NCO phase per sample), then feeds the mixed block into the RateConverter. The resampler maintains history across calls, so arbitrary block sizes produce contiguous output with no edge artefacts. Output length ≈ x_len * rate (varies by ±1 due to polyphase indexing). |
| size_t | ddc_execute_max_out (ddc_state_t * state) Return the maximum output samples for one execute call. |
| double | ddc_get_norm_freq (const ddc_state_t * state) Return the current LO normalised frequency (cycles/sample). |
| double | ddc_get_rate (const ddc_state_t * state) Return the configured output/input rate ratio (read-only). The rate is fixed at create time; change it by destroying and recreating the DDC with the new value. |
| void | ddc_get_state (const ddc_state_t * state, void * blob) Serialize state's LO + RateConverter state intoblob . |
| void | ddc_reset (ddc_state_t * state) Zero LO phase and resampler history. After reset, the next execute call produces the same output as the first execute after create — useful for reproducible block-by-block processing or looped test fixtures. |
| size_t | ddc_run (ddc_state_t * state, const void * state_in, void * state_out, const float complex * in, size_t n_in, float complex * out, size_t max_out) Pure run: (state_in, input) -> (state_out, output) ; either blob may be NULL (NULL in = current; NULL out = discard). |
| void | ddc_set_norm_freq (ddc_state_t * state, double val) Retune the LO without resetting phase or resampler history. Updates the NCO phase increment atomically so the carrier shift changes seamlessly across block boundaries. The resampler history and LO phase accumulator are left intact, avoiding the transient that a full reset would cause. |
| int | ddc_set_state (ddc_state_t * state, const void * blob) Restore LO + RateConverter state from blob . |
| size_t | ddc_state_bytes (const ddc_state_t * state) Byte size of state's blob (envelope + extra + lo + rc). |
| ddcr_state_t * | ddcr_create (double norm_freq, double rate) Create a real-input Digital Down-Converter (Architecture D2). The signal chain is: halfband R2C (2:1, bakes in +fs/4 shift) → fine LO mix at the intermediate rate (fs_in/2) → RateConverter → CF32 output. The halfband stage uses ±1/0 coefficients (no multiplications), making DDCR roughly 2× cheaper than DDC at the same total decimation ratio. |
| void | ddcr_destroy (ddcr_state_t * s) Free all resources held by a DDCR instance. Releases the halfband, RateConverter, and LO substructures, then the struct itself. Passing NULL is a no-op. |
| size_t | ddcr_execute (ddcr_state_t * s, const float * in, size_t n_in, float _Complex * out, size_t max_out) Process a block of real float32 samples through the full DDCR signal chain: halfband R2C → LO mix → RateConverter → CF32. The halfband decimates by 2 and applies a built-in +fs/4 frequency shift; the fine NCO then completes the tuning. State is maintained across calls for contiguous streaming. Output length ≈ n_in * rate (±1 from polyphase indexing). A real tone at input normalised frequency f_c has amplitude 0.5 in the baseband output (one-sided spectrum), consistent with analytic signal theory. |
| double | ddcr_get_norm_freq (const ddcr_state_t * s) Return the current fine NCO normalised frequency at the intermediate rate (fs_in/2, cycles/sample). |
| double | ddcr_get_rate (const ddcr_state_t * s) Return the total configured rate (fs_out / fs_in, read-only). This is the end-to-end ratio from ADC input to CF32 output. Change it by destroying and recreating the DDCR. |
| void | ddcr_get_state (const ddcr_state_t * s, void * blob) Serialize s's full-chain state intoblob . |
| void | ddcr_reset (ddcr_state_t * s) Zero halfband filter history, LO phase, and resampler history. After reset, the next execute call reproduces the output of the first call after create, enabling repeatable block-by-block tests. |
| size_t | ddcr_run (ddcr_state_t * s, const void * state_in, void * state_out, const float * in, size_t n_in, float _Complex * out, size_t max_out) Pure run: inject state_in , processin , exportstate_out —(state_in, input) -> (state_out, output) over an engine treated as immutable config. Either state may be NULL (NULL in = use current; NULL out = discard).state_in /state_out may alias. |
| void | ddcr_set_norm_freq (ddcr_state_t * s, double norm_freq) Retune the fine NCO without resetting halfband or resampler history. Updates the LO phase increment only; state is preserved for seamless tuning across block boundaries. |
| int | ddcr_set_state (ddcr_state_t * s, const void * blob) Restore full-chain state from blob intos . |
| size_t | ddcr_state_bytes (const ddcr_state_t * s) Byte size of s's state blob (envelope + extra + chain). |
Macros¶
| Type | Name |
|---|---|
| define | DDCR_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('D', 'D', 'C', 'R') |
| define | DDCR_STATE_VERSION 1u |
| define | DDC_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('D', 'D', 'C', '\_') |
| define | DDC_STATE_VERSION 1u |
Detailed Description¶
Two types:
Ddc — complex (CF32) input. Chain: LO mix → RateConverter. DdcR — real (float32) input. Chain: halfband R2C → LO mix → RateConverter.
Both are streaming (variable block size per execute call).
RateConverter selects the cheapest cascade (CIC + optional halfband + polyphase resampler) for the requested rate at create time. This makes large-ratio decimation (e.g., 100:1) significantly cheaper than a single polyphase stage.
Ddc signal chain¶
norm_freq: NCO normalised frequency (cycles/sample at fs_in). Set to -f_carrier to shift a carrier at f_carrier to DC.
DdcR signal chain¶
float in (fs_in) → halfband R2C (2:1, embedded fs/4 shift)
→ LO mix at intermediate rate (fs_in/2)
→ RateConverter → CF32 out (fs_out)
norm_freq: Fine NCO frequency at the INTERMEDIATE rate (fs_in/2). To tune a real tone at f_carrier (input normalised) to DC: set norm_freq = -(2*f_carrier + 0.5). Total output rate: fs_out = rate * fs_in (rate < 0.5).
DdcR is approximately 2x cheaper than Ddc at equivalent total decimation because the halfband R2C step has an fs/4 frequency shift baked in at zero extra multiplications — the +/-1/0 coefficients multiply for free.
Retuning vs. rebuilding¶
- Retune (centre-frequency change): call ddc_set_norm_freq / ddcr_set_norm_freq. Cheap — updates the LO phase increment without disturbing the resampler history. Seamless across block boundaries.
- Rate change (span / decimation change): destroy and recreate the DDC for the new rate.
Usage¶
// Complex DDC: shift a carrier at +0.1·fs to DC, decimate by 4
ddc_state_t *ddc = ddc_create(-0.1, 0.25);
float _Complex out[4096];
size_t n = ddc_execute(ddc, in, 1024, out, 4096);
ddc_destroy(ddc);
// Real DDC: same carrier and decimation from a real ADC stream
// norm_freq at intermediate rate: -(2 * 0.1 + 0.5) = -0.7
ddcr_state_t *ddcr = ddcr_create(-0.7, 0.25);
size_t m = ddcr_execute(ddcr, real_in, 1024, out, 4096);
ddcr_destroy(ddcr);
Public Types Documentation¶
typedef ddc_state_t¶
typedef ddcr_state_t¶
Public Functions Documentation¶
function ddc_create¶
Create a complex-input Digital Down-Converter. Allocates internal state for the LO and RateConverter cascade. The RateConverter selects the cheapest multi-stage decimation chain (CIC + optional halfband + polyphase resampler) for the given rate.
Parameters:
norm_freqLO frequency in cycles/sample at the input rate. Set to -f_carrier to shift a carrier at f_carrier to DC. Any real value is accepted.rateOutput rate / input rate. Must be > 0. Values ≥ 1 are up-sampling; typical use is decimation (0 < rate < 1).
Returns:
Non-NULL on success, NULL on OOM or invalid args.
>>> from doppler.ddc import DDC
>>> ddc = DDC(norm_freq=-0.1, rate=0.25)
>>> ddc.norm_freq
-0.1
>>> ddc.rate
0.25
function ddc_destroy¶
Free all resources held by a DDC instance. Releases the RateConverter and LO substructures, then the struct itself. Passing NULL is a no-op.
>>> from doppler.ddc import DDC
>>> ddc = DDC(norm_freq=0.0, rate=0.25)
>>> ddc.destroy() # releases C memory immediately
function ddc_execute¶
Mix and resample a block of CF32 samples. Multiplies each input sample by the current LO phasor (advancing the NCO phase per sample), then feeds the mixed block into the RateConverter. The resampler maintains history across calls, so arbitrary block sizes produce contiguous output with no edge artefacts. Output length ≈ x_len * rate (varies by ±1 due to polyphase indexing).
size_t ddc_execute (
ddc_state_t * state,
const float complex * x,
size_t x_len,
float complex * out,
size_t max_out
)
Parameters:
stateMust be non-NULL.xCF32 input block; accepted as float32 (auto-cast).x_lenNumber of input samples (C-only, hidden from Python).outCF32 output buffer (C-only, hidden from Python).max_outOutput buffer capacity (C-only, hidden from Python).
Returns:
Number of output samples written (C-only).
>>> from doppler.ddc import DDC
>>> import numpy as np
>>> ddc = DDC(norm_freq=-0.1, rate=0.25)
>>> t = np.arange(4096)
>>> x = np.exp(1j * 2 * np.pi * 0.1 * t).astype(np.complex64)
>>> y = ddc.execute(x)
>>> y.shape
(1024,)
>>> y.dtype
dtype('complex64')
>>> round(float(abs(y[500])), 2) # shifted to DC; amplitude ≈ 1
1.0
function ddc_execute_max_out¶
Return the maximum output samples for one execute call.
Returns 0, signalling the Python extension to fall back to allocating n_in samples — always sufficient for a decimating DDC.
function ddc_get_norm_freq¶
Return the current LO normalised frequency (cycles/sample).
function ddc_get_rate¶
Return the configured output/input rate ratio (read-only). The rate is fixed at create time; change it by destroying and recreating the DDC with the new value.
function ddc_get_state¶
Serialize state's LO + RateConverter state intoblob .
function ddc_reset¶
Zero LO phase and resampler history. After reset, the next execute call produces the same output as the first execute after create — useful for reproducible block-by-block processing or looped test fixtures.
>>> from doppler.ddc import DDC
>>> import numpy as np
>>> ddc = DDC(norm_freq=0.0, rate=0.25)
>>> x = np.ones(64, dtype=np.complex64)
>>> y1 = ddc.execute(x)
>>> ddc.reset()
>>> y2 = ddc.execute(x)
>>> bool(np.array_equal(y1, y2))
True
function ddc_run¶
Pure run: (state_in, input) -> (state_out, output) ; either blob may be NULL (NULL in = current; NULL out = discard).
size_t ddc_run (
ddc_state_t * state,
const void * state_in,
void * state_out,
const float complex * in,
size_t n_in,
float complex * out,
size_t max_out
)
function ddc_set_norm_freq¶
Retune the LO without resetting phase or resampler history. Updates the NCO phase increment atomically so the carrier shift changes seamlessly across block boundaries. The resampler history and LO phase accumulator are left intact, avoiding the transient that a full reset would cause.
Parameters:
stateMust be non-NULL.valNew normalised frequency (cycles/sample at input rate).
>>> from doppler.ddc import DDC
>>> ddc = DDC(norm_freq=-0.1, rate=0.25)
>>> ddc.norm_freq = -0.2
>>> ddc.norm_freq
-0.2
function ddc_set_state¶
Restore LO + RateConverter state from blob .
Returns:
DP_OK, or DP_ERR_INVALID if the envelope/rate rejects.
function ddc_state_bytes¶
Byte size of state's blob (envelope + extra + lo + rc).
function ddcr_create¶
Create a real-input Digital Down-Converter (Architecture D2). The signal chain is: halfband R2C (2:1, bakes in +fs/4 shift) → fine LO mix at the intermediate rate (fs_in/2) → RateConverter → CF32 output. The halfband stage uses ±1/0 coefficients (no multiplications), making DDCR roughly 2× cheaper than DDC at the same total decimation ratio.
Parameters:
norm_freqFine NCO frequency at the intermediate rate (fs_in/2, cycles/sample). To tune a real tone at normalised input frequency f_c to DC, set norm_freq = -(2*f_c + 0.5).rateTotal output/input rate. Must be in (0, 0.5) because the halfband pre-decimates by 2.
Returns:
Non-NULL on success, NULL on OOM or invalid args.
>>> from doppler.ddc import Ddcr
>>> ddcr = Ddcr(norm_freq=-0.7, rate=0.25)
>>> ddcr.norm_freq
-0.7
>>> ddcr.rate
0.25
function ddcr_destroy¶
Free all resources held by a DDCR instance. Releases the halfband, RateConverter, and LO substructures, then the struct itself. Passing NULL is a no-op.
>>> from doppler.ddc import Ddcr
>>> ddcr = Ddcr(norm_freq=0.0, rate=0.25)
>>> ddcr.close() # releases C memory immediately
function ddcr_execute¶
Process a block of real float32 samples through the full DDCR signal chain: halfband R2C → LO mix → RateConverter → CF32. The halfband decimates by 2 and applies a built-in +fs/4 frequency shift; the fine NCO then completes the tuning. State is maintained across calls for contiguous streaming. Output length ≈ n_in * rate (±1 from polyphase indexing). A real tone at input normalised frequency f_c has amplitude 0.5 in the baseband output (one-sided spectrum), consistent with analytic signal theory.
size_t ddcr_execute (
ddcr_state_t * s,
const float * in,
size_t n_in,
float _Complex * out,
size_t max_out
)
Parameters:
sMust be non-NULL.inReal float32 input block.n_inNumber of input samples (C-only, hidden from Python).outCF32 output buffer (C-only, hidden from Python).max_outOutput buffer capacity (C-only, hidden from Python).
Returns:
Number of output samples written (C-only).
>>> from doppler.ddc import Ddcr
>>> import numpy as np
>>> ddcr = Ddcr(norm_freq=-0.7, rate=0.25)
>>> t = np.arange(4096)
>>> x = np.cos(2 * np.pi * 0.1 * t).astype(np.float32)
>>> out = np.empty(len(x), dtype=np.complex64)
>>> y = ddcr.execute(x, out)
>>> y.shape
(1024,)
>>> y.dtype
dtype('complex64')
>>> round(float(abs(y[500])), 2) # one-sided cosine amplitude ≈ 0.5
0.5
function ddcr_get_norm_freq¶
Return the current fine NCO normalised frequency at the intermediate rate (fs_in/2, cycles/sample).
function ddcr_get_rate¶
Return the total configured rate (fs_out / fs_in, read-only). This is the end-to-end ratio from ADC input to CF32 output. Change it by destroying and recreating the DDCR.
function ddcr_get_state¶
Serialize s's full-chain state intoblob .
function ddcr_reset¶
Zero halfband filter history, LO phase, and resampler history. After reset, the next execute call reproduces the output of the first call after create, enabling repeatable block-by-block tests.
>>> from doppler.ddc import Ddcr
>>> import numpy as np
>>> ddcr = Ddcr(norm_freq=0.0, rate=0.25)
>>> x = np.ones(64, dtype=np.float32)
>>> out = np.empty(64, dtype=np.complex64)
>>> y1 = ddcr.execute(x, out).copy()
>>> ddcr.reset()
>>> y2 = ddcr.execute(x, out)
>>> bool(np.array_equal(y1, y2))
True
function ddcr_run¶
Pure run: inject state_in , processin , exportstate_out —(state_in, input) -> (state_out, output) over an engine treated as immutable config. Either state may be NULL (NULL in = use current; NULL out = discard).state_in /state_out may alias.
size_t ddcr_run (
ddcr_state_t * s,
const void * state_in,
void * state_out,
const float * in,
size_t n_in,
float _Complex * out,
size_t max_out
)
Returns:
Number of CF32 output samples written.
function ddcr_set_norm_freq¶
Retune the fine NCO without resetting halfband or resampler history. Updates the LO phase increment only; state is preserved for seamless tuning across block boundaries.
Parameters:
sMust be non-NULL.norm_freqNew frequency at the intermediate rate (fs_in/2).
>>> from doppler.ddc import Ddcr
>>> ddcr = Ddcr(norm_freq=-0.7, rate=0.25)
>>> ddcr.norm_freq = -0.5
>>> ddcr.norm_freq
-0.5
function ddcr_set_state¶
Restore full-chain state from blob intos .
Returns:
DP_OK, or DP_ERR_INVALID if the envelope/rate disagree with s (rebuild the engine from the matching descriptor first).
function ddcr_state_bytes¶
Byte size of s's state blob (envelope + extra + chain).
Macro Definition Documentation¶
define DDCR_STATE_MAGIC¶
define DDCR_STATE_VERSION¶
define DDC_STATE_MAGIC¶
define DDC_STATE_VERSION¶
The documentation for this class was generated from the following file native/inc/ddc/ddc_core.h