Skip to content

File nco_core.h

FileList > inc > nco > nco_core.h

Go to the source code of this file

Pure 32-bit phase-accumulator NCO. More...

  • #include "clib_common.h"
  • #include "dp_state.h"
  • #include "jm_perf.h"

Classes

Type Name
struct nco_state_t
NCO state.

Public Functions

Type Name
nco_state_t * nco_create (double norm_freq, uint32_t nmax)
Create an NCO instance. Allocates and initialises the phase accumulator to zero, converts norm_freq to the integer phase_inc = floor(frac(norm_freq) × 2^32), and stores nmax for scaled output. The NCO is immediately ready to call nco_steps_u32 / nco_steps_u32_scaled / nco_steps_u32_ovf.
void nco_destroy (nco_state_t * state)
double nco_get_norm_freq (const nco_state_t * state)
Normalised frequency (read/write). Setting norm_freq recomputes phase_inc = floor(frac(v) × 2^32) and takes effect on the next nco_steps_* call; phase is NOT reset.
uint32_t nco_get_phase (const nco_state_t * state)
Current phase accumulator value (read/write). Reading returns the current integer phase in [0, 2^32) . Writing overrides the accumulator directly, allowing arbitrary phase offsets without re-creating the NCO.
uint32_t nco_get_phase_inc (const nco_state_t * state)
Per-sample phase increment (read-only). Derived from norm_freq as floor(frac(norm_freq) × 2^32). Updated automatically whenever norm_freq is written. A freq of 0.25 gives phase_inc = 1073741824 (0x40000000).
void nco_get_state (const nco_state_t * state, void * blob)
Serialize the phase accumulator into blob .
void nco_reset (nco_state_t * state)
Zero the phase accumulator. Sets phase to 0 so the next nco_steps_u32 call starts from the beginning of the cycle. norm_freq, phase_inc, and nmax are unchanged; the NCO is ready to generate samples again immediately.
void nco_set_norm_freq (nco_state_t * state, double norm_freq)
void nco_set_phase (nco_state_t * state, uint32_t phase)
int nco_set_state (nco_state_t * state, const void * blob)
Restore phase; DP_OK, or DP_ERR_INVALID if the envelope rejects.
size_t nco_state_bytes (const nco_state_t * state)
Serialized-state byte size.
size_t nco_steps_u32 (nco_state_t * state, size_t n, uint32_t * out)
Advance n samples; write raw uint32 accumulator values. Each element is the phase value BEFORE the increment fires, so out[0] is the phase at the moment of the call. The accumulator wraps silently at 2^32, giving the full-resolution integer ramp that the scaled and carry variants derive from. Returns n.
size_t nco_steps_u32_max_out (nco_state_t * state)
Maximum samples per call (determines pre-allocated buffer size).
size_t nco_steps_u32_ovf (nco_state_t * state, size_t n, uint32_t * out, uint8_t * out1)
Advance n samples; write raw phase values and per-sample carry. Identical to nco_steps_u32 for the phase array, but simultaneously fills a parallel uint8 carry buffer: out1[i] is 1 if the add that producedout[i] 's post-increment phase wrapped past 2^32, else 0. The carry marks the exact boundary of one input period and is the primitive for polyphase sample-clock and rational resampling engines. Returns n.
size_t nco_steps_u32_ovf_max_out (nco_state_t * state)
size_t nco_steps_u32_scaled (nco_state_t * state, size_t n, uint32_t * out)
Advance n samples; values scaled to [0, nmax) . Uses the branchless fixed-point identityout[i] = (uint64_t)phase * nmax >> 32 to map the full accumulator range uniformly onto [0, nmax) without a modulo operation. When nmax == 0 falls back to the raw accumulator (identical to nco_steps_u32). Useful for polyphase filter bank indexing and direct LUT addressing. Returns n.
size_t nco_steps_u32_scaled_max_out (nco_state_t * state)

Public Static Functions

Type Name
uint8_t nco_add_ovf_ (uint32_t a, uint32_t b, uint32_t * res)
Wrapping add with carry detection.

Macros

Type Name
define NCO_ADD_OVF (a, b, res) nco\_add\_ovf\_ ((a), (b), (res))
define NCO_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('N', 'C', 'O', '\_')
define NCO_STATE_VERSION 1u

Detailed Description

Implements a numerically-controlled oscillator whose 32-bit phase register advances by phase_inc every sample and wraps naturally at 2^32, giving exact integer arithmetic with no floating-point drift. Three output mappings expose different views of the accumulator:

nco_steps_u32 raw accumulator value [0, 2^32) nco_steps_u32_scaled (uint64)phase * nmax >> 32 → [0, nmax) nco_steps_u32_ovf raw phase + per-sample carry flag

nmax=0 in nco_steps_u32_scaled is treated identically to nco_steps_u32 (returns raw accumulator unchanged).

Normalised-frequency → phase_inc conversion: phase_inc = floor((norm_freq mod 1.0) × 2^32)

Negative frequencies fold correctly: −0.25 → phase_inc = 3×2^30.

reset() zeroes phase only; norm_freq and nmax are unchanged.

Lifecycle: nco_create → (steps / reset)* → nco_destroy

nco_state_t *nco = nco_create(0.25, 0);
uint32_t out[4];
nco_steps_u32(nco, 4, out);
// out[0]=0x00000000, out[1]=0x40000000,
// out[2]=0x80000000, out[3]=0xC0000000
nco_destroy(nco);

Public Functions Documentation

function nco_create

Create an NCO instance. Allocates and initialises the phase accumulator to zero, converts norm_freq to the integer phase_inc = floor(frac(norm_freq) × 2^32), and stores nmax for scaled output. The NCO is immediately ready to call nco_steps_u32 / nco_steps_u32_scaled / nco_steps_u32_ovf.

nco_state_t * nco_create (
    double norm_freq,
    uint32_t nmax
) 

Parameters:

  • norm_freq Normalised frequency in cycles per sample. Any real value; only the fractional part matters. Negative values fold correctly (−0.25 → 3×2^30).
  • nmax Wrap target for nco_steps_u32_scaled. Pass 0 to return the raw 32-bit accumulator.

Returns:

Heap-allocated state, or NULL on allocation failure.

>>> from doppler.source import NCO
>>> nco = NCO(norm_freq=0.25, nmax=0)
>>> nco.phase_inc
1073741824


function nco_destroy

void nco_destroy (
    nco_state_t * state
) 

Free all resources. May be NULL (no-op).


function nco_get_norm_freq

Normalised frequency (read/write). Setting norm_freq recomputes phase_inc = floor(frac(v) × 2^32) and takes effect on the next nco_steps_* call; phase is NOT reset.

double nco_get_norm_freq (
    const nco_state_t * state
) 

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 0)
>>> nco.norm_freq
0.25
>>> nco.norm_freq = 0.5
>>> nco.phase_inc
2147483648

function nco_get_phase

Current phase accumulator value (read/write). Reading returns the current integer phase in [0, 2^32) . Writing overrides the accumulator directly, allowing arbitrary phase offsets without re-creating the NCO.

uint32_t nco_get_phase (
    const nco_state_t * state
) 

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 0)
>>> nco.phase
0
>>> nco.phase = 2147483648
>>> nco.phase
2147483648

function nco_get_phase_inc

Per-sample phase increment (read-only). Derived from norm_freq as floor(frac(norm_freq) × 2^32). Updated automatically whenever norm_freq is written. A freq of 0.25 gives phase_inc = 1073741824 (0x40000000).

uint32_t nco_get_phase_inc (
    const nco_state_t * state
) 

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 0)
>>> nco.phase_inc
1073741824

function nco_get_state

Serialize the phase accumulator into blob .

void nco_get_state (
    const nco_state_t * state,
    void * blob
) 


function nco_reset

Zero the phase accumulator. Sets phase to 0 so the next nco_steps_u32 call starts from the beginning of the cycle. norm_freq, phase_inc, and nmax are unchanged; the NCO is ready to generate samples again immediately.

void nco_reset (
    nco_state_t * state
) 

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 0)
>>> _ = nco.steps_u32(2)
>>> nco.phase
2147483648
>>> nco.reset()
>>> nco.phase
0
>>> nco.norm_freq
0.25

function nco_set_norm_freq

void nco_set_norm_freq (
    nco_state_t * state,
    double norm_freq
) 

function nco_set_phase

void nco_set_phase (
    nco_state_t * state,
    uint32_t phase
) 

function nco_set_state

Restore phase; DP_OK, or DP_ERR_INVALID if the envelope rejects.

