Subsystem Search

Straight into Subsample

By the end of this chapter, every transmission Substation records goes straight into Subsample, ready to play, and is there again each time Subsample starts.

This is the short way from a receiver to sounds to play, for a musician using Subsample: Installing Substation, A first scan, then this chapter. The chapters after it explain what Substation does at each step, and how to set it up when it does not do what you hoped.

The folder so far

The radio folder as A first scan left it: Substation installed in an environment inside it, switched on, its settings written, and the test signal made.

$ mkdir radio
$ cd radio
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install "substation==0.8.2"
…
Successfully installed … substation-…
$ substation --init
Created /home/keanu/radio/config.yaml
This is the full default configuration, fully commented. Edit it to suit
your hardware and bands, then start a scan, e.g. `substation --band amateur_2m`.
Any setting you delete falls back to the built-in default.
# Makes test-signal.wav: eight seconds of the 2m band as a receiver hands it over,
# hiss with a transmission in the middle, for Substation to scan in place of a receiver.

import wave

import numpy

# The frequency the transmission is sent on, in hertz, and how many seconds it lasts.
frequency = 145_500_000
length = 3

# The frequency the recording is centred on, and how many IQ samples it holds
# a second, as a receiver set for the 2m band hands them over.
centre = 145_000_000
rate = 2_400_000

# Two seconds of hiss first, while Substation measures it, and three after.
start = 2
seconds = start + length + 3

# The hiss is random, but the same every time the script runs.
noise = numpy.random.default_rng(1)

with wave.open("test-signal.wav", "wb") as file:

	# Each IQ sample as two 16-bit numbers, I and Q, held as two audio channels.
	file.setnchannels(2)
	file.setsampwidth(2)
	file.setframerate(rate)

	# A second at a time, so the script needs little memory.
	for second in range(seconds):

		# The moment of each IQ sample, in seconds.
		t = second + numpy.arange(rate) / rate

		# The receiver's own hiss.
		hiss = (noise.standard_normal(rate) + 1j * noise.standard_normal(rate)) * 0.01

		# A buzz like a voice: overtones of a low note, each softer than the one below.
		voice = sum(5.3 / n ** 2 * numpy.sin(2 * numpy.pi * 150 * n * t) for n in range(2, 7))

		# Louder and softer six times a second, like syllables.
		loudness = numpy.abs(numpy.sin(2 * numpy.pi * 3 * t))

		# FM: the sound swings the transmission's frequency to either side of its own.
		phase = 2 * numpy.pi * (frequency - centre) * t + loudness * voice

		# The transmission, only while it is on, added to the hiss.
		on = (t >= start) & (t < start + length)
		iq = hiss + numpy.where(on, 0.3 * numpy.exp(1j * phase), 0)

		# Written as pairs of 16-bit numbers, I then Q.
		pairs = numpy.stack([iq.real, iq.imag], axis=1)
		file.writeframes((pairs * 32767).astype("<i2").tobytes())
$ python make_signal.py

What you need

Subsample installed with its OSC support, and a project of it set up to play, as Subsample's own guide shows. OSC, Open Sound Control, is how one program tells another what has happened. Subsample listens for it once osc.enabled and osc.receive_enabled are on in its project's config.yaml, as Subsample's guide shows under Messages over OSC. This chapter takes the project that guide makes, kitchen-kit, to be beside your radio folder.

Two settings

Open config.yaml, the file --init wrote in A first scan, in any text editor, and change two settings in it: under recording, set audio_output_dir to your Subsample project's samples/captures folder, and under osc, set sampler_enabled to true. Every other setting in that file is one of Substation's defaults, so with those two changed, this is all that differs:

recording:
  audio_output_dir: ../kitchen-kit/samples/captures
osc:
  sampler_enabled: true

Scan

Start Subsample first, in a terminal of its own: a recording saved while Subsample is not running is not sent again, though Subsample finds it in its folder the next time it starts. Then, in another terminal, scan from the radio folder as before:

Example not checked: it is not run when this site is built.

$ substation --band amateur_2m

Without a receiver, scan the test signal from A first scan instead. Two lines of the log say what changed: where the recordings go, and that each is sent to Subsample, on this computer at port 9002, where Subsample listens:

$ substation --band amateur_2m --iq-file test-signal.wav --center-freq 145000000 --start-time "2026-10-05 21:00:00"
… - INFO - DC offset: shifted the centre frequency +6250 Hz to avoid masking a radio channel at 145.00000 MHz
… - INFO - Initialized scanner for band 'amateur_2m'
… - INFO - Frequency range: 144.00000 - 146.00000 MHz
… - INFO - Number of radio channels: 161
… - INFO - Center frequency: 145.00625 MHz
… - INFO - Required bandwidth: 2.02300 MHz (inc. 6.2kHz edge margin)
… - INFO - Sample rate: 2.400 MHz
… - INFO - SNR threshold: 4.5 dB ON / 1.5 dB OFF (hysteresis)
… - INFO - SDR Device: file (index 0)
… - INFO - Modulation: NFM
… - INFO - Recording: ENABLED (16000 Hz mono WAV to ../kitchen-kit/samples/captures)
… - INFO - OSC sender → no sequencer, sampler 127.0.0.1:9002
… - INFO - IQ file playback: test-signal.wav, center 145.000000 MHz, start 2026-10-05 21:00:00
… - INFO - Starting scan...
… - INFO - Setting up SDR device...
… - INFO - IQ file: test-signal.wav, 2.400 MHz, 19200000 frames (8s / 0.0h), center 145.000000 MHz
… - INFO - Using device center frequency: 145.000000 MHz (band midpoint: 145.006250 MHz)
… - INFO - SDR Gain: 36.0
… - INFO - SDR device configured successfully
… - INFO - Noise estimation regions: 162 gaps, 8748 bins total
… - INFO - FFT size: 65536 bins, frequency resolution: 36.6 Hz
… - INFO - Welch segments: 8, samples per slice: 524288
… - INFO - DC spike exclusion: 7 bins around center
… - INFO - Started async SDR streaming
… - INFO - Time slice: 200 ms (524288 samples)
… - INFO - Noise floor warmup complete (6.8 dB). Detection enabled.
… - INFO - Radio channel 121 ON (f = 145.50000 MHz, SNR = 15.3dB, recording: YES)
… - INFO - Radio channel 121 OFF (f = 145.50000 MHz, SNR = -0.2dB, recording: YES)
… - INFO - IQ file playback complete
… - INFO - Cancelled async SDR streaming
… - INFO - SDR device closed

Subsample's log says when a recording arrives: OSC /sample/import:, then the recording's path, and a few seconds later its instrument library one sound larger. Beside the recording, Subsample writes two files of its own: what it measured in the sound, and a picture of it.

Playing them

Each recording joins Subsample's library like a sound Subsample recorded itself. Its ready-made drum kit plays, for each drum, whichever sound in the library is most like it, so your recordings take their place among the others, and a map of your own can choose them by what they hold, as Subsample's guide shows from Your first MIDI map.

Which bands record

Out of the box, only some of Substation's bands record: the amateur bands and CB radio, six of the 48. The others only detect, logging each transmission without recording it. The 2m band can be quiet, so the first sound may take a while to arrive.

The next chapter shows which bands record by default, and how to make a band of your own.