• Home
  • Blog
  • Thinking
  • Guides

On this page

  • Seeing Broadcast FM in IQ Space
    • Introduction
    • Broadcast FM in IQ Space
    • IQ constellation: the rotating carrier
    • Dynamic IQ: rotation over time
    • Magnitude vs Time: what FM does not use
    • Phase and instantaneous frequency
      • Instantaneous frequency: audio revealed
    • Demodulated audio
    • Audio-synchronised animation
    • What broadcast FM teaches us
    • GitHub Link to code used to create these plots
    • Next Time…

Seeing Broadcast FM in IQ Space

2026-01-05

Seeing Broadcast FM in IQ Space

Introduction

Modern software-defined radios (SDRs) do not deliver audio directly. Instead, they provide a stream of complex samples known as I and Q, which together describe the radio signal at baseband. This representation preserves both amplitude and phase, allowing software to examine, demodulate, or decode a wide range of signals long after they have been captured. In this series of posts, I’ll use raw IQ data to look at what familiar radio signals actually look like before demodulation.

Representing a signal as IQ turns the radio waveform into a rotating vector in the complex plane. For signals with a carrier, this rotation is not an error but a fundamental part of the signal’s structure. By plotting these IQ samples directly, we can build visual intuition for how different modulations work, what changes, what stays the same, and where the information really lives.

This post is the first in a short series exploring what different radio signals look like in IQ space. It begins with broadcast FM, moves on to narrowband FM and airband AM, and finishes with DAB, where the limits of time-domain IQ visualisation become clear. In the future I may add posts around digital modes as IQ plots are especially good at investigating modulation patterns

Broadcast FM in IQ Space

Broadcast FM is a good place to start because it produces one of the cleanest and most recognisable patterns in IQ space. A broadcast FM signal consists of a strong carrier whose frequency is varied by the audio, while its amplitude remains largely constant.

When viewed as IQ samples, this carrier appears as a rotating vector in the complex plane. If the receiver is tuned exactly to the centre frequency, the carrier rotates at a steady rate. Any frequency offset between the transmitter and receiver simply changes how fast this rotation appears.

IQ constellation: the rotating carrier

Load and plot raw IQ
import numpy as np
import matplotlib.pyplot as plt
from iq_io import load_iq

# Load IQ
iq, fs, fc = load_iq("iq_capture.npz")
iq = iq[1000:]  # drop initial transient
N = min(len(iq), 200000)
iq_seg = iq[:N]

# Plot constellation
plt.figure(figsize=(6,6))
plt.scatter(iq_seg.real, iq_seg.imag, s=2, alpha=0.3)
plt.xlabel("I")
plt.ylabel("Q")
plt.title(f"Constellation @ {fc/1e6:.3f} MHz")
plt.axis("equal")
plt.grid(True)
plt.show()
Figure 1: IQ constellation diagram showing raw iq from a short capture of BBC Radio Cambridgshire broadcast signal

Plotting a short segment of raw IQ samples (Figure 1) produces a near-perfect circle. This circle is the carrier rotating in the complex plane. Unlike AM, the radius remains almost constant — the signal does not expand and contract with the audio.

What does change is the angular velocity. As the transmitted audio modulates the carrier frequency, the rotation speeds up and slows down. In a static scatter plot this motion is not visible, but it becomes obvious when time ordering is preserved.

TipKey point

In FM, information is carried in how fast the vector rotates, not how far it is from the origin.

Dynamic IQ: rotation over time

Animate IQ (GIF)
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots(figsize=(5,5))
ax.set_xlabel('I')
ax.set_ylabel('Q')
ax.set_title('Dynamic IQ Plot (GIF)')
ax.set_xlim(-0.05, 0.05)
ax.set_ylim(-0.05, 0.05)

scatter = ax.scatter([], [])

def update(frame):
    scatter.set_offsets(np.column_stack((iq_seg.real[:frame], iq_seg.imag[:frame])))
    return scatter,

ani = FuncAnimation(fig, update, frames=500, blit=True)
ani.save("dynamic_iq.gif", dpi=80)
Figure 2: Fading IQ points over the full capture

Animating the IQ samples (Figure 2) makes the underlying motion much clearer. The points trace out a rotating path, with the rotation rate continuously changing as the audio varies. Silence, speech, and music all appear as subtle changes in rotational behaviour rather than changes in size.

This animation does more than any single static plot to build intuition for what FM “is doing”.

