Adaptive notch tracking

Tutorial goal

Track and suppress a sinusoidal interferer with a stable second-order notch model.

Note

New to the terminology? See the lattice DSP concept map and the causality/data-use guide for how online, offline, block, and MIMO examples should be read.

Context

Notch filters are a compact way to remove narrowband interference. This tutorial uses a small adaptive example to show how a stable recursive structure can follow an interfering tone.

Key idea and equations

A second-order notch has a pair of zeros near the interference frequency and stable poles whose radius controls bandwidth.

How to read the result

Inspect the estimated notch frequency and the before/after error or suppression metric.

Run command

python examples/adaptive_notch_tracking.py

Run status

Return code: 0

Captured stdout

true frequency [Hz]:      1240.0
estimated frequency [Hz]: 1238.68
input RMS:                0.7088300560372145
output RMS:               0.12923913178119548

Source code

 1"""Adaptive notch tracking demo.
 2
 3Run after installing the package:
 4    python examples/adaptive_notch_tracking.py
 5"""
 6
 7import numpy as np
 8
 9from lattice_dsp import AdaptiveNotch
10
11rng = np.random.default_rng(123)
12fs = 8_000
13n = np.arange(8_000)
14theta_true = 0.31 * np.pi
15frequency_hz = theta_true * fs / (2 * np.pi)
16
17x = np.sin(theta_true * n) + 0.05 * rng.normal(size=n.size)
18notch = AdaptiveNotch(theta=0.8, pole_radius=0.98, mu=0.005)
19y = notch.process(x)
20
21print("true frequency [Hz]:     ", round(frequency_hz, 2))
22print("estimated frequency [Hz]:", round(notch.theta * fs / (2 * np.pi), 2))
23print("input RMS:               ", float(np.sqrt(np.mean(x * x))))
24print("output RMS:              ", float(np.sqrt(np.mean(y[-2000:] * y[-2000:]))))