Skip to main content

comnum_waveform/
lib.rs

1//! # comnum-waveform — time–frequency structures
2//!
3//! Waveforms carry mapped symbols, so this crate uses `comnum-mapping` (the sibling
4//! dependency allowed by the architecture guide) and, for MIMO-OFDM, the detectors of
5//! `comnum-mimo` (ADR 0009).
6//!
7//! | Module | Content |
8//! |---|---|
9//! | [`ofdm`] | OFDM and DFT-spread OFDM: oversampled modulator, cyclic prefix, demodulator; resource grid and pilots, LS/LMMSE channel estimation, Schmidl & Cox synchronisation, MIMO-OFDM detection |
10//! | [`otfs`] | orthogonal time frequency space: ISFFT and Heisenberg transform with rectangular pulses and one prefix per frame, the sparse effective delay–Doppler channel (Raviteja et al. 2019) |
11//! | [`papr`] | peak-to-average power ratio: theory of its distribution, clipping (Bussgang), and the reduction techniques (clipping and filtering, SLM, PTS, tone reservation, ACE, companding) |
12//! | [`ce_ofdm`] | constant-envelope OFDM (Thompson et al. 2008): modulator, phase demodulator (principal or unwrapped phase), and the error rates of its linear regime |
13//! | [`css`] | chirp spread spectrum (the frequency-shift chirp modulation of LoRa): modulator at any oversampling, noncoherent DFT receiver, exact error rates, cross-correlation, spectrum in Fresnel functions, same-SF interference (Chiani & Elzanaty 2019, Afisiadis et al. 2020) |
14//! | [`fbmc`] | filter-bank multicarrier with offset QAM (FBMC/OQAM): PHYDYAS prototype, polyphase modulator and demodulator, OQAM staggering, intrinsic interference |
15//! | [`ufmc`] | universal filtered multicarrier: OFDM filtered per sub-band by Dolph–Chebyshev filters, no prefix, receiver on the even bins of a DFT of twice the size (Schaich & Wild 2014) |
16//! | [`cpm`] | continuous-phase modulation (CPFSK, MSK, GMSK, raised-cosine pulses): modulator with an exact phase state, coherent maximum-likelihood sequence detector, quadrature detector of MSK, differential detector, the MSK spectrum and the Gaussian pulse in closed form, minimum distance |
17//!
18//! # Conventions
19//!
20//! A modem is a configuration with public fields, built by a constructor (`new`, or
21//! `Css::lora`), changed by struct update and checked by `check` when it is used, with
22//! two exceptions: FBMC samples its prototype once in `new` and reads its fields through
23//! getters, and CPM tabulates its phase response in `CpmConfig::build`. Every modem
24//! implements `Modulate` and `Demodulate` of `comnum-core`, at its `sample_rate` (1 Hz by
25//! default, time in samples), and refuses an input at another rate: those that map
26//! complex symbols to samples (OFDM, UFMC, OTFS, CE-OFDM, FBMC) on `Symbols`, CSS and CPM,
27//! which carry the index of a symbol, on `Labels` (`Modulate<Labels>`,
28//! `docs/adr/0012-index-symbol-waveforms.md`). The vocabulary is shared: `n_fft` the size of the transform (the
29//! subcarriers of the grid), `n_active` the modulated subcarriers, `n_cp` the cyclic
30//! prefix, `block_len()` the samples of a block, prefix included. `oversampling` is the
31//! number of samples per Nyquist-rate sample (OFDM, its prefix counted at the Nyquist
32//! rate) or per chip (CSS); CE-OFDM folds it into its DFT size and counts its prefix in
33//! samples of that DFT. The channels the waveforms cross are in `comnum-channel`.
34//!
35//! Single-carrier waveforms are planned (see `docs/ARCHITECTURE.md`).
36
37pub mod ce_ofdm;
38pub mod cpm;
39pub mod css;
40pub mod fbmc;
41pub mod ofdm;
42pub mod otfs;
43pub mod papr;
44pub mod ufmc;
45
46use comnum_core::signal::{Signal, Symbols};
47use comnum_core::units::Hertz;
48use comnum_core::{Result, ensure};
49use comnum_math::C64;
50
51/// `Ok` for a sample rate that is positive and finite.
52pub(crate) fn check_rate(rate: Hertz) -> Result<()> {
53    ensure(rate.0 > 0.0 && rate.0.is_finite(), || format!("the sample rate must be positive, got {} Hz", rate.0))
54}
55
56/// `Ok` if the signal `y` has one stream at the sample rate `rate` of the modem `what`:
57/// the input check of the `Demodulate` blocks.
58pub(crate) fn check_input(y: &Signal, rate: Hertz, what: &str) -> Result<()> {
59    ensure(y.n_streams() == 1, || format!("{what} demodulates one stream, not {}", y.n_streams()))?;
60    ensure(y.sample_rate() == rate, || {
61        format!("a signal at {} Hz for a {what} demodulator at {} Hz", y.sample_rate().0, rate.0)
62    })
63}
64
65/// The one stream of `x`, the input of a single-stream modulator `what` (an error with
66/// several streams): the input check of the `Modulate` blocks.
67pub(crate) fn one_stream<'a>(x: &'a Symbols, what: &str) -> Result<&'a [C64]> {
68    ensure(x.n_streams() == 1, || format!("{what} modulates one stream, not {}", x.n_streams()))?;
69    Ok(x.stream(0))
70}