Project 2: The Envelope (AM Demodulator)

Build an AM receiver that tunes an IQ file or live stream, extracts the amplitude envelope, and outputs clean audio.

Quick Reference

Attribute Value
Difficulty Intermediate
Time Estimate 2-3 weeks
Main Programming Language Python (Alternatives: C, Rust)
Alternative Programming Languages C++, Julia
Coolness Level Medium-High
Business Potential Medium (communications tooling, education)
Prerequisites FFT basics, IQ handling, digital filters
Key Topics AM envelope detection, baseband shifting, decimation

1. Learning Objectives

By completing this project, you will:

  1. Shift a chosen RF carrier to baseband using complex mixing.
  2. Implement AM demodulation via envelope detection and DC removal.
  3. Design low-pass filters to isolate audio bandwidth.
  4. Perform safe decimation and resampling to audio rates.
  5. Produce a playable WAV file with normalized audio.

2. All Theory Needed (Per-Concept Breakdown)

2.1 AM Modulation and Envelope Detection

Fundamentals

Amplitude Modulation (AM) encodes information by varying the amplitude of a carrier. If the carrier is c(t) = A cos(2πf_ct) and the message is m(t), an AM signal is typically s(t) = (1 + k m(t)) cos(2πf_ct), where k is the modulation index. The key insight is that the information lives in the amplitude envelope of the carrier. If you can recover the envelope, you can recover the message. In an SDR, you can shift the carrier to 0 Hz and compute the magnitude of the complex baseband signal; this magnitude approximates the envelope. Then you remove the DC component and scale the remaining waveform into audio.

AM is sensitive to noise because amplitude is directly affected by channel variation and interference. That is why AM sounds “noisier” than FM. In practice, the envelope of a real AM broadcast is not perfectly flat. It contains the audio signal plus noise and any residual carrier or adjacent channel energy. This is why you must apply a low-pass filter to isolate the audio band (typically up to 5 kHz for AM broadcast).

Envelope detection in SDR is typically done by taking the magnitude of IQ samples: I + jQ . This is equivalent to sqrt(I^2 + Q^2). For performance, you can compute magnitude squared and later apply a scaling that approximates magnitude. The result is a positive-valued waveform. Because AM broadcasts include a carrier, the envelope has a DC offset. Removing that offset is crucial so your audio centers around zero. Once centered, you can normalize to avoid clipping in the WAV output.

Deep Dive into the Concept

AM can be understood as a multiplication of a message by a carrier. In the frequency domain, this produces two sidebands around the carrier frequency. The envelope is the magnitude of the complex analytic signal. In analog radios, a diode and RC circuit serve as a crude envelope detector. In SDR, we can compute the analytic representation directly because IQ sampling already gives us a complex signal. This makes envelope detection precise and easy.

However, raw magnitude is not perfect. If the carrier is not exactly centered at 0 Hz, the magnitude will beat at the residual frequency, producing a low-frequency tone. This is why digital tuning is the first step: you multiply by exp(-j*2πf_shift t) to shift the carrier to baseband, then apply a low-pass filter. If you do not align properly, you get a “whine” in the demodulated audio.

The modulation index k determines how deep the amplitude changes are. If k is too large (>1), the envelope crosses zero and produces distortion (over-modulation). Many AM transmitters limit k to avoid this. In your decoder, if the amplitude is near zero or negative due to noise, the envelope may be noisy. This is not your decoder’s fault; it is a property of AM. Understanding this helps you set realistic expectations.

Another subtlety is AGC (automatic gain control). Many SDR front-ends include AGC modes that change gain based on signal strength. If you enable AGC, the amplitude envelope is distorted, because the radio is altering amplitude to keep the signal within range. That may be fine for listening but makes envelope detection less faithful. For this project, it is often better to set manual gain and keep it fixed.

Finally, AM demodulation also teaches you about linear vs non-linear operations. Taking magnitude is a non-linear operation. It can introduce harmonics or distortions if the signal includes strong adjacent channels. Therefore, it is usually helpful to band-pass filter around the target AM station before envelope detection. This isolates the intended signal and improves audio quality.

How this fits on projects

  • You will apply AM envelope detection in §5.4 (Concepts) and §5.10 (Phases).
  • This concept appears again in P07 (NOAA APT) for subcarrier envelope extraction.

Definitions & key terms

  • AM (Amplitude Modulation): Information encoded in the amplitude of a carrier.
  • Envelope: The slow-varying outline of the signal magnitude.
  • Modulation index: Ratio of message amplitude to carrier amplitude.
  • Over-modulation: Envelope crosses zero, causing distortion.
  • AGC: Automatic gain control, can distort amplitude.

