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//! | [`sir`] | the signal-to-interference ratio of any multicarrier scheme with a one-tap equaliser over a doubly-selective WSSUS channel with Jakes' Doppler, in closed form from its pulses (Nissel, Schwarz & Rupp 2017) |
16//! | [`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) |
17//! | [`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 |
18//!
19//! # Conventions
20//!
21//! A modem is a configuration with public fields, built by a constructor (`new`, or
22//! `Css::lora`), changed by struct update and checked by `check` when it is used, with
23//! two exceptions: FBMC samples its prototype once in `new` and reads its fields through
24//! getters, and CPM tabulates its phase response in `CpmConfig::build`. Every modem
25//! implements `Modulate` and `Demodulate` of `comnum-core`, at its `sample_rate` (1 Hz by
26//! default, time in samples), and refuses an input at another rate: those that map
27//! complex symbols to samples (OFDM, UFMC, OTFS, CE-OFDM, FBMC) on `Symbols`, CSS and CPM,
28//! which carry the index of a symbol, on `Labels` (`Modulate<Labels>`,
29//! `docs/adr/0012-index-symbol-waveforms.md`). The vocabulary is shared: `n_fft` the size of the transform (the
30//! subcarriers of the grid), `n_active` the modulated subcarriers, `n_cp` the cyclic
31//! prefix, `block_len()` the samples of a block, prefix included. `oversampling` is the
32//! number of samples per Nyquist-rate sample (OFDM, its prefix counted at the Nyquist
33//! rate) or per chip (CSS); CE-OFDM folds it into its DFT size and counts its prefix in
34//! samples of that DFT. The channels the waveforms cross are in `comnum-channel`.
35//!
36//! Single-carrier waveforms are planned (see `docs/ARCHITECTURE.md`).
37
38pub mod ce_ofdm;
39pub mod cpm;
40pub mod css;
41pub mod fbmc;
42pub mod ofdm;
43pub mod otfs;
44pub mod papr;
45pub mod sir;
46pub mod ufmc;
47
48use comnum_core::signal::{Signal, Symbols};
49use comnum_core::units::Hertz;
50use comnum_core::{Result, ensure};
51use comnum_math::C64;
52
53/// `Ok` for a sample rate that is positive and finite.
54pub(crate) fn check_rate(rate: Hertz) -> Result<()> {
55 ensure(rate.0 > 0.0 && rate.0.is_finite(), || format!("the sample rate must be positive, got {} Hz", rate.0))
56}
57
58/// `Ok` if the signal `y` has one stream at the sample rate `rate` of the modem `what`:
59/// the input check of the `Demodulate` blocks.
60pub(crate) fn check_input(y: &Signal, rate: Hertz, what: &str) -> Result<()> {
61 ensure(y.n_streams() == 1, || format!("{what} demodulates one stream, not {}", y.n_streams()))?;
62 ensure(y.sample_rate() == rate, || {
63 format!("a signal at {} Hz for a {what} demodulator at {} Hz", y.sample_rate().0, rate.0)
64 })
65}
66
67/// The one stream of `x`, the input of a single-stream modulator `what` (an error with
68/// several streams): the input check of the `Modulate` blocks.
69pub(crate) fn one_stream<'a>(x: &'a Symbols, what: &str) -> Result<&'a [C64]> {
70 ensure(x.n_streams() == 1, || format!("{what} modulates one stream, not {}", x.n_streams()))?;
71 Ok(x.stream(0))
72}