Documentation
¶
Overview ¶
Package equalizer implements adaptive channel equalizers used to fight simulcast distortion — the inter-symbol interference produced when multiple transmitters cover the same frequency at slightly different arrival delays at the receiver. Premium hardware scanners market this capability as "True I/Q"; with an SDR we always have I/Q, so the win is what we do with it.
Two complementary algorithms ship here:
lms.go Least-Mean-Squares adaptive FIR equalizer. Trained
with reference (or decision-directed) symbols; fast to
converge but needs a known training sequence (or a
slicer it can trust).
cma.go Constant Modulus Algorithm — blind equalizer for
constant-envelope modulations (PSK family). Drives the
output toward a constant magnitude without ever needing
a reference; useful when the upstream demod has no
preamble to lock to.
The package operates on complex64 IQ samples / symbols (matching the rest of the DSP stack). Equalizers slot between the channelizer and the symbol-time-recovery / demodulator stages of a per-call chain — the demod-pipeline composer is the natural integration point once a protocol decoder needs them on a real signal.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CMA ¶
type CMA struct {
// contains filtered or unexported fields
}
CMA is the Constant Modulus Algorithm — a blind adaptive equaliser that requires no training sequence. It exploits the fact that PSK-family signals (BPSK / QPSK / π/4-DQPSK / 8PSK) have a constant modulus on the air; multipath / simulcast distortion blurs that constant-magnitude property, and CMA drives the output back toward it.
Cost function and gradient (Godard / CMA-2):
J = E[(|y|^2 - R^2)^2] ∂J/∂w* ∝ (|y|^2 - R^2) · y · conj(x) w[n+1] = w[n] - μ · (|y|^2 - R^2) · y · conj(x)
Pick R^2 so the equilibrium weight scaling matches the expected constellation. For unit-magnitude PSK use R^2 = 1; QPSK with Gray-coded ±1±j has |y|^2 = 2 so R^2 = 2 is conventional.
Caveats:
- CMA is phase-blind. After convergence the constellation may sit at any rotation; downstream symbol mapping must apply a constellation-aware phase recovery (a per-constellation rotator using known training symbols, or differential decoding).
- On non-constant-modulus signals (FM, OQPSK, QAM) CMA's cost function isn't zero at the right answer; use LMS in decision-directed mode there.
func NewCMA ¶
NewCMA constructs a blind equaliser. `target` is the desired squared modulus (R^2): use 1.0 for unit-modulus PSK, 2.0 for ±1±j QPSK.
func (*CMA) Process ¶
Process consumes one input sample and returns the equalised output. The error proxy (|y|^2 - R^2) is also returned for diagnostics / convergence-monitoring; once it settles near zero the equaliser has opened the constellation.
type LMS ¶
type LMS struct {
// contains filtered or unexported fields
}
LMS is a complex-valued tapped-delay-line adaptive equalizer trained with the standard Least-Mean-Squares update rule:
y[n] = sum_k w_k(n) * x[n-k] // FIR output e[n] = d[n] - y[n] // training error w[n+1] = w[n] + μ · e[n] · conj(x[n-k]) // weight update
Notes:
- The reference signal d[n] can be a true training preamble or, in decision-directed mode, the slicer's hard decision on y[n].
- μ (StepSize) sets the trade-off between convergence speed and mean-squared-error floor; 0.005 to 0.05 are reasonable starting points for symbol-spaced channels.
- Initialised to a centre spike: w_{N/2}(0) = 1, others zero. That starts the equaliser as a pass-through so a benign channel stays roughly intact while training begins.
The struct is not safe for concurrent use; one equaliser belongs to one demod chain.
func NewLMS ¶
NewLMS constructs an equaliser with `taps` complex weights and the supplied step size. taps must be > 0; an odd taps count is recommended so the centre spike is well-defined.
func (*LMS) Process ¶
Process consumes one input sample x and updates the filter. The `desired` argument is the reference / training symbol; in decision-directed mode supply the upstream slicer's hard decision on the previous output. Returns the equalised output y[n] and the instantaneous error e[n].
func (*LMS) Reset ¶
func (e *LMS) Reset()
Reset returns the equaliser to its centre-spike initial state.
func (*LMS) SetStepSize ¶
SetStepSize updates μ. Larger steps converge faster but settle to a noisier weight vector.