Mental model diagram (ASCII)

Carrier + Message -> AM Signal -> |I + jQ| -> Envelope -> Audio

How it works (step-by-step, with invariants and failure modes)

  1. Shift the carrier to 0 Hz using complex mixing.
  2. Low-pass filter to keep the AM band.
  3. Compute magnitude I + jQ to get envelope.
  4. Remove DC and normalize.
  5. Low-pass to audio bandwidth and resample.

Invariants:

  • Envelope should be positive before DC removal.
  • After DC removal, audio should be centered at 0.

Failure modes:

  • Residual carrier causes audible tone.
  • Over-modulation causes distortion or “crackle.”

Minimal concrete example

# mix to baseband
n = np.arange(len(samples))
shift = np.exp(-1j * 2*np.pi*freq_offset/fs * n)
baseband = samples * shift
# envelope
env = np.abs(baseband)
# remove DC
audio = env - np.mean(env)

Common misconceptions

  • “AM is just magnitude.” You must still tune and filter correctly.
  • “AGC always helps.” AGC can distort the envelope.
  • “Any low-pass filter is fine.” Poor filters cause aliasing on decimation.

Check-your-understanding questions

  1. Why do we remove DC after envelope detection?
  2. What happens if the carrier is not exactly centered?
  3. How does over-modulation affect the envelope?

Check-your-understanding answers

  1. The carrier produces a DC offset that must be removed to center audio.
  2. You hear a low-frequency tone or beat in the audio.
  3. The envelope crosses zero and distorts, producing harsh artifacts.

Real-world applications

  • AM broadcast radio reception.
  • Aircraft NDB (non-directional beacon) monitoring.
  • HF communications.

Where you’ll apply it

  • This project: §3.4 (Example Usage), §5.10 (Phases).
  • Also used in: P07 NOAA APT.

References

  • “Understanding Digital Signal Processing” by Lyons, Chapter 10
  • “Software-Defined Radio for Engineers” by Collins, Chapter 4

Key insights

AM demodulation is envelope extraction after careful tuning and filtering.

Summary

You learned how AM signals encode information in amplitude and how to recover audio via envelope detection.

Homework/Exercises to practice the concept

  1. Generate a synthetic AM signal and verify the envelope matches the message.
  2. Over-modulate the signal and observe distortion in the envelope.
  3. Add noise and see how the envelope degrades.

Solutions to the homework/exercises

  1. The recovered envelope should track the original message with a DC offset.
  2. The envelope clips and crosses zero, creating distortion.
  3. The envelope becomes noisy, showing AM’s sensitivity to noise.

2.2 Low-Pass Filtering, Decimation, and Resampling

Fundamentals

AM broadcast audio bandwidth is small compared to RF bandwidth. After you extract the envelope, you only need audio up to around 5 kHz. If your IQ stream is 2.4 MSPS, you must drastically reduce sample rate. This is done with decimation, but decimation requires filtering first. Without filtering, high-frequency components alias into the audio band, producing whistles or distortion. The standard workflow is: filter -> decimate -> possibly filter again -> resample to audio rate (e.g., 48 kHz).

A low-pass filter in SDR is typically a finite impulse response (FIR) filter designed with a cutoff and transition band. For AM, a cutoff around 5 kHz works well. The transition band depends on your decimation factor. If you decimate by 50, your sample rate drops from 2.4 MHz to 48 kHz; you need the filter to attenuate everything above 24 kHz before decimation. This implies a sharp filter if you decimate aggressively in one step. To reduce CPU load, you can do multi-stage decimation, applying moderate filters at each step.

Resampling is often needed because your decimated rate may not be exactly 48 kHz. If you downsample by 50 from 2.4 MHz, you get 48 kHz exactly. But if your SDR sample rate is 2.048 MHz, dividing by 42.666 is not an integer. In that case, use a rational resampler or scipy’s resample_poly. Understanding how resampling works helps you avoid pitch shifts and artifacts in the audio.

Deep Dive into the Concept

Filtering and decimation are intimately linked. A discrete-time signal sampled at Fs has a Nyquist frequency at Fs/2. When you decimate by M, the new sample rate is Fs/M, and the new Nyquist is Fs/(2M). Any frequency components above that will fold into the baseband. Therefore, you must low-pass filter to keep only what can be represented after decimation.

