equalizer

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: Apache-2.0 Imports: 0 Imported by: 0

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

func NewCMA(taps int, stepSize, target float32) *CMA

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

func (c *CMA) Process(x complex64) (complex64, float32)

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.

func (*CMA) Reset

func (c *CMA) Reset()

Reset returns the equaliser to centre-spike initial state.

func (*CMA) Taps

func (c *CMA) Taps() []complex64

Taps returns a copy of the current weight vector.

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

func NewLMS(taps int, stepSize float32) *LMS

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

func (e *LMS) Process(x, desired complex64) (complex64, complex64)

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

func (e *LMS) SetStepSize(step float32)

SetStepSize updates μ. Larger steps converge faster but settle to a noisier weight vector.

func (*LMS) Taps

func (e *LMS) Taps() []complex64

Taps returns a copy of the current weight vector. Useful in tests and when an operator wants to inspect what the equaliser has learned (or stash and restore taps across calls on the same channel).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL