File wfm_dsp.h¶
FileList > inc > wfm > wfm_dsp.h
Go to the source code of this file
DSSS spreading + root-raised-cosine pulse shaping (Phase B). More...
#include "clib_common.h"#include <math.h>
Public Functions¶
| Type | Name |
|---|---|
| size_t | wfm_cont_dsss_chips (const uint8_t * code, size_t code_len, const uint8_t * data, size_t n_data, double chips_per_symbol, size_t n_chips, uint8_t * out) Build a CONTINUOUS, ASYNCHRONOUS DSSS chip pattern. |
| void | wfm_dsss_spread (const float _Complex * syms, size_t n_sym, const uint8_t * code, size_t sf, float _Complex * out) Spread n_sym complex data symbols by a binary PN code. |
| size_t | wfm_frame_dsss_chips (const uint8_t * acq_code, size_t acq_len, size_t acq_reps, const uint8_t * data_code, size_t data_len, const uint8_t * sync, size_t sync_len, const uint8_t * payload, size_t payload_len, int crc, uint8_t * out) Build a two-code DSSS burst as one flat 0/1 chip pattern. |
| size_t | wfm_frame_dsss_nchips (size_t acq_len, size_t acq_reps, size_t data_len, size_t sync_len, size_t payload_len, int crc) Chip count of a DSSS burst frame (sizes wfm_frame_dsss_chips ). |
| void | wfm_polyphase_bank (const float * proto, size_t proto_len, size_t num_phases, size_t num_taps, float * bank) Deal an arbitrary FIR prototype into a polyphase interpolation bank. |
| void | wfm_rrc_polyphase_bank (double beta, int sps, int span, float * bank) Decompose the RRC pulse shape into a polyphase interpolation bank. |
| void | wfm_rrc_taps (double beta, int sps, int span, float * taps) Fill taps with a unit-energy root-raised-cosine impulse response. |
Public Static Functions¶
| Type | Name |
|---|---|
| size_t | wfm_cont_dsss_nchips (size_t n_chips) Chip count for wfm_cont_dsss_chips : exactlyn_chips . |
| double | wfm_rc_h (double t, double beta) The MATCHED pair's composite pulse: rrc * rrc , in closed form. |
| size_t | wfm_rrc_bank_ntaps (int span) Number of taps per phase in a wfm_rrc_polyphase_bank :2*span + 1 . |
| double | wfm_rrc_h (double t, double beta) Analytic root-raised-cosine impulse response at one instant. |
| size_t | wfm_rrc_ntaps (int sps, int span) Number of taps a wfm_rrc_taps call produces:2*span*sps + 1 . |
Macros¶
| Type | Name |
|---|---|
| define | M_PI 3.14159265358979323846 |
Detailed Description¶
Two pure DSP primitives the engine/composer use to build spread-spectrum and band-limited waveforms: * wfm_dsss_spread: multiply each data symbol by a PN chip code. * wfm_rrc_taps: a unit-energy root-raised-cosine FIR (matched-filter pulse shape), applied by upsample + FIR.
Public Functions Documentation¶
function wfm_cont_dsss_chips¶
Build a CONTINUOUS, ASYNCHRONOUS DSSS chip pattern.
size_t wfm_cont_dsss_chips (
const uint8_t * code,
size_t code_len,
const uint8_t * data,
size_t n_data,
double chips_per_symbol,
size_t n_chips,
uint8_t * out
)
The continuous counterpart to wfm_frame_dsss_chips. Two differences, both required by a continuously-transmitting spread carrier (CCSDS command-link style) rather than a bounded burst:
- Continuous: no preamble, no sync word, no CRC. The spreading code repeats end to end and data rides on it the whole way.
- Asynchronous: the data-symbol clock is independent of the code epoch, so
chips_per_symbolis a non-integerdoubleand symbol boundaries land inside code epochs. The burst builder spreads exactly one bit per full code period — synchronous by construction, integer always.
Chip i carries code[i % code_len] ^ data[floor(i / chips_per_symbol)], so both clocks advance independently off the same chip index. Because the symbol index is a floor of a fractional quotient, consecutive symbols legitimately span different numbers of chips (1136 or 1137 at SPEC.md's 3.069 Mcps / 2700 bps) — that jitter IS the asynchronicity, not an artifact.
Materialising the pattern up front, exactly as the burst builder does, is what lets the synth's existing cyclic chip latch play it back unchanged: no new per-sample branch, no new running state, no serialization change.
Parameters:
codespreading code chips (0/1), lengthcode_len.code_lenspreading code length in chips (> 0).datadata bits (0/1), lengthn_data; cycled if exhausted.n_datadata bit count (> 0).chips_per_symbolchips per data symbol (> 0, typically non-integer).n_chipschips to produce (the caller's requested span).outoutput chip array (0/1) ofn_chipselements.
Returns:
Chips written (== n_chips), or 0 on invalid geometry.
function wfm_dsss_spread¶
Spread n_sym complex data symbols by a binary PN code.
void wfm_dsss_spread (
const float _Complex * syms,
size_t n_sym,
const uint8_t * code,
size_t sf,
float _Complex * out
)
out[i*sf + j] = syms[i] * (code[j] ? -1 : +1) — each symbol is repeated across sf chips, sign-flipped per code chip. Output length is n_sym*sf. Works for BPSK (real syms) and QPSK (complex syms).
Parameters:
symscomplex data symbols;n_symtheir count.codePN chip code (0/1), lengthsf;sfspreading factor.outoutput chips, lengthn_sym * sf.
function wfm_frame_dsss_chips¶
Build a two-code DSSS burst as one flat 0/1 chip pattern.
size_t wfm_frame_dsss_chips (
const uint8_t * acq_code,
size_t acq_len,
size_t acq_reps,
const uint8_t * data_code,
size_t data_len,
const uint8_t * sync,
size_t sync_len,
const uint8_t * payload,
size_t payload_len,
int crc,
uint8_t * out
)
The transmit side of burst_demod's frame contract, assembled in one place so TX and RX can never drift:
[ acq_code × acq_reps | (sync | payload | crc16(payload)) ⊕ data_code ]
The preamble is the unmodulated repeated acquisition code (no data on it — a pure coherent-integration target). Every frame bit is then spread by the (distinct) data code: chip j of frame bit b is b ^ data_code[j]. The CRC-16-CCITT trailer (dp_crc16.h) is computed over the payload bits only and spread MSB-first. Mapping chips to ±1 (BPSK) is the synth's job.
Parameters:
acq_codepreamble code (0/1), lengthacq_len; NULL whenacq_len*acq_reps == 0.acq_lenpreamble code length in chips.acq_repspreamble repetitions.data_codepayload spreading code (0/1), lengthdata_len.data_lenchips per frame symbol (the spreading factor).syncframe-sync word bits (0/1), lengthsync_len; NULL ok.sync_lensync word length in bits.payloadpayload bits (0/1), lengthpayload_len; NULL ok.payload_lenpayload length in bits.crcnon-zero: append the CRC-16 trailer after the payload.outoutput chip array (0/1) ofwfm_frame_dsss_nchips(...)elements.
Returns:
Chips written, or 0 on invalid geometry (see wfm_frame_dsss_nchips).
function wfm_frame_dsss_nchips¶
Chip count of a DSSS burst frame (sizes wfm_frame_dsss_chips ).
size_t wfm_frame_dsss_nchips (
size_t acq_len,
size_t acq_reps,
size_t data_len,
size_t sync_len,
size_t payload_len,
int crc
)
acq_len*acq_reps + (sync_len + payload_len + crc_bits) * data_len, where crc_bits is 16 when crc is set and there are payload bits, else 0 (a CRC over nothing protects nothing). Returns 0 when the geometry is invalid: frame bits present but no data code, or nothing to transmit at all.
Parameters:
acq_lenpreamble code length in chips (0 = no preamble).acq_repspreamble repetitions (0 = no preamble).data_lenpayload spreading-code length (chips per symbol).sync_lenframe-sync word length in bits (0 = none).payload_lenpayload length in bits.crcnon-zero: a CRC-16 trailer follows the payload.
Returns:
Total burst chips, or 0 if the geometry is invalid/empty.
function wfm_polyphase_bank¶
Deal an arbitrary FIR prototype into a polyphase interpolation bank.
void wfm_polyphase_bank (
const float * proto,
size_t proto_len,
size_t num_phases,
size_t num_taps,
float * bank
)
The pure decomposition shared by every polyphase-bank builder: phase p gets the prototype taps that land on output samples of residue p, so bank[p*num_taps + t] = proto[t*num_phases + p] (zero-padded past proto_len). Row-major, num_phases * num_taps floats — exactly the layout resamp_create_custom(num_phases, num_taps, bank, rate) consumes. Interpolate an input stream by num_phases (rate = num_phases) with the resulting bank and you recompute the dense proto convolution from only the nonzero upsampled contributions.
Parameters:
protoprototype FIR taps.proto_lennumber of prototype taps.num_phasesinterpolation factor (bank rows).num_tapstaps per phase; must satisfynum_phases * num_taps >= proto_len(use(proto_len + num_phases - 1) / num_phases).bankoutput bank, row-major, lengthnum_phases * num_taps.
function wfm_rrc_polyphase_bank¶
Decompose the RRC pulse shape into a polyphase interpolation bank.
The dense pulse shaper upsamples a symbol stream by sps (one impulse per sps samples, the rest hard zeros) then runs the full wfm_rrc_taps FIR over it — (sps-1)/sps of every tap-multiply hits a structural zero. The polyphase form computes the identical convolution from only the nonzero contributions: it splits the length-wfm_rrc_ntaps(sps, span) prototype into sps phases of wfm_rrc_bank_ntaps(span) taps each, so phase p selects the subset of prototype taps that land on output samples of residue p.
The prototype is wfm_rrc_taps(beta, sps, span) scaled by sqrt(sps) — the same unit-average-power scaling wfm_synth_set_rrc applies to the dense taps, folded in here so the two paths shape at byte-comparable amplitude. The row-major layout bank[p*num_taps + t] = proto[t*sps + p] (zero-padded past the final partial tap) is exactly the decomposition resamp's own Kaiser bank uses, so the bank drops straight into resamp_create_custom(sps,
wfm_rrc_bank_ntaps(span), bank, sps) as an interpolate-by-sps shaper.
Unlike resamp's Kaiser prototype (which carries a ×num_phases gain to compensate interpolation energy spreading), the RRC prototype carries no such gain: the interpolate path reproduces the dense FIR output to float precision with the raw scaled taps.
Parameters:
betaroll-off in[0, 1].spssamples per symbol (>= 1); also the number of phases.spanone-sided span in symbols (>= 1).bankoutput bank, row-major, lengthsps * wfm_rrc_bank_ntaps(span).
function wfm_rrc_taps¶
Fill taps with a unit-energy root-raised-cosine impulse response.
Length is wfm_rrc_ntaps(sps, span); the response is symmetric about the centre tap and normalised so sum(taps^2) == 1 (so cascading TX·RX gives a Nyquist raised cosine). The t = 0 and t = ±1/(4β) singularities are handled by their closed-form limits.
Parameters:
betaroll-off in[0, 1].spssamples per symbol (>= 1).spanone-sided span in symbols (>= 1).tapsoutput array of lengthwfm_rrc_ntaps(sps, span).
Public Static Functions Documentation¶
function wfm_cont_dsss_nchips¶
Chip count for wfm_cont_dsss_chips : exactlyn_chips .
Trivial, but present so the two continuous entry points mirror the burst pair (wfm_frame_dsss_nchips / wfm_frame_dsss_chips) and callers size their buffer through a named function rather than an open-coded expression.
function wfm_rc_h¶
The MATCHED pair's composite pulse: rrc * rrc , in closed form.
A root-raised cosine convolved with itself is a raised cosine, so the pulse a matched receiver actually sees needs no convolution and no table — which is what lets a constructor evaluate it. Normalised to g(0) = 1, the level a unity-gain matched cascade delivers (see RateConverter_gain()), so g(t) IS the recovered symbol amplitude at timing offset t.
Nyquist by construction: g(k) = 0 at every non-zero integer k, which is why a timing error and not an amplitude error is what inter-symbol interference looks like here.
The removable singularity at t = ±1/(2β) is handled by its closed-form limit; t = 0 needs none (the sinc limit is taken explicitly).
Parameters:
ttime in SYMBOL periods (T = 1), relative to the pulse centre.betaroll-off in[0, 1].
Returns:
g(t), with g(0) = 1.
function wfm_rrc_bank_ntaps¶
Number of taps per phase in a wfm_rrc_polyphase_bank :2*span + 1 .
Parameters:
spanone-sided filter span in symbols (>= 1).
function wfm_rrc_h¶
Analytic root-raised-cosine impulse response at one instant.
The RRC formula itself, evaluated at an arbitrary continuous time — the single source of truth every RRC consumer samples. wfm_rrc_taps() walks this on the uniform 1/sps grid and normalises; a receiver's polyphase matched-filter bank (RateConverter's pulse-shaped terminal stage) samples it at num_phases * num_taps instants that are NOT a uniform sub-multiple of the input grid, which is why the point evaluator is public: an arbitrary (non-integer) samples-per-symbol bank cannot be built by decomposing an integer-oversampled prototype, and a second copy of this formula is exactly the kind of peer implementation that drifts.
Both removable singularities are handled by their closed-form limits: the 0/0 at t = 0, and the 0/0 at t = ±1/(4β) where the denominator's 1 - (4βt)^2 vanishes.
Parameters:
ttime in SYMBOL periods (T = 1), relative to the pulse centre.betaroll-off in[0, 1].
Returns:
h(t), unnormalised (peak ≈ 1 - β + 4β/π at t = 0).
function wfm_rrc_ntaps¶
Number of taps a wfm_rrc_taps call produces:2*span*sps + 1 .
Parameters:
spssamples per symbol (>= 1).spanone-sided filter span in symbols (>= 1).
Macro Definition Documentation¶
define M_PI¶
The documentation for this class was generated from the following file native/inc/wfm/wfm_dsp.h