FIR filters are common in SDR because they are stable and have linear phase. Linear phase means all frequency components are delayed equally, preserving waveform shape. This is important for audio. FIR filters are designed using windowing (e.g., Hamming window) or more advanced methods (equiripple). The filter length (number of taps) controls the sharpness of the transition band. Longer filters give better attenuation but cost more CPU. In a streaming receiver, you must choose a reasonable compromise.

Multi-stage decimation is practical. Suppose you start at 2.4 MSPS. You can decimate by 5 (2.4 MHz -> 480 kHz) with a filter cutoff of ~100 kHz. Then decimate by 5 again (480 kHz -> 96 kHz) with a cutoff of 20 kHz, then resample to 48 kHz. Each stage uses a shorter filter, reducing CPU. This is the same approach used in professional SDRs and digital downconverters.

Resampling introduces its own constraints. A rational resampler (up/down by integers) uses a polyphase filter. It can convert from 96 kHz to 48 kHz (down by 2) easily, but converting from 110.25 kHz to 48 kHz requires a rational ratio. Libraries like scipy provide resample_poly which designs the necessary polyphase filter. You must still choose filter parameters to avoid aliasing and to keep audio fidelity.

Finally, note that filtering is also used to reduce noise. AM is noisy; a filter that cuts above 5 kHz removes high-frequency hiss. But if you cut too low, speech sounds muffled. For music, you might want 8-10 kHz bandwidth. Most AM broadcast channels are 10 kHz wide, so 5 kHz audio is a standard compromise. Build a parameter so you can experiment and hear the difference.

How this fits on projects

  • You will build the decimation chain in §5.10 and verify in §6.
  • The same filtering/resampling approach appears in P03 (FM audio) and P07 (NOAA APT).

Definitions & key terms

  • Decimation: Reducing sample rate by an integer factor.
  • Anti-aliasing filter: Low-pass filter applied before decimation.
  • FIR filter: Finite impulse response filter with linear phase.
  • Resample: Change sample rate by non-integer factor.
  • Transition band: The frequency region between passband and stopband.

Mental model diagram (ASCII)

IQ @ 2.4 MSPS -> LPF -> /5 -> LPF -> /5 -> Resample -> 48 kHz audio

How it works (step-by-step, with invariants and failure modes)

  1. Design a low-pass FIR filter for your target bandwidth.
  2. Filter the envelope output.
  3. Decimate by an integer factor.
  4. Repeat if needed; then resample to audio rate.

Invariants:

  • Filter cutoff must be below new Nyquist after decimation.

Failure modes:

  • Aliasing artifacts (whistles, tones) if filtering is insufficient.
  • Muffled audio if cutoff too low.

Minimal concrete example

from scipy.signal import firwin, lfilter, resample_poly

cutoff = 5000  # Hz
numtaps = 129
fir = firwin(numtaps, cutoff, fs=fs)
filtered = lfilter(fir, 1.0, audio)

audio_48k = resample_poly(filtered, up=1, down=int(fs/48000))

Common misconceptions

  • “Decimation just drops samples.” It must be preceded by a filter.
  • “Short filters are fine.” Short filters can alias and distort.
  • “Any audio rate is okay.” Audio devices expect standard rates.

Check-your-understanding questions

  1. Why must you filter before decimation?
  2. What is the relationship between Fs, decimation factor, and Nyquist?
  3. How does filter length affect transition band width?

Check-your-understanding answers

  1. To prevent high-frequency content from aliasing into the baseband.
  2. New Nyquist = Fs / (2M), where M is the decimation factor.
  3. Longer filters give sharper transitions but cost more computation.

Real-world applications

  • Digital downconversion in SDRs.
  • Audio processing pipelines.
  • Communications receivers.

Where you’ll apply it

References

  • “Understanding Digital Signal Processing” by Lyons, Chapters 11-12
  • “Digital Signal Processing” by Oppenheim, Chapter 7

Key insights

Decimation only works if you remove energy above the new Nyquist limit.

Summary

You learned how filtering, decimation, and resampling preserve audio quality while reducing data rate.

Homework/Exercises to practice the concept

  1. Decimate a 2.4 MSPS signal to 48 kHz in one stage and observe aliasing.
  2. Repeat with two-stage decimation and compare audio quality.
  3. Vary filter length and listen for changes in noise.

Solutions to the homework/exercises

  1. One-stage decimation with a weak filter produces alias tones.
  2. Two-stage decimation gives cleaner audio and better CPU performance.
  3. Longer filters reduce hiss but increase latency.

3. Project Specification

3.1 What You Will Build

An AM demodulator that tunes a carrier, extracts the envelope, filters it into the audio band, and outputs a WAV file or live audio stream.

