Simple FM Receiver

Pick a local radio station, tune to it, and listen to it through your computer.

Download
Blocks 5
Category Receivers
Version 1.0.0
License MIT
Updated Sep 28, 2026

Blocks used

Soapy SDR
FM Demodulator
Audio
Spectrum Analyzer
Note

Overview

Broadcast FM is the easiest signal to start with on a software-defined radio. The stations are strong, most broadcast around the clock, and you can tell by ear within seconds whether the receiver works. In the United States they share the band from 88 to 108 MHz, one station per 200 kHz channel.[1] Each station carries its program as changes in the carrier's frequency, swinging up to 75 kHz either side of center.[2]

This flowgraph captures 240 kHz around one station, turns those frequency swings back into sound, and plays it through the computer's sound output. A spectrum analyzer fed directly by the radio shows the captured band. Running it needs an antenna for the FM broadcast band, a station within reach, speakers or headphones, and a radio supported by SoapySDR, such as an RTL-SDR.

How it works

Soapy SDRFM DemodulatorAudio

The radio delivers complex I/Q samples at 240 kS/s, tuned to 88.9 MHz in the saved file. By Carson's rule a mono broadcast needs 180 kHz, twice the sum of the 75 kHz deviation and 15 kHz of audio, so it fits.[3] A stereo multiplex reaches 53 kHz,[4] which by the same rule needs 256 kHz, so the capture trims a stereo station's outermost sidebands.

A frequency-modulated carrier stores its message in how fast its phase turns. The quadrature demodulator takes the angle of each sample times the conjugate of the one before it, which is the phase step between them,[5]

f[n]=fs2πarg⁡(x[n] x∗[n−1]),f[n] = \frac{f_s}{2\pi} \arg\left(x[n]\, x^{*}[n-1]\right),

where fsf_s is the sample rate and f[n]f[n] the instantaneous frequency. The block scales it to a real audio signal.

The result is the station's whole baseband multiplex. A stereo station sends the sum of the left and right channels, a 19 kHz pilot tone, and their difference on a suppressed 38 kHz subcarrier.[2] The saved Narrowband mode passes it on as is, and the Audio block's resampler filters out the stereo subcarrier, so here the name means mono, not a narrow channel. Wideband doubles the pilot's phase to rebuild the 38 kHz subcarrier, brings the difference signal down to audio with it, and adds and subtracts the two to recover left and right.

Transmitters boost the treble before modulation with a time constant of 50 µs in Europe and 75 µs in the United States.[2] The De-emphasis setting undoes it and is saved as None.

What to look for

The spectrum analyzer shows the 240 kHz window around the tuned frequency above a scrolling waterfall. A station shows up as one broad hump centered in the window, widening and narrowing with the loudness of the program. A flat, noisy trace means there is no station at that frequency or no antenna.

In the United States, stations sit on odd tenths of a megahertz from 88.1 to 107.9 MHz,[1] and 88.9 MHz is one of them. Tuning 50 kHz away from a station slides the hump off center and distorts the sound. Loud passages then swing past the edge of the captured window, and those peaks wrap around.

Switching Mode to Wideband brings in the stereo image at a cost. The difference signal rides high in the multiplex, where the demodulator's noise is strongest, and stereo is generally taken to cost about 23 dB of signal-to-noise ratio against mono.[4] On a weak station, Wideband turns hissy while Narrowband holds up. With no pilot detection, Wideband adds that hiss even on a mono station. Setting De-emphasis to your region's time constant removes the bright, hissy treble that None leaves in.

For a deeper treatment of FM stereo and quadrature demodulation, see the sources below.

Going further

Program audio averages out over a second, so the mean of the demodulated signal is the station's offset from the tuned frequency. That makes the demodulator a tuning meter. The reading includes the station's own carrier error, which the FCC allows up to 2 kHz either way for most stations,[6] and the error of the radio's oscillator, which at 88.9 MHz adds 88.9 Hz for every part per million.

The demodulator divides its output by 100 kHz in Narrowband mode and 75 kHz in Wideband, so the code scales the mean back to hertz. Give a Python block one input and no outputs, connect it to the FM Demodulator output, and paste the code below. It averages 15 updates, about one second, and prints the offset to the block console, positive when the station sits above the tuned frequency. In Wideband mode, replace the DEVIATION = 100e3 line with DEVIATION = 75e3.

PYTHON
import numpy as np

DEVIATION = 100e3

_SUM = 0.0
_COUNT = 0
_CALLS = 0


def compute(ctx):
    global _SUM, _COUNT, _CALLS
    y = np.asarray(ctx.inputs[0]).reshape(-1)
    y = y[np.isfinite(y)]
    _SUM += float(np.sum(y))
    _COUNT += y.size
    _CALLS += 1
    if _CALLS % 15 == 0 and _COUNT > 0:
        print(f"Station offset: {_SUM / _COUNT * DEVIATION / 1e3:+.2f} kHz")
        _SUM, _COUNT = 0.0, 0

Frequency, mode, de-emphasis, and volume can change freely. The sample rate appears on the Soapy SDR, FM Demodulator, and Audio blocks, and all three must match, or the pitch and the demodulator's scaling go wrong. An RTL-SDR accepts rates above 225 kS/s up to 300 kS/s and above 900 kS/s up to 3.2 MS/s,[7] and Wideband needs at least 200 kS/s.

The graph has no channel filter, so a strong station close by can bleed into the audio. The Multi-FM Receiver flowgraph captures 2 MHz and filters two stations from it at once. The block catalog and the Python block reference cover the blocks used here.

References

References

  1. U.S. Federal Communications Commission, "Numerical designation of FM broadcast channels," Code of Federal Regulations, Title 47, Sec. 73.201. ↩ ↩2

  2. Transmission standards for FM sound broadcasting at VHF, ITU-R, Recommendation BS.450-4, Oct. 2019, Secs. 1 and 2.2. itu.int/rec/R-REC-BS.450-4-201910-I ↩ ↩2 ↩3

  3. Determination of necessary bandwidths including examples for their calculation and associated examples for the designation of emissions, ITU-R, Recommendation SM.1138-3, Oct. 2019, Annex 1, Part III-A. itu.int/rec/R-REC-SM.1138-3-201910-I ↩

  4. F. Foti, "A method to improve conventional FM stereo," Radio World, Oct. 2010. ↩ ↩2

  5. M. Lichtman, "End-to-end example with RDS," PySDR: A Guide to SDR and DSP using Python. pysdr.org/content/rds.html ↩

  6. U.S. Federal Communications Commission, "Carrier frequency departure tolerances," Code of Federal Regulations, Title 47, Sec. 73.1545(b). ↩

  7. Osmocom, rtl-sdr, GitHub. github.com/osmocom/rtl-sdr ↩

CyberEther

High-performance GPU-accelerated signal processing and visualization framework that runs anywhere.

Star on GitHub

© 2026 CyberEther. All rights reserved.

The final frontier!