Magnitude vs Time: what FM does not use

Plot magnitude vs time
import matplotlib.pyplot as plt

t = np.arange(len(iq_seg)) / fs
mag = np.abs(iq_seg)

plt.figure(figsize=(10,4))
plt.plot(t, mag, linewidth=1)
plt.xlabel("Time (s)")
plt.ylabel("|IQ|")
plt.title("Magnitude vs Time")
plt.grid(True)
plt.show()
Figure 3: Magnitude of IQ vs Time

Plotting the magnitude of the IQ samples over time (Figure 3) shows a nearly constant level. This confirms that the amplitude of the signal is not being used to carry information. Any small variations are due to noise, multipath, or receiver effects rather than the modulation itself.

This plot is a useful contrast with AM, where the magnitude directly follows the audio waveform.

Phase and instantaneous frequency

Phase and instantaneous frequency
# Unwrapping Phase
phase = np.angle(iq_seg)
phase_unwrapped = np.unwrap(phase)

# Instantaneous frequency
inst_freq = np.diff(phase_unwrapped) * fs / (2*np.pi)
t_freq = t[1:]

plt.figure(figsize=(10,4))
plt.plot(t_freq, inst_freq)
plt.xlabel("Time (s)")
plt.ylabel("Frequency (Hz)")
plt.title("Instantaneous Frequency vs Time")
plt.grid(True)
plt.show()
Figure 4: Unwrapped phase vs Time

If the phase of the IQ samples is unwrapped and plotted against time (Figure 4), it appears as a steadily increasing line. The slope of this line corresponds to frequency. When audio is present, the slope changes continuously, this is frequency modulation made visible.

While this plot is mathematically important, it is not always easy to interpret visually. For that reason, it is often more intuitive to plot instantaneous frequency, derived from the difference between successive phase samples.

Instantaneous frequency: audio revealed

Figure 5: Instantaneous frequency

When instantaneous frequency is plotted (Figure 5), the audio content becomes immediately visible. Speech and music appear directly as deviations around the centre frequency. This plot makes explicit what FM is doing: audio is mapped onto frequency deviation.

Although this step already involves a small amount of processing, it remains very close to the raw signal and provides a clear bridge between the abstract idea of phase and the familiar concept of audio.

Demodulated audio

FM demodulated audio
from scipy.signal import butter, filtfilt

# Low-pass filter for audio
fs_audio_cut = 15e3
b, a = butter(5, 15e3/(fs/2), btype='low')
audio = filtfilt(b, a, inst_freq)

# Normalize and plot
audio /= max(abs(audio))

plt.figure(figsize=(10,4))
plt.plot(t_freq, audio, linewidth=1)
plt.xlabel("Time (s)")
plt.ylabel("Normalized Audio")
plt.title("Demodulated FM Audio Waveform")
plt.grid(True)
plt.show()
Figure 6: Demodulated audio waveform

For completeness, the FM signal can of course be demodulated into audio (Figure 6). The resulting waveform confirms what we have already inferred from the instantaneous frequency plot.

In this series, demodulated audio is included only as a reference. The focus is on understanding the signal before demodulation, using the structure already present in the IQ data.

Audio-synchronised animation

Figure 7: Animated IQ plot with audio overlay

As a final illustration, the IQ animation can be synchronised with the recovered audio (Figure 7). Hearing the audio while watching the rotation speed change reinforces the connection between sound and frequency deviation.

ImportantEducational note

The audio-synchronised MP4 is included solely to illustrate how audio maps onto frequency deviation in broadcast FM. It uses a short off-air capture for technical demonstration, not for redistribution or listening.

What broadcast FM teaches us

Broadcast FM establishes three ideas that will carry through the rest of this series:

  • IQ represents a rotating vector, not a static point
  • A visible carrier produces rotation
  • Different modulations encode information in different aspects of that rotation

GitHub Link to code used to create these plots

For complete reproducible scripts, including capture, plotting, and video generation, see the GitHub repository. The link is currently here as a placeholder but code will be added shortly

Next Time…

In the next post, we’ll look at an FM repeater signal and see how the same underlying geometry appears in a more dynamic, real-world setting , with silence, speech, and key-up transients all visible in IQ space.

We use Google Analytics to collect anonymous data on site usage. Learn more.
© 2025 Dr Ian Henry, G0LFT. All rights reserved.
Privacy Policy | Image Credits | About | Station | Contact | QRZ | YouTube
Main Hub