Included:

  • Frequency shift to baseband.
  • Envelope detection via magnitude.
  • DC removal and normalization.
  • Low-pass filtering and decimation.
  • WAV output (48 kHz mono).

Excluded:

  • AGC implementation (optional extension).
  • Synchronous AM detection (SSB demod not required).

3.2 Functional Requirements

  1. Accept IQ file or live SDR input.
  2. Shift center frequency to carrier.
  3. Low-pass filter around AM bandwidth.
  4. Compute envelope and remove DC.
  5. Decimate/resample to audio rate.
  6. Save output as WAV and optionally play.

3.3 Non-Functional Requirements

  • Performance: Decode 2.4 MSPS in real time on a laptop.
  • Reliability: Deterministic output on recorded files.
  • Usability: Clear CLI flags for freq, sample rate, gain.

3.4 Example Usage / Output

$ python am_demod.py --input am_740kHz.iq --fs 2.4e6 --freq 740e3 --audio-rate 48e3
[INFO] Tuned carrier to 0 Hz
[INFO] LPF cutoff: 5 kHz
[INFO] Decimating to 48 kHz
[INFO] Wrote output.wav

3.5 Data Formats / Schemas / Protocols

  • IQ file: u8 interleaved (same as Project 1).
  • Audio output: 16-bit PCM WAV, 48 kHz, mono.

3.6 Edge Cases

  • Carrier offset causing audible tone.
  • Over-modulated stations causing distortion.
  • File with low SNR (speech barely audible).

3.7 Real World Outcome

You will be able to decode intelligible AM audio and save it to WAV.

3.7.1 How to Run (Copy/Paste)

python am_demod.py --input am_740kHz.iq --fs 2.4e6 --freq 740e3 --audio-rate 48000 \
  --lpf-cutoff 5000 --output am_audio.wav

3.7.2 Golden Path Demo (Deterministic)

Input: synthetic AM signal with 1 kHz tone and 50% modulation. Output: WAV containing a clean 1 kHz tone with no drift.

3.7.3 CLI Transcript (Exact)

$ python am_demod.py --input test_am_1k.iq --fs 2400000 --freq 0 --audio-rate 48000
[INFO] Envelope detected, DC removed
[INFO] Audio RMS: -12.3 dBFS
[OK] Wrote test_am_1k.wav

3.7.4 Failure Demo (Bad Input)

$ python am_demod.py --input missing.iq --fs 2.4e6
[ERROR] File not found: missing.iq
[EXIT] code=1

4. Solution Architecture

4.1 High-Level Design

IQ -> Tune -> Bandpass/LPF -> Envelope -> DC remove -> Decimate -> WAV

4.2 Key Components

| Component | Responsibility | Key Decisions | |———–|—————-|—————| | Tuner | Mix to baseband | Use complex NCO | | Envelope | Magnitude extraction | Magnitude vs power | | Filter/Decimator | Anti-aliasing + rate reduction | Multi-stage vs single-stage | | Output | WAV writer/player | 16-bit PCM |

4.3 Data Structures (No Full Code)

class AudioBuffer:
    samples: np.ndarray  # float32 in [-1, 1]
    fs: int

4.4 Algorithm Overview

Key Algorithm: AM Demod Chain

  1. Multiply IQ by NCO to tune carrier.
  2. Low-pass filter to isolate channel.
  3. Compute magnitude and remove DC.
  4. Filter audio band and resample.
  5. Normalize and write WAV.

Complexity Analysis:

  • Time: O(N log N) if FFT-based filtering, or O(N*T) for FIR.
  • Space: O(N) for streaming buffers.

5. Implementation Guide

5.1 Development Environment Setup

python -m venv .venv
source .venv/bin/activate
pip install numpy scipy soundfile

5.2 Project Structure

am-demod/
├── src/
│   ├── main.py
│   ├── tuner.py
│   ├── demod.py
│   ├── filters.py
│   └── wav_out.py
├── tests/
│   └── fixtures/
└── README.md

5.3 The Core Question You’re Answering

“How do you remove the RF carrier and keep only the audio hidden in its amplitude?”

5.4 Concepts You Must Understand First

  1. AM envelope detection (§2.1)
  2. Filtering and decimation (§2.2)
  3. Complex mixing for tuning (§2.1 references)

5.5 Questions to Guide Your Design

  1. What cutoff frequency preserves speech clarity?
  2. How many decimation stages minimize aliasing?
  3. How will you normalize audio to avoid clipping?

5.6 Thinking Exercise