int nco_set_state (
    nco_state_t * state,
    const void * blob
) 


function nco_state_bytes

Serialized-state byte size.

size_t nco_state_bytes (
    const nco_state_t * state
) 


function nco_steps_u32

Advance n samples; write raw uint32 accumulator values. Each element is the phase value BEFORE the increment fires, so out[0] is the phase at the moment of the call. The accumulator wraps silently at 2^32, giving the full-resolution integer ramp that the scaled and carry variants derive from. Returns n.

size_t nco_steps_u32 (
    nco_state_t * state,
    size_t n,
    uint32_t * out
) 

Parameters:

  • state NCO state returned by nco_create().
  • n Number of samples to generate.
  • out Output buffer; must hold at least n uint32_t values.

Returns:

n (always).

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 0)
>>> out = nco.steps_u32(4)
>>> out.dtype
dtype('uint32')
>>> out.tolist()
[0, 1073741824, 2147483648, 3221225472]


function nco_steps_u32_max_out

Maximum samples per call (determines pre-allocated buffer size).

size_t nco_steps_u32_max_out (
    nco_state_t * state
) 

The Python extension pre-allocates output buffers of this size at create time. Requesting more samples per call is undefined behaviour.


function nco_steps_u32_ovf

Advance n samples; write raw phase values and per-sample carry. Identical to nco_steps_u32 for the phase array, but simultaneously fills a parallel uint8 carry buffer: out1[i] is 1 if the add that producedout[i] 's post-increment phase wrapped past 2^32, else 0. The carry marks the exact boundary of one input period and is the primitive for polyphase sample-clock and rational resampling engines. Returns n.

size_t nco_steps_u32_ovf (
    nco_state_t * state,
    size_t n,
    uint32_t * out,
    uint8_t * out1
) 

Parameters:

  • state NCO state returned by nco_create().
  • n Number of samples to generate.
  • out Phase output buffer; must hold at least n uint32_t values.
  • out1 Carry output buffer; must hold at least n uint8_t values.

Returns:

n (always).

>>> from doppler.source import NCO
>>> nco = NCO(0.5, 0)
>>> ph, carry = nco.steps_u32_ovf(4)
>>> ph.tolist()
[0, 2147483648, 0, 2147483648]
>>> carry.tolist()
[0, 1, 0, 1]
>>> carry.dtype
dtype('uint8')


function nco_steps_u32_ovf_max_out

size_t nco_steps_u32_ovf_max_out (
    nco_state_t * state
) 

function nco_steps_u32_scaled

Advance n samples; values scaled to [0, nmax) . Uses the branchless fixed-point identityout[i] = (uint64_t)phase * nmax >> 32 to map the full accumulator range uniformly onto [0, nmax) without a modulo operation. When nmax == 0 falls back to the raw accumulator (identical to nco_steps_u32). Useful for polyphase filter bank indexing and direct LUT addressing. Returns n.

size_t nco_steps_u32_scaled (
    nco_state_t * state,
    size_t n,
    uint32_t * out
) 

Parameters:

  • state NCO state returned by nco_create().
  • n Number of samples to generate.
  • out Output buffer; must hold at least n uint32_t values.

Returns:

n (always).

>>> from doppler.source import NCO
>>> nco = NCO(0.25, 4)
>>> out = nco.steps_u32_scaled(4)
>>> out.dtype
dtype('uint32')
>>> out.tolist()
[0, 1, 2, 3]


function nco_steps_u32_scaled_max_out

size_t nco_steps_u32_scaled_max_out (
    nco_state_t * state
) 

Public Static Functions Documentation

function nco_add_ovf_

Wrapping add with carry detection.

static inline uint8_t nco_add_ovf_ (
    uint32_t a,
    uint32_t b,
    uint32_t * res
) 

NCO_ADD_OVF(a, b, res) computes *res = a + b and returns 1 if the addition wrapped (carry out), 0 otherwise. Branchless on x86/AArch64.


Macro Definition Documentation

define NCO_ADD_OVF

#define NCO_ADD_OVF (
    a,
    b,
    res
) `nco_add_ovf_ ((a), (b), (res))`

define NCO_STATE_MAGIC

#define NCO_STATE_MAGIC `DP_FOURCC ('N', 'C', 'O', '_')`

define NCO_STATE_VERSION

#define NCO_STATE_VERSION `1u`


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