Draw the AM spectrum and mark the carrier and sidebands. What bandwidth is needed for 5 kHz audio?

5.7 The Interview Questions They’ll Ask

  1. Why is AM more sensitive to noise than FM?
  2. Why must you filter before downsampling?
  3. What is the modulation index?

5.8 Hints in Layers

  1. Mix the carrier to 0 Hz.
  2. Apply a low-pass filter around 5 kHz.
  3. Take magnitude and remove DC.
  4. Resample to 48 kHz and write WAV.

5.9 Books That Will Help

| Topic | Book | Chapter | |——-|——|———| | AM theory | Understanding Digital Signal Processing (Lyons) | Ch. 10 | | Digital filters | Scientist & Engineer’s Guide to DSP (Smith) | Ch. 14 |

5.10 Implementation Phases

Phase 1: Foundation (3-4 days)

Goals: Tune and envelope detect. Tasks: Implement NCO mixer, magnitude, DC removal. Checkpoint: A synthetic AM tone decodes correctly.

Phase 2: Core Functionality (4-6 days)

Goals: Filtering and audio rate output. Tasks: FIR low-pass, decimation, WAV output. Checkpoint: Clear audio from a real AM station.

Phase 3: Polish & Edge Cases (3-4 days)

Goals: Stability and CLI options. Tasks: Add auto-leveling, error handling. Checkpoint: Handles weak/strong stations without clipping.

5.11 Key Implementation Decisions

| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | Envelope method | |I+jQ| vs power | |I+jQ| | Easier to scale | | Decimation | One stage vs multi-stage | Multi-stage | Lower CPU, better filters | | Audio rate | 44.1k vs 48k | 48k | Easier integer decimation from 2.4 MSPS |


6. Testing Strategy

6.1 Test Categories

| Category | Purpose | Examples | |———|———|———-| | Unit Tests | Demod math correctness | Envelope vs expected waveform | | Integration Tests | End-to-end WAV | Synthetic AM IQ file | | Edge Cases | Over-modulation | Distortion detection |

6.2 Critical Test Cases

  1. 1 kHz AM tone: Output audio is a clean 1 kHz sine.
  2. Frequency offset: Residual tone appears if not tuned.
  3. Low SNR: Audio still intelligible but noisy.

6.3 Test Data

fs=2.4e6, fc=0, tone=1kHz, modulation index=0.5
Expected RMS ~ -12 dBFS

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

| Pitfall | Symptom | Solution | |———|———|———-| | Missing DC removal | Loud hum | Subtract mean of envelope | | Wrong cutoff | Muffled or harsh audio | Adjust LPF cutoff | | No filtering before decimation | Whistles/aliasing | Add anti-aliasing FIR |

7.2 Debugging Strategies

  • Plot the envelope before and after DC removal.
  • Listen to output at different cutoff frequencies.
  • Verify frequency alignment with known station offsets.

7.3 Performance Traps

  • Using heavy filters at full sample rate.
  • Recomputing filter coefficients per block.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add an RMS meter for audio level.
  • Add a simple AGC.

8.2 Intermediate Extensions

  • Add selectable audio bandwidth (2.5, 5, 8 kHz).
  • Add synchronous AM detection.

8.3 Advanced Extensions

  • Add SSB (single sideband) demodulation.
  • Add a streaming network output.

9. Real-World Connections

9.1 Industry Applications

  • HF radio receivers.
  • Emergency and maritime AM transmissions.
  • csdr: command-line DSP toolkit.
  • GNU Radio: AM demod blocks.

9.3 Interview Relevance

  • Demonstrates DSP pipeline thinking and filtering knowledge.

10. Resources

10.1 Essential Reading

  • Lyons, “Understanding DSP” Ch. 10
  • Smith, “Scientist and Engineer’s Guide to DSP” Ch. 14

10.2 Video Resources

  • SDR AM demod tutorials (RTL-SDR blog videos)

10.3 Tools & Documentation

  • rtl_fm: reference AM/FM demod tool.
  • scipy.signal: filters and resampling.

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain AM modulation and envelope detection.
  • I know why filtering before decimation is required.

11.2 Implementation

  • Audio output is clean and intelligible.
  • I can decode at least two stations.

11.3 Growth

  • I can explain the effect of modulation index.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • WAV output from an AM IQ file.
  • Correct tuning and DC removal.

Full Completion:

  • Real-time decoding with stable audio.
  • Adjustable bandwidth and gain controls.

Excellence (Going Above & Beyond):

  • Synchronous detection and AGC.
  • SSB decoding add-on.