Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SDR-- logo

Welcome to SDR--

SDR-- is a software-defined radio application. Listen, decode, view, and record signals from an SDR, a network receiver, or an IQ recording.

Build a receiver by connecting nodes in Patch view. Pin frequently used controls and displays to Rack view. Start with an RTL-SDR and a local FM station.

Get started

  1. Install SDR--.
  2. Build your first receiver.
  3. Learn the workspace controls.

Find a guide

TaskGuide
Connect a radioRadios and hardware
Listen or decodeChannels and decoding
Save and replay signalsRecording and playback
Operate over a networkContainers and remote radios
Use a phone in the fieldField mode
Fix a problemTroubleshooting
Develop SDR--Build and test

How it runs

The server handles the radio and signal processing. The desktop app and browser provide the same interface. Run everything on one computer, or place the server near the antenna and connect over the network. All clients share the active workspace.

SDR-- is under active development. The channel catalog lists supported modes, test coverage, and experimental limits.

Install SDR--

Choose a desktop app for local use or a server to control from a browser. Both provide the same receiver and interface.

InstallationBest for
Desktop applicationA radio connected to your computer
Portable serverA Raspberry Pi, home server, or remote receiver
HomebrewPackage management on macOS or Linux
NixLinux systems managed with Nix
ContainerA persistent server with Docker

Desktop application

Download your platform's installer from GitHub Releases, install it, and open SDR--. The app starts its server automatically on a private local port.

PlatformPackage
macOS.dmg for Apple silicon or Intel
Linux.deb or .AppImage
Windows.msi or .exe

Portable server

Download and unpack the sdrmm archive for your operating system and processor from GitHub Releases. Run the binary:

./sdrmm

On Windows, run sdrmm.exe. Open http://localhost:8080 on the server or http://<server>:8080 from another computer.

The server listens on all network interfaces without authentication by default. Configure a shared token and HTTPS before allowing untrusted network access.

Homebrew

Add the tap:

brew tap newspicel/tap

For the macOS desktop app:

brew install --cask sdrminusminus

For the server on macOS or Linux:

brew install sdrmm
brew services start sdrmm

The cask installs into /Applications. The service runs the server in the background and starts it at login. Open http://localhost:8080.

Nix

With flakes enabled, install and launch the desktop app on x86_64 or aarch64 Linux:

nix --extra-experimental-features 'nix-command flakes' \
  profile install github:Newspicel/sdrminusminus
sdrmm-desktop

The flake exposes the desktop package as sdrmm-desktop, sdrmm, and default. To build it from a checkout:

nix --extra-experimental-features 'nix-command flakes' build

The result is result/bin/sdrmm-desktop.

The Nix package uses SoapySDR for local radios. Select their modules with soapyPlugins. This NixOS example assumes the repository is declared as the sdrminusminus flake input:

environment.systemPackages = [
  (inputs.sdrminusminus.packages.${pkgs.stdenv.hostPlatform.system}.sdrmm.override {
    soapyPlugins = with pkgs; [ soapyrtlsdr soapyremote ];
  })
];

hardware.rtl-sdr.enable = true;
users.users.your-user.extraGroups = [ "plugdev" ];

Keep only the modules and hardware options you need. The package provides the SoapySDR core; modules and USB permissions come from your configuration.

Container

On Linux, start the supplied Docker Compose service:

git clone https://github.com/Newspicel/sdrminusminus.git
cd sdrminusminus
docker compose pull
docker compose up -d

Open http://localhost:8080. The service persists its database and recordings in sdrmm-data. For USB radios, set group_add to the host group that owns the device. See container setup for permissions, authentication, and HTTPS.

Connect your radio

Open a Device node and select your receiver. Many radios work with the built-in drivers; others need a vendor library or SoapySDR module. The hardware guide lists the requirements for each receiver and package.

If a radio is missing, select Check hardware on an unbound Device node or run sdrmm --doctor.

Stable and nightly builds

Use a stable release for regular use. Desktop apps check for stable updates at startup. The rolling nightly release follows main and may change saved-data formats without migration support. Stable apps do not automatically update to nightlies.

Build from source

Follow Build and test to develop SDR-- or choose custom backends.

Next step

Follow Your first receiver to listen to broadcast FM with an RTL-SDR.

Your first receiver

Listen to a local FM broadcast station with an RTL-SDR. You need the receiver, an antenna, and an installed copy of SDR--.

1. Connect the RTL-SDR

Attach the antenna and plug the receiver into the computer running SDR--. For a remote setup, plug it into the server.

Open SDR-- and select your RTL-SDR on the Device node. A new installation also includes a connected Scope and a Speaker. If these nodes are missing, add them from + Node and connect Device IQ to Scope IQ.

If the radio is missing, open Check hardware on Device. The hardware guide covers driver requirements and USB permissions.

2. Tune a broadcast station

On Device, set the sample rate to 2.4 MS/s and tune to a local FM station's frequency. For example, enter 100.0 MHz only if a station broadcasts there in your area.

Start with moderate tuner gain. Adjust it until the station is visible on the Scope without clipping or a large rise in the surrounding noise.

3. Add a WFM channel

Choose + Node, search for WFM, and add it. WFM is the mode for broadcast FM. Connect the nodes:

Device IQ → Scope IQ
Device IQ → WFM IQ
WFM audio → Speaker audio

Set the WFM channel to the station's frequency. The Device dial selects the received frequency range; the channel dial selects one station inside it. Both must cover the station.

Changes apply automatically. Press Apply patch if the node requests it.

4. Start audio

Start playback on the Speaker and adjust the volume. If audio stays silent:

  • Turn off channel squelch temporarily.
  • Check that the channel marker covers the station on the Scope.
  • Click the page to allow browser audio, and check the system output device.

See audio troubleshooting if needed.

5. Arrange your receiver

Select the controls you use most and press p to pin them to Rack view. Press v to switch between Patch and Rack. Your layout is saved automatically.

To display station names and radio text, connect WFM events to a Readout. RDS appears when the station transmits it and reception is strong enough.

Next steps

Understand the workspace

A workspace saves your nodes, connections, radio settings, rack layout, and regional band plan. It lives on the server and is shared by every connected client.

Patch and rack

Use Patch to connect nodes and inspect signal flow. Ports accept compatible signals: IQ feeds channels and scopes, audio feeds speakers, and decoder events feed displays and exports.

Use Rack for everyday operation. Select a node and press p to pin or unpin its controls. Moving or resizing it in Rack leaves its connections intact. Press v to switch views.

Node types

GroupExamplesPurpose
SourcesDevice, GPS positionSupply IQ or station position
DecodersAM, NFM, WFM, ADS-B, DMRReceive one signal from IQ
ToolsArray, Direction finder, Passive radar, Combiner, Scanner, Signal hunt, DMR trunk, Event filter, TriangulationProcess signals or control receivers
OutputsScope, Map, Readout, Decoder log, Video, Speaker, recorders, Network IQ, ExportDisplay, play, save, or forward results

+ Node lists the nodes available in the running server. Start with a Device, connect a channel, and add outputs for its audio or events.

Live position wiring

Add GPS position and select a source:

TabSource
ReceiverSerial NMEA receiver, selected from the list or entered as a device path
Networkgpsd endpoint; default 127.0.0.1:2947
FixedLatitude and longitude entered manually
This deviceBrowser or desktop WebView location, where supported

Serial and network sources must be reachable from the server. This device uses the client showing the interface. For serial receivers, set the baud rate and maximum published update rate. The node validates GGA and RMC sentences and displays a six-character Maidenhead locator.

Connect position to any consumers that need it:

ConsumerUses position for
ADS-BLocal CPR decoding reference
MapStation position, route, and visited-location heatmap
RecorderPosition and fix time in SigMF capture metadata
Direction finder, Passive radar, Propagation mapGeographic results

One source can feed several nodes. Lost fixes are reported and stale coordinates stop being used. Serial and gpsd sources reconnect automatically. Forget source reopens the source picker.

Drive a signal survey

  1. Add Signal survey and connect Device IQ and GPS position.
  2. Choose a frequency offset within the IQ span and a measurement width.
  3. Wait for a spectrum level and GPS fix, then start the survey.
  4. Pause before changing the receiver setup. Export the results as CSV when finished.

The offset controls move the measured slice without retuning the radio. Each fix records the peak spectrum level within that slice. Nearby fixes are grouped into roughly ten-metre cells.

Levels are in dBFS. Keep gain, antenna, cable, and measurement width unchanged to compare locations.

Device identity and reconnection

Device nodes remember the selected receiver's identity. Unplugging it preserves the node, connections, and settings; reconnecting the same receiver restores the binding.

Use Forget this radio to release it and choose a replacement. The node and wires remain.

Applying a patch

Most edits save and apply automatically. Applying a patch opens devices, restores settings, updates channels, and removes live objects no longer used by the workspace. Press Apply patch when a node reports that the saved layout and running receiver differ.

Multiple workspaces and clients

Tuning, switching workspaces, and applying templates affect everyone connected to the server. Workspaces organise activities; they are not private browser sessions.

Concurrent edits to the same saved revision produce a conflict rather than overwrite another client's layout. See Workspaces, templates, and presets for saving, sharing, undo, and reuse.

Radios and hardware

Select a radio on a Device node. SDR-- supports built-in drivers, SoapySDR modules, network receivers, and virtual sources. Use Check hardware or sdrmm --doctor if a receiver is missing.

Built-in drivers

Standard desktop and portable builds include the drivers below, except where noted. The Nix package uses SoapySDR for local hardware. Custom builds can select their own backends.

ReceiverExtra software
RTL-SDRNone
KrakenSDR and KerberosSDRNone
HackRFNone
Airspy R2 and MiniNone; experimental driver
Airspy HF+ and HF+ DiscoveryNone; experimental driver
AntSDR, ADALM-Pluto, and compatible AD936x boardsNone; the board must serve iiod
SDRplay RSP1, RSP1A, RSP1B, RSP2, RSPduo, RSPdx, RSPdx-R2SDRplay API 3.15 or newer
Dragon Labs CR-8Vendor CR-8 library; requires a server build with cr8 enabled

SoapySDR modules for receivers handled by enabled built-in drivers are skipped to avoid duplicate entries. Virtual sources and direct network protocols do not need SoapySDR.

Check the installation

sdrmm --doctor

The report lists compiled backends, loaded libraries, SoapySDR paths and modules, discovered receivers, data paths, and Linux USB permissions. Check hardware on an unbound Device node runs the same checks.

For a radio using SoapySDR, also run:

SoapySDRUtil --info
SoapySDRUtil --find
SoapySDRUtil --probe="driver=bladerf"

Replace bladerf with your module's driver name. If the utility finds a library that SDR-- misses, set SDRMM_SOAPY_LIBRARY to its full path. Use sdrmm --doctor to check built-in drivers; SoapySDRUtil reports only its own modules and devices.

Linux USB permissions

Install your receiver's udev rules and join the group they grant, usually plugdev. Reload udev and reconnect the radio after changing rules. The server account needs permission to open the USB device; SDR-- does not require root.

Containers need the USB bus passed through and the owning group's numeric ID in group_add. Check it on the host with stat -c '%g %G %a' /dev/bus/usb/*/*. See container USB setup for reconnect support and examples.

SoapySDR modules

Install SoapySDR and a matching module for hardware without a built-in driver.

ReceiverModule
bladeRFSoapyBladeRF
LimeSDRSoapyLMS7
USRPSoapyUHD
Remote SoapySDR serverSoapyRemote

Package contents

PackageSoapySDR availability
Desktop installer or portable archiveUses a separately installed system library and modules
Homebrew server formulaInstalls the core as a dependency; add modules separately
NixProvides the core; select modules with soapyPlugins
ContainerIncludes Debian's core and bladeRF, LimeSDR, and SoapyRemote modules

The core loads at runtime. Built-in drivers work when SoapySDR is absent.

Install the core and modules

SystemCoreExample module
Debian, Ubuntu, Raspberry Pi OSsudo apt install libsoapysdr0.8sudo apt install soapysdr-module-bladerf
Fedorasudo dnf install SoapySDRsudo dnf install SoapySDR-bladeRF
Archsudo pacman -S soapysdrsudo pacman -S soapybladerf
macOS with Homebrewbrew install soapysdrbrew install soapybladerf
WindowsPothosSDRIncluded modules
NixOSNix configurationsoapyPlugins

Modules must match the SoapySDR 0.8 ABI. Incompatible modules are rejected and logged. Use these overrides for nonstandard locations:

VariableValue
SDRMM_SOAPY_LIBRARYFull path to the core library
SDRMM_SOAPY_MODULE_PATHExtra module directories, searched before system paths

On macOS, discovery includes Homebrew prefixes. On Windows, put the PothosSDR installation on PATH. To add container modules, see SoapySDR in containers.

Network receivers

Open the Network tab on an unbound Device node and enter the receiver's address.

ProtocolDefault port
rtl_tcp1234
SpyServer5555
AD936x / iiod30431

All three protocols are built in. A remote SoapySDRServer instead requires SoapyRemote and appears through the normal device search.

Virtual sources

Release builds support SigMF recording playback. Synthetic sources are available only in debug builds: a signal generator, a four-lane coherent array, and test transceivers. See Build and test.

Device controls

Controls reflect the selected driver's capabilities: RX/TX lanes, tuning, sample rate, bandwidth, antennas, gain, AGC, clock sources, and model-specific settings. Changing a setting can change other available controls. For example, RTL-SDR direct sampling changes the tuning range.

The interface reports transmit capabilities, but the transmit workflow is not yet available.

RTL-SDR

SettingEffect
TUNERTuner gain
ppmCrystal frequency correction
bias_teeAntenna-port power
agcR82xx tuner AGC
direct_samplingoff, i, or q
  • Gain: uses the tuner's supported steps. An R820T request for 20 dB rounds to 19.7 dB.
  • Sample rate: 225–300 kHz or 900 kHz–3.2 MHz. Rates in the gap are rejected.
  • IF filter: 0–8 MHz on R82xx tuners; 0 selects bandwidth automatically.
  • Direct sampling: unavailable on RTL-SDR Blog V4. Its upconverter handles tuning below 28.8 MHz.

KrakenSDR

KrakenSDR opens as one Device with five lanes; KerberosSDR has four. Discovery groups the tuners by serial number and USB hub. The vendor Raspberry Pi image is not required.

SettingEffect
TUNERGain per lane
ppmCrystal correction
bias_teePower on the array's antenna ports
agcR82xx tuner AGC

All lanes tune together. Direct sampling is unavailable. The shared clock provides time_sync coherence; relative phase must be recalibrated after each retune. SDR-- controls the built-in noise source during array calibration.

If the array is missing, check that every tuner appears in sdrmm --doctor or Linux lsusb. Incomplete units appear as individual dongles.

HackRF

SettingEffect
LNAGain in 8 dB steps
VGAGain in 2 dB steps
AMPSwitchable +14 dB RF amplifier; included in total gain
bias_teeAntenna-port power

Airspy

The built-in Airspy drivers need no libairspy, libairspyhf, or SoapySDR module. Both are experimental: USB and signal-processing tests pass, but live reception has not been verified.

To use SoapySDR instead, build without the airspy and airspyhf features and install the corresponding SoapySDR modules.

Airspy R2 and Airspy Mini

The displayed sample rate is complex IQ output. The USB stream carries real ADC samples at twice that rate; SDR-- converts them to IQ.

LNA, mixer, and VGA gain use firmware step numbers rather than dB. The Device also exposes LNA AGC, mixer AGC, and bias-tee switches.

Airspy HF+ and HF+ Discovery

Tuning covers up to 31 MHz and 60–260 MHz. Frequencies in the gap are rejected.

Controls include a preamp switch, attenuation from 0 to −48 dB in 6 dB steps, AGC, AGC threshold, and bias tee.

At zero-IF rates, the engine offsets the local oscillator and removes DC. The driver does not implement the vendor library's adaptive IQ balancing, so image rejection may be lower at these rates.

AntSDR, PlutoSDR and other AD936x boards

The built-in driver connects directly to iiod over Ethernet or USB. It supports AntSDR E200/E310, ADALM-Pluto, and compatible AD936x boards without a host libiio or SoapySDR installation.

Capabilities come from the board. An AD9361 typically reports 70 MHz–6 GHz; an AD9363 reports 325 MHz–3.8 GHz. A 2×2 board exposes two RX and two TX lanes; a stock Pluto exposes one of each.

SettingEffect
RXReceive gain per lane
TXTransmit attenuation per lane
ppmCrystal correction relative to factory trim
gain_modemanual, slow_attack, fast_attack, or hybrid
quadrature_tracking, rf_dc_tracking, bb_dc_trackingHardware corrections
fir_filterProgrammable decimating filter
tx_portTransmit port
AntennaReceive port; usually A_BALANCED on a single-input board

Discovery: USB boards appear automatically. Search checks ant.local, 192.168.1.10, pluto.local, and 192.168.2.1. Enter other addresses in the Network tab.

Tuning and lanes: the dial tunes RX and TX together. Two RX lanes share a synthesizer and sample clock and report phase coherence. Gain and input port are set per lane.

Sample rate: roughly 2.084–61.44 MS/s, limited in practice by the connection. USB 2.0 carries a few MS/s; gigabit Ethernet allows higher rates.

USB: Linux requires the libiio udev rules. sdrmm --doctor checks them. Boards exposing only two endpoint pairs operate half duplex; simultaneous RX and TX requires another pair.

SDRplay

Install SDRplay API 3.15 or newer and keep sdrplay_apiService running. The built-in driver loads the vendor library at runtime, usually from /usr/local/lib or C:\Program Files\SDRplay\API. No SoapySDR module is needed.

The API is installed separately. If an RSP is missing, check SDRplay API in sdrmm --doctor. Container setup requires the library and host IPC.

Gain

Both sliders show gain, so increasing either raises the signal level.

StageControl
RFLNA gain relative to the band's weakest state; steps depend on frequency, port, and HDR mode
IF0–39 dB, corresponding to the inverse of the API's 20–59 dB gain reduction

AGC controls IF gain. With AGC enabled, the IF slider sets the starting gain and the setpoint sets the target level in dBFS.

Sample rates

Single-tuner modes provide 62.5 kS/s–10.66 MS/s. Rates below 2 MS/s use hardware decimation.

RSPduo

Available operating modes appear as separate choices: Tuner 1, Tuner 2, Dual Tuner, Master, and Slave. The workspace saves the chosen mode. Modes held by another application are unavailable.

Dual Tuner exposes two independently tuned streams. Dual Tuner, Master, and Slave use a 6 MHz ADC rate and 1.62 MHz IF. Output rates are 2 MS/s and successive halvings down to 62.5 kS/s; analog bandwidth is capped at 1.536 MHz.

Slave mode waits for a master application. The master owns the clock; a slave can change its own decimation but cannot apply ppm correction.

Licensing

The Rust interface follows the public SDRplay API specification. The specification grants use of its information for software supporting SDRplay receivers. No vendor source, headers, or binaries are included. Gain tables come from that specification.

Dragon Labs CR-8

The CR-8 has eight phase_coherent lanes sharing a clock and synthesizer. Use one Device node with outputs iq through iq8 for calibration, direction finding, beamforming, or passive radar.

Install the vendor library separately and run sdrmm --doctor to verify loading. Set SDRMM_DLCR_LIBRARY to its full path if it is outside the normal search locations. Use a server build with the cr8 feature enabled. Standard packaged builds exclude this backend.

SettingBehaviour
FrequencyTunes all eight lanes together
Sample rateFixed at 12.5 MS/s
GainLNA, mixer, and VGA per lane
ClockOnboard oscillator or external 10 MHz reference

The tuning range follows the hardware documentation because the SDK does not report it.

How radios are discovered

Discovery runs when USB devices change and once per minute for network radios. SoapySDR probing uses a child process so a crashing or stalled vendor module does not terminate SDR--. For debugging, SDRMM_SOAPY_PROBE=in-process disables that isolation.

Before an unattended deployment

Test the packaged build with your radio:

  1. Save the sdrmm --doctor report.
  2. Stream for at least 30 minutes and check overruns.
  3. Test tuning, gain, sample rate, and the controls you intend to use.
  4. Reconnect the radio and confirm the workspace restores it.
  5. Record a short capture and replay it.

Channels and decoding

A channel receives one frequency from a Device's IQ stream. Retuning the Device preserves channel frequencies. Channels outside its reception range stay configured and resume when the radio covers them again.

Add a channel

Choose a mode from + Node and connect Device IQ to channel IQ. Set the channel frequency, then connect the outputs you need:

OutputDestinationResult
audioSpeakerLive audio
eventsReadoutStation text, aircraft tables, and other current state
eventsDecoder logStored message history
eventsMapDecoded positions
eventsExportCSV or JSON of stored rows
videoVideoATV frames or an SSTV picture

Channel catalog

The Decoders palette lists modes available in the running build. Support and test coverage vary by mode:

GroupChannelsMaturity
Analog voiceAM, NFM, SSB, WFM (broadcast, with stereo and RDS)tested on air
Digital voiceDMRtested on air
Digital voiceFreeDV 1600tested on air
Digital voiceD-STAR, System Fusion, NXDN, P25 Phase 1, dPMR, M17fixture-only
AviationADS-B (1090ES)tested on air
AviationACARS, VDL Mode 2, HFDL, Inmarsat Classic Aerofixture-only
AviationVOR, ILS localizer / glideslopeexperimental
MarineAIS, NAVTEX, Digital Selective Calling, Inmarsat STD-C / EGCfixture-only
Amateur data and HFAPRS / AX.25, RTTY, PSK (31, 63, 125, 250 baud), Morse (CW), CW skimmer, FT8, FT4, WSPRfixture-only
Paging and telemetryPOCSAGtested on air
Paging and telemetryFLEX, ERMES, Selcall (CCIR/ZVEI), Sub-GHz OOK/FSK frames, ISM sensors, radio clocks (DCF77, WWVB, MSF, JJY)fixture-only
VideoATV, SSTVfixture-only
Wideband digitalDAB / DAB+, DATV (DVB-S / S2), DRM30 / DRM+experimental
UtilitySignal identifier, Iridium bursts, DECT base station surveyfixture-only
UtilityGNSS lab (GPS L1 C/A)experimental

Optional services, trunking variants, and vendor extensions may be unsupported. Check the mode-specific limits below.

What the maturity labels mean

LabelEvidence
tested on airLive reception verified through the receiver and decoder integration
fixture-onlyGenerated IQ, reference vectors, or recordings tested; live integration unverified
experimentalPartial acquisition, decoding, or measurement support

Fixture tests catch decoding errors but provide limited evidence for drift, interference, transients, and multipath. Labels apply only to the tested services.

The fixture library lists recording origins and expected output, including DMR, ADS-B, FreeDV 1600, and FT8. Some modes also use published protocol vectors. Iridium uses off-air bits in a synthetic waveform.

VDL Mode 2, HFDL, Inmarsat Classic Aero, Inmarsat STD-C, and DSC use xng. Their labels describe the SDR-- integration's coverage.

ISM sensors

A Sub-GHz channel decodes known sensor payloads and displays raw frames for other signals. Supported devices are grouped by pulse coding:

CodingDevices
Pulse positionNexus-T/TH, Rubicson (also Solight TE44, EMOS E0107T), Acurite 609TXC, Acurite 606TX, Prologue-TH, inFactory-TH, Kedsum-TH, Springfield soil probe
Pulse widthLaCrosse TX141TH-Bv2, Fine Offset WH2, Auriol HG02832, Geevon TX16-3, WS2032 weather mast, EMOS E6016 rain gauge, Rubicson 48942 pool, WT0124 pool, Opus XT300 soil probe
ManchesterAmbient Weather F007TH
Pulse code (FSK)Ambient Weather WH31E, Renault TPMS, Toyota TPMS
Differential ManchesterWT450-TH

Readings can include temperature, humidity, soil moisture, wind speed and direction, rainfall, tyre pressure, and power. Renault TPMS adds Manchester coding after framing; Toyota TPMS adds differential Manchester.

The decoder checks pulse timings and the device's checksum, digest, or parity before reporting a reading. Unrecognised bursts remain available in the raw timing view. FSK sensors use bit periods of 55–58 µs; the default minimum pulse width admits these signals.

Pulse slicing, payload layouts, validation rules, and CRC/LFSR digest routines follow rtl_433, licensed GPL-2.0-or-later.

Experimental mode limits

ModeAvailable outputMissing or limited functionality
DAB / DAB+FIC and MSC decoding, CRC-checked DAB+ access unitsNo audio codec or playback
DATVDVB-S/S2 transport packets and programme tables, or generic-stream datagramsNo audio or video codec output
DRM30 / DRM+Acquisition, lock, SNR, and frequency errorNo FAC, SDC, or MSC decoding; no service labels or media
GNSS labGPS L1 C/A acquisition and NAV telemetryNo position solution
VOR / ILSRadial or difference in depth of modulationTested only against analytically generated signals

To add on-air coverage, contribute a short IQ capture restricted to the relevant band, with its expected decoded output. See Build and test and the contribution guide.

Pager text

POCSAG uses seven-bit text. Some German networks substitute umlauts and ß using DIN 66003. SDR-- applies that mapping inside words next to lowercase letters: M}nchen becomes München and Stra~e becomes Straße.

Other text stays ASCII, including [ALARM] and entirely uppercase messages. There is no manual character-set setting.

Sample rate and passband

Keep the channel's full occupied bandwidth inside the Device's reception range. If it does not fit, retune the Device, move the channel, or increase the sample rate.

Most channels resample IQ internally. These modes require a specific device rate:

ChannelDevice rate
ADS-B2–4 MS/s
ATV2–20 MS/s
GNSS lab2.048 MS/s

The channel reports incompatible rates and offers a suitable choice. Use the lowest rate that covers your signals to reduce USB traffic and CPU load.

Tuning and squelch

Tune through the channel dial, its Scope marker, or keyboard shortcuts. Direct entry accepts MHz by default, or an explicit kHz, MHz, or GHz suffix. Step buttons adjust by −25, −5, +5, or +25 kHz.

The lock beside a dial prevents changes to that frequency. A locked channel does not lock its source Device. You can set channel frequencies before connecting a radio; an untuned Device initially opens over its connected channels.

Squelch

ModeBehaviour
OffPass all signals
ManualOpen above a fixed level; lower thresholds open more easily
AutoOpen a chosen number of dB above the measured noise floor

The level meter marks the opening threshold. Auto learns during quiet periods, so a continuous signal can be mistaken for noise. Once open, the floor cannot rise and suppress a long transmission. Returning to Manual restores the previous manual threshold.

NFM also supports tone squelch:

SettingBehaviour
DetectReport CTCSS or DCS without gating audio
CTCSSOpen only for the selected tone
DCSOpen only for the selected code

Compander applies 2:1 audio expansion for links using matching compression. Leave it off for ordinary NFM. Expansion stops 20 dB below the reference level; sub-audible tones are excluded from level tracking.

Audio processing

The Audio block processes stages in this order. All are off by default except AM and SSB AGC.

StageEffect and controls
BlankerRemoves IQ impulses before the channel filter. Lower thresholds remove more impulses but can also damage the wanted signal.
De-clickRemoves short audio impulses after demodulation. Detection compares each sample with the surrounding level and neighbours; width is set by mode.
PassbandSets low and high audio cutoffs. Narrow the range to the audio you need.
NotchesRemoves up to four selected frequencies, each with an adjustable width.
Auto notchSuppresses steady carriers without manual frequency selection.
DenoiseTracks the noise floor in each spectral bin and attenuates bins without a detected signal. Strength ranges from no attenuation at 0 to 20 dB at 100. Continuous carriers can be treated as noise.
AGCLevels audio. Slow suits SSB speech, fast suits tuning, and medium provides an intermediate response.

Blanker acts on IQ before filtering to reduce impulse ringing. The remaining stages process audio.

Identifying a signal

Add Signal identifier and select a span up to 192 kHz wide. It reports detected transmissions, loudest first, with modulation, frequency, bandwidth, symbol rate, deviation, burst timing, and OFDM timing where measurable.

Candidates combine four kinds of evidence:

EvidenceContribution
WaveformModulation and measured timing
FrequencyLikely services for the band
BurstsDistinguishes signals with similar modulation
Decoder checksConfirms candidates through valid frames, checksums, or digital-voice sync

Confirmed candidates outrank waveform matches. Confirmation is available where an integrated decoder can run at the identifier's rate.

Interval sets the observation length. Threshold sets the required level above noise. Results settle across recent windows to reduce changes caused by one noisy measurement.

The identifier can recognise some wider signals from a partial slice, but cannot detect spread-spectrum signals below noise or resolve densely packed 50 Hz HF signals. For fixture comparisons, run cargo xtask ident-matrix.

Slow-scan television

Tune SSTV to the SSB carrier. It receives the 1000–2600 Hz video subcarrier above that frequency. Pictures take roughly 36 seconds to four and a half minutes, depending on mode.

SettingEffect
Follow VISRead the transmitted mode header automatically
Manual modeDecode using the selected mode when the header is missed or damaged
Slant correctionTrack line sync to correct sample-clock differences; normally leave enabled
Keep unfinished picturesSave partial images after a fade or interrupted transmission

Supported modes are Robot 36/72, Martin M1/M2, Scottie S1/S2/DX, PD50/90/120/180, and Wraase SC2-180.

Connect video to Video to watch reception line by line. Finished and retained partial images are saved as PNGs on the server, including while no client is connected. The channel panel lists them. Retention is 24 hours, capped at 512 images.

Surveying a DECT network

The dect channel surveys identity, configuration, and security signalling on one carrier. It reads the A-field, excluding call audio and user data in the B-field.

Use a receiver covering the DECT band with at least 2.304 MS/s. An RTL-SDR cannot reach the band; HackRF and SDRplay can. Carriers occupy 1.728 MHz.

SettingChoice
BandEurope: 1880–1900 MHz; US: 1920–1930 MHz
SideBase, Handset, or Both

European carrier 0 is 1897.344 MHz; carrier numbers descend in 1.728 MHz steps to 1881.792 MHz. US carriers count upward from 1921.536 MHz.

Bursts are grouped by slot timing to separate base stations sharing a carrier. Each A-field must pass its R-CRC check. Records include:

FieldContents
RFPIBase identity, access-rights class, operator or manufacturer, and cell identifiers
System informationCarrier, frequency, slot pair, transceiver count, available carriers, scan carrier
CapabilitiesSlot types, frequency control, handover, connectionless and higher-layer services
SecurityAdvertised DSAA authentication and DSC ciphering, observed encryption negotiation, key index when present
HandsetsPMIDs seen in encryption handshakes and the fixed part's FMID

Burst and error counts appear per station. Encryption is marked active after an observed grant. Advertised support does not prove encryption was used, and missing signalling does not prove a call was unencrypted.

Following a DMR trunk system

Add DMR trunk system, connect Device iq, and enter the control-channel frequency in MHz. Choose a system type or auto-detect. The node manages the required DMR decoders.

SystemChannel discovery
Tier III, including Capacity MaxLearns logical channel definitions and follows voice grants
Capacity PlusUses Repeater outputs or Search to find carriers sharing rest-channel changes; follows both timeslots
Hytera XPTUses the same discovery approach with XPT signalling

Following continues on the server without an open browser. Traffic channels must fit in the Device passband; out-of-range grants report a failure. Increase the sample rate or retune as needed.

Record calls buffers completed calls and audio in memory. Encrypted calls retain metadata only. Disable it to follow traffic without audio buffering.

Where decoder output goes

Events include source, frequency, and timestamp. Use Readout for current state, Decoder log for message history, Map for positions, and Export for saved rows.

Decoder-log retention is bounded. SSTV images use a separate picture store: the log records arrival, while GET /api/images serves the pictures.

Scanning

Use Scanner to search frequency lists or ranges and hold on activity. Scanning controls the radio's tuning, so stop it before retuning manually.

Build a scanner

  1. Add Scanner from + Node.
  2. Connect Scanner control to Device control.
  3. Enter frequency ranges or targets and choose a scan mode.
  4. Set the detection level and timing.
  5. Start scanning and watch the frequency, level, hit count, and status.

Configure detection

ModeBehaviour
TargetsHold on a listed frequency above the threshold
Close callFind the strongest carrier above the noise-floor margin within the searched span

For target ranges, match the step to the service's channel spacing. Smaller steps cover more frequencies per sweep and take longer. Use longer dwell times for weak signals or short digital bursts. Measurement bandwidth sets the slice used to measure activity.

The resume delay controls how long the scanner waits after activity ends before continuing.

Listen to a detected signal

Add a channel with the required mode and connect it to a Speaker. Select it under Listen on. When the scanner holds on a signal, it tunes that channel to the detected frequency.

Other channels retain their frequencies and receive only while the radio covers them. For continuous reception across a fixed band, use ordinary channels without a scanner.

Sweep methods

On supported hardware, the scanner can use the radio's firmware sweep. Otherwise it retunes through the targets. If firmware sweeping fails, it returns to retuning and reports the change. The Sweep readout shows the method in use.

Firmware sweeping interrupts ordinary reception while active. Channels are restored when normal reception resumes. Retuning sweeps need time for the radio and processing to settle.

Multiple radios

Use Also sweep with to share targets across eligible running radios. Each must be free of another scan or hunt and support a single tuning control. Receivers with independently tuned streams cannot participate in this scan workflow.

Propagation map

Map reception paths from FT8, FT4, and WSPR decodes and estimate a lower bound on maximum usable frequency (MUF). The map uses decoder events, with no additional signal processing.

Build one

  1. Add FT8, FT4, or WSPR channels and a Propagation map.
  2. Connect each channel's events output to the map.
  3. Connect GPS position to position. For a fixed station, enter coordinates in the GPS node's Fixed tab.

The map also loads six hours of decoder-log history for connected channels when opened.

Read the layers

LayerDisplay
ActivityEstimated reflection points weighted by decode count and age
MUFEstimated MUF lower bound per Maidenhead square
PathsGreat-circle paths by station and band, newest first; off by default

A message must contain a Maidenhead locator to add a path. Reports, RRR, RR73, and 73 usually contribute no new location data.

The model divides each path into hops and estimates reflection points. A single-hop reflection point is the midpoint. Points are grouped into Maidenhead squares and lose half their weight per Half-life, adjustable from five minutes to twelve hours. The table ranks squares by activity.

Measured MUF

Receiving a signal proves its path supported that frequency at that time. The model scales it to a 3000 km reference hop:

MUF(3000) ≥ f × M(3000) / M(D / hops)

Here, f is received frequency, D is path length, and M is the obliquity factor (sec φ) for a thin reflecting layer over a spherical Earth. At a 300 km layer height, M(3000) is about 3.28. A single 3000 km hop reports the received frequency; shorter hops scale upward.

Interpret the result as a model-dependent lower bound:

  • Paths under 500 km count as activity but do not contribute to MUF.
  • Missing decodes on a band do not establish that the band was closed.
  • Layer height changes the estimate. Use 300 km for F2 or 110 km for sporadic-E modelling.
  • A result below a forecast does not by itself disprove that forecast.

Comparing against the ionosonde network

Enable Ionosondes for GIRO and INGV soundings through prop.kc2g.com. The server caches results for fifteen minutes.

The map shows station MUF(3000 km) and compares local estimates with an inverse-distance interpolation of sounding sites within 3000 km. The footer reports squares above the forecast and the median difference.

Feed failures are reported while local decodes remain visible. Disable Ionosondes to stop sounding requests; basemap requests are separate.

Recording and playback

Choose a recorder for the signal you need to save:

NodeRecordsFormat
RecorderOne device IQ laneSigMF
Baseband recorderFiltered IQ from individual channelsSigMF
Audio recorderProcessed channel audio48 kHz, 16-bit PCM WAV
Time machineRecent device IQ plus live captureSigMF

SigMF stores samples in .sigmf-data and frequency, sample rate, timing, and annotations in .sigmf-meta. Keep both files together.

Record IQ

  1. Connect Device IQ to Recorder IQ.
  2. Start the radio and press Record.
  3. Press Stop to finish the files.

For multi-lane radios, the connected port selects the lane. Connect GPS position to include location metadata. The recorder shows elapsed time, bytes, dropped samples, and write errors.

A clean server shutdown finalises active recordings. Forcibly ending the process can leave an incomplete capture.

Record a channel's audio

Connect channel audio outputs to Audio recorder. Press Record beside each channel you want to save, then Stop to finish its WAV file.

Each channel gets a separate file after squelch, filtering, noise reduction, and AGC. Closed squelch writes silence to preserve timing. Mode and device-rate changes do not stop audio recording; removing a channel does. Headers update during capture so interrupted files remain playable through the last finalised data.

Audio and IQ recording can run independently at the same time.

Record a channel's baseband

Connect channel baseband outputs to Baseband recorder. Start and stop each channel separately.

Files contain IQ after frequency translation and filtering, before squelch. They preserve the channel frequency and sample rate and use less storage than full-device IQ. Completed files appear in the IQ library for playback.

A channel rebuild, including a mode or device-rate change, finishes the recording. Removing the channel also finishes it.

The IQ time machine

Capture a signal after it happens:

  1. Connect Device IQ to Time machine, with optional GPS position.
  2. Set a buffer duration and press Arm.
  3. Press Capture to save the buffer and continue recording live IQ.
  4. Press Stop to finish and remain armed, or Disarm to release the buffer.

Memory use is seconds × sample rate × 8 bytes, up to the server's 1 GiB limit. The display shows the required memory and maximum duration.

Sample rate is locked while armed. Retuning starts a new SigMF capture segment. The first timestamp belongs to the oldest buffered sample.

Storage

Recordings default to sdrmm/recordings under the platform data directory. Override it with:

sdrmm --recordings-dir /srv/sdrmm/recordings

Containers use /data/recordings; persist /data. The library rebuilds its IQ index from valid SigMF pairs on disk. Audio files live in the audio subdirectory.

Tags and notes

In Library → Recordings, choose Annotate and enter comma-separated tags and a note. Search matches names, tags, and notes; click a tag to search for it.

Annotations are stored in SigMF metadata as sdrmm:tags and core:description, so they survive downloads and index rebuilds. Duplicate tags merge while keeping the first spelling.

Download and export

Download IQ as the original SigMF archive or a stereo float WAV with I and Q as separate channels. WAV preserves samples but only part of the capture metadata. Channel audio provides the audio WAV downloads.

Downloads stream from disk. Failed exports abort instead of returning an apparently complete, truncated file.

Play a recording

Choose Open as source in Library → Recordings. Connect the new playback Device to channels and displays as you would a radio.

Playback uses the capture's centre frequency and sample rate. Use play, pause, stop, and seek to review the same samples with different decoder settings. Recording playback is available in release builds.

Decoder logs are separate

For messages, identifiers, and positions, connect channel events to Decoder log. Add Export for CSV or JSON. Logs store decoded results in SQLite; IQ files store the signal needed to decode again.

Network IQ export

Send live IQ to another analysis program over UDP or TCP.

Start an export

  1. Add Network IQ.
  2. Connect one Device IQ lane or one channel baseband output.
  3. Choose the protocol, sample encoding, and destination as host:port.
  4. Start the receiving program, then press Start export.
  5. Enter the displayed sample rate and centre frequency in the receiver.

A node accepts one source. Channel baseband exports filtered IQ at the channel's lower rate; each channel supports one export, independently of device-wide export.

Sample rate is locked during export. Retuning remains available, but you must update the receiving program's centre frequency. The display reports sent bytes, writes, capture overruns, and errors.

Wire contract

Payloads contain unframed, interleaved I, Q, I, Q, ... samples:

EncodingComponentsBytes per complex sampleGNU Radio input
cf32_leLittle-endian 32-bit float8Complex
ci16_leLittle-endian signed 16-bit integer4Short, then Interleaved Short to Complex
cu8Unsigned 8-bit integer, zero at 127.52RTL-SDR-style byte IQ

Encoding names follow SigMF datatypes. The stream carries no metadata, timestamps, or stream IDs.

UDP

Datagrams contain whole complex samples, with payloads up to 1,400 bytes. Configure GNU Radio's UDP Source with header None, matching data type, and payload size 1,400.

There are no sequence numbers. SDR-- reports loss before the socket but cannot detect missing or reordered network datagrams.

TCP

SDR-- connects to a listening receiver and writes a continuous byte stream. TCP preserves order and delivery, but a slow receiver can fill the bounded export queue. The export then stops and reports an error.

Access control

Exports can use substantial bandwidth and send to caller-selected destinations. Restrict server access to trusted operators with authentication and network controls.

Protocol compatibility

The output is raw IQ. Receiving software must accept the selected encoding and use the displayed rate and frequency. VITA 49 and DIFI framing are not supported.

rtl_tcp is a separate protocol for controlling remote radios. SDR-- supports it as a Device source.

Workspaces, templates, and presets

ToolSaves or suppliesUse
WorkspacePatch, rack, radio references and settings, band planA complete receiver layout
TemplateBuilt-in graph and radio configurationStart a common activity
PresetNamed workspace snapshot with bound radio settingsRestore a tuned setup
BookmarkFrequency and labelRetune a selected Device

Workspaces

Use the workspace name in the top bar to create, switch, or delete layouts. A new database starts with Device, Scope, and Speaker nodes; later workspaces start empty.

Changes save automatically on the server. All clients share the active workspace.

Export and import

The ↓ button downloads the workspace as JSON, including its name, patch, rack, band plan, and node settings. Database identity, revision, and undo history are excluded.

Import a workspace file creates and activates a new workspace. Duplicate names receive a copy number. Available radios open with the imported settings; missing radios remain disconnected and appear in the apply report. Select a replacement to use different hardware.

Unsupported newer file versions are rejected.

Undo and redo

Use the top-bar arrows or Ctrl/⌘ Z and Ctrl/⌘ Shift Z. Each workspace keeps 100 layouts on the server. Undo affects all clients and updates the running graph; for example, undoing an added channel closes it. A new edit after undo discards redo history.

Radio tuning is excluded from layout history.

Copy and paste

Select nodes, press Ctrl/⌘ C, then Ctrl/⌘ V. Copies appear beside the originals with their internal wires. Connections outside the selection are excluded, and copied Device nodes need a radio selected.

The clipboard works across workspaces for the lifetime of the browser tab.

Templates

Select a Device, then open Library → Templates. Applying a template retunes that radio, sets its sample rate, and adds channels and compatible outputs. The button names the target radio. Templates outside its tuning or sample-rate capabilities are disabled.

Templates cover broadcast, aviation, marine, paging, amateur, digital voice, ISM, and other services. Choose a setup for a signal available at your location.

Undo removes the added nodes but does not restore the previous radio frequency or sample rate.

Presets

Save a preset after arranging and tuning a workspace. Applying it restores the graph and radio settings using saved hardware identities. The apply report lists anything that could not be restored.

Presets are editable and stored on the server. Templates ship with the app and are read-only.

Bookmarks and band plans

A bookmark tunes the selected Device to a saved frequency without changing the graph.

Choose a band-plan region in the workspace menu. Bands searches allocations for that region. Enable the Scope allocation ruler to browse them: hover for details or click to tune, using the usual mode when available.

Automatic region selection uses browser location and requires HTTPS or localhost. Manual selection is always available.

Keyboard controls

Shortcuts act on the selected node or its connected Device. They are inactive while editing a text field or control. Press ? to open the reference in the app.

KeysAction
Left / RightTune down or up one step
Shift + Left / RightTune ten steps
[ / ]Choose a smaller or larger tuning step
fFocus the device dial; press Enter to type a frequency
, / .Select the previous or next channel
m / MCycle the selected channel's analog mode forward or backward
- / + or =Lower or raise squelch by 2 dB
sToggle squelch
19Select the nth node in the patch
pPin or unpin the selected node on the rack
vSwitch between Patch and Rack
Ctrl/⌘ ZUndo the last workspace change, for every connected client
Ctrl/⌘ Shift Z or Ctrl/⌘ YRedo
Ctrl/⌘ CCopy the selected nodes and the wires between them
Ctrl/⌘ VPaste them beside the originals
BackspaceDelete the selected node or wire
?Open the keyboard reference
EscapeClose an overlay or menu

Dial controls

Once the frequency dial has focus:

  • Left and Right choose a digit.
  • Up and Down change the selected digit.
  • Page Up and Page Down change the next larger place.
  • Home and End jump to the first or last place.
  • Enter opens direct frequency entry.

Select a Device before scrolling its dial. This keeps canvas scrolling from changing the frequency.

Troubleshooting

Start with Check hardware on an unbound Device node, or run:

sdrmm --doctor

The report checks drivers, libraries, discovery, USB permissions, and storage paths.

The page does not open

  • Find the address printed beside SDR-- ready in the server log.
  • On the server itself, try http://127.0.0.1:8080.
  • For remote access, bind to a reachable interface, for example sdrmm --bind 0.0.0.0:8080.
  • Check firewall rules and container port mappings.
  • Serve reverse-proxy deployments at the origin root; path prefixes are unsupported.

Use https:// when TLS is enabled.

The Linux window is blank or the waterfall is broken

Some WebKitGTK graphics drivers cause blank windows, frozen panels, or waterfall unavailable: no WebGL2 context. Try safe rendering:

SDRMM_LINUX_GRAPHICS=safe sdrmm-desktop
ValueBehaviour
autoDefault; disables DMABUF when the NVIDIA kernel module is loaded
safeDisables DMABUF and accelerated compositing
offLeaves graphics settings unchanged

Safe rendering may lower the waterfall frame rate. Existing WEBKIT_* variables take precedence; the startup log shows applied settings. See Tauri's graphics debugging guide for individual options.

If the window still fails, use the server in a browser:

sdrmm --bind 127.0.0.1:8080

A token is rejected

The browser stores the token for the server's origin. After an unauthorised response, it clears the saved value and prompts again. Enter the token currently configured on the server.

API clients use Authorization: Bearer <token>. WebSocket and browser download URLs can use ?token=....

A radio is missing

  1. Confirm the operating system detects it.
  2. Run sdrmm --doctor and resolve library or permission errors.
  3. Stop other SDR software that may hold the receiver.
  4. For SoapySDR radios, run SoapySDRUtil --find and check that modules match ABI 0.8.
  5. On Linux, install udev rules and grant the server account access. In containers, pass the owning group's numeric ID with group_add.

Desktop and portable packages use system SoapySDR installations. Containers include selected modules; Nix uses configured plugins. SDRMM_SOAPY_MODULE_PATH adds search directories. See Radios and hardware for package and receiver requirements.

An SDRplay receiver does not appear

Install SDRplay API and start sdrplay_apiService. The SDRplay API section in sdrmm --doctor reports library and service errors.

An RSPduo already in use lists only free operating modes. See SDRplay and container setup.

A device is present but a saved node is disconnected

The node waits for its saved radio identity. Check the serial number and variant. To replace the receiver, choose Forget this radio and select the new one.

Spectrum works but audio is silent

  • Connect channel audio to Speaker audio and start Speaker playback.
  • Click the page to allow browser audio.
  • Turn off squelch temporarily or lower its threshold.
  • Check that the channel covers the signal and fits inside the Device passband.
  • Check tab mute, system volume, and the selected audio output.

For broken or intermittent browser audio, use HTTPS or localhost. Plain LAN HTTP uses a fallback without AudioWorklet, which can stutter while the display is busy.

A decoder produces nothing

  • Confirm frequency, mode, baud rate, and protocol variant.
  • Check the Scope for a signal within the channel bandwidth.
  • Resolve any sample-rate warning. ADS-B needs 2–4 MS/s.
  • Connect events to the right output: Decoder log for frames, Readout for current state, Map for positions.
  • Adjust gain and check for clipping or overruns.
  • Check the mode's coverage and limitations.

Overruns or gaps

An overrun means samples were lost because capture outpaced processing. It can affect audio, spectrum, recordings, and decoding.

  • Lower the sample rate and close unused channels or displays.
  • Use a release build for regular reception.
  • Check CPU throttling and temperature on small computers.
  • Use wired Ethernet for high-rate network receivers.

Recordings do not appear

  • Confirm the server can write to --recordings-dir.
  • In Docker, check the persisted /data/recordings directory.
  • Stop active recordings to finalise metadata.
  • Check that each IQ capture has valid .sigmf-meta and .sigmf-data files.

Development server requests fail

Use cargo xtask dev to start the backend and configure the Vite API and WebSocket proxy. When starting them separately, use --dev-cors only for trusted local development.

Coherent arrays

An array processes several antenna signals together. Shared clocks determine whether those signals can support direction finding, beamforming, or passive radar.

TierShared hardwareSupported operations
phase_coherentReference clock and synthesizerBearings, beamforming, combining, passive radar
time_syncReference clockPassive radar; phase-dependent operations need calibration
noneNo shared referenceIndependent reception

A shared clock is required. Independent receivers drift apart even when tuned to the same frequency.

Radios that are already an array

Add one Device for a multi-lane receiver such as KrakenSDR, CR-8, RSPduo in dual-tuner mode, or a multi-channel SoapySDR radio. Its driver reports the coherence tier. Connect its iq, iq2, and subsequent outputs directly to processing nodes.

KrakenSDR

KrakenSDR has five lanes; KerberosSDR has four. Their shared clock provides time_sync coherence, but tuner phases change after every retune.

Set Cal source to Noise. SDR-- switches the built-in noise source on when calibration is needed, including after a retune or a press of Calibrate, then returns to the antennas. During calibration, the display shows noise source in and suppresses bearings.

Use fixed gain and equal-length antenna cables. Calibration pauses while scanning or hunting.

Radios you wired together yourself

Use an Array node for separate receivers physically connected to a shared clock.

  1. Add a Device for each receiver.
  2. Set matching sample rates and, for shared tuning, matching centre frequencies.
  3. Connect each Device's iq output to an Array input. Inputs expand as members are added.
  4. Set Wired as to match the hardware: shared clock, or shared clock and local oscillator.
  5. Connect the Array outputs to your processor, channels, or recorders.

Input order sets antenna numbering. Use fixed gain on every receiver; calibration can correct different fixed gains, but AGC changes invalidate it.

Tuning and membership

Tune and change sample rate through Array to keep members aligned. Independently tuned arrays provide a frequency control per lane. Disconnect the array before scanning or hunting.

Device nodes keep ownership of their radios and existing outputs. Removing Array leaves those running. Removing a member removes the dependent array. A disconnected member faults the array; processing reconnects when all members recover.

Calibration

Press Calibrate on the coherent processor. It measures delay, amplitude, and phase corrections for each lane.

Cal sourceRequired signal
SignalA strong signal received by every element
NoiseNoise injected into every lane, from the radio or an external splitter

A time_sync array needs injected noise or a specified pilot frequency to resolve phase after retuning. Built-in noise sources switch automatically. Inject an external reference before pressing Calibrate. On phase_coherent hardware, calibration corrects cable and other path differences.

The display reports solved, still solving, or phase unknown. Phase unknown means the reference is insufficient for bearings or beamforming.

Combining antennas

Connect one coherent source to a Combiner, then connect its beam output to an ordinary channel.

ModeEffect
CombineAlign and sum signals; two antennas can improve SNR by about 3 dB under suitable conditions
CancelUse the other antennas as noise references for the first antenna

For cancellation, place the wanted signal on the first antenna and receive the local noise on the others. Both modes need known relative phase; time_sync arrays require a pilot or noise reference.

Direction finding

A Direction finder estimates signal arrival direction from a coherent array. It shows a bearing, confidence, and angular response. Triangulation combines bearings into a position estimate.

Set up a finder

  1. Add a multi-lane Device or an Array.
  2. Add Direction finder. Set Geometry to your antenna layout and Elements to its count.
  3. Connect every lane to the matching iq, iq2, and subsequent inputs. All must come from one source.
  4. Set Offset and Bandwidth to cover the signal.
  5. Calibrate the array.
  6. Connect GPS position for map output or triangulation.

Geometry supports a circle with radius, a line with element spacing, or explicit element positions.

Algorithm

AlgorithmUse
BeamformerBroad response; useful with limited covariance data
MUSICSharper peaks; requires an accurate source count

For one transmitter, start with Sources set to one.

Read the compass

The compass shows response peaks, the selected bearing, and confidence. Bearings run clockwise from north at 0°. The strip below shows calibration quality per lane.

Phase unknown suppresses bearings. Check clock connections and the calibration reference.

Listen along a bearing

Connect beam to a channel. Follow bearing steers toward the current estimate. Fixed azimuth holds a chosen direction and starts at the beam's current bearing.

Crossing bearings from several finders

  1. Add Triangulation and connect the finders' events outputs.
  2. Give each finder its own position source, using GPS or fixed coordinates.
  3. View the estimate, error ellipse, guidance, and age of each bearing. Clear resets the estimate.

Bearings from different positions constrain the transmitter location. A finder alone provides bearings; position estimates and driving guidance require Triangulation.

On the map

Connect finder events to Map for bearing rays that fade with age. Connect Triangulation events for the estimated location, uncertainty ellipse, contributing stations, and next waypoint.

Guidance

For a long, narrow uncertainty ellipse, guidance suggests moving across the bearing to improve the intersection angle. Once the estimate converges, it suggests approaching the location.

The first converged fix emits an event that connected webhook, MQTT, or Matrix outputs can forward. Use field mode for phone guidance and navigation.

Passive radar

Passive radar compares a transmitter's direct signal with its reflections to measure echo delay and Doppler shift. Use two receiver lanes sharing a sample clock. A time-synced array is sufficient; relative phase calibration is unnecessary.

Set up the receiver

  1. Add a Device or Array with at least two time-synced lanes.
  2. Add Passive radar.
  3. Connect the antenna aimed at the transmitter to ref.
  4. Connect the surveillance antenna aimed at the area of interest to surv.
  5. Connect GPS position for map output.

Processing and settings

StagePurpose
ECACancel the direct signal and stationary clutter
CAFCompare reference and surveillance signals across delay and Doppler offsets
CFARDetect cells above their local background
ClusterMerge adjacent detections
TrackAssociate echoes across observations
SettingEffect
IntegrationLonger intervals can reveal weaker echoes, but motion can blur them
Range binsDelay extent of the display
Doppler spanFrequency-shift range searched

Reading the surface

The display plots range against Doppler and marks detections. Repeated observations receive a track number. A brief detection may be noise or an unconfirmed echo.

Echoes on the map

Enable Transmitter and enter its coordinates and frequency. With receiver position available, the map draws an ellipse of possible locations for each echo.

The measurement is bistatic range, the extra distance travelled by the reflected signal. One echo does not give a unique position or bearing. Tracks follow range and Doppler, not geographic coordinates. Without transmitter coordinates, no ellipse is drawn.

Use the Radar watch mission in field mode to view the surface and tracks on a phone.

Field mode

Field mode provides phone controls for signal hunting, direction finding, and passive radar. Prepare the active workspace on a desktop, then connect the phone to the same server.

Connect your phone

Open Library → Field and scan the QR code. The link includes the server token, which field mode stores and removes from the address bar. When the desktop uses localhost, the QR code offers a reachable LAN address.

You can also open /field directly from a browser that can reach the server.

Missions

Available missions depend on the active workspace. Each controls one node.

MissionRequired nodeControls
Fox huntSignal huntLevel, rising/falling indication, variable-rate clicks, start/stop
DF driveDirection finderCompass, guidance, map
Radar watchPassive radarRange–Doppler surface and tracks

Fullscreen and screen wake lock are available where supported by the browser.

Driving to a signal

DF drive orients the compass using GPS course over ground, not the phone's compass sensor. A connected Triangulation node provides crossing and approach guidance. Without it, the bearing display still works.

Turn-by-turn

Configure a routing backend for routes, next manoeuvres, and distances. Spoken directions become available after a touch interaction. The routing key stays on the server.

Nav modeDestination
AutoCrossing waypoint until convergence, then the location estimate
DirectCurrent location estimate
OffHeading guidance only

Routes update when you leave the route, the target moves, or the guidance phase changes. If routing is unavailable, the screen reports the reason and keeps heading guidance.

Navigate in Maps opens the target in the phone's navigation app. Open it again when the target changes; the browser cannot update an already open native navigation session.

Maps without internet

Place basemap.pmtiles beside the server database for an offline basemap. Otherwise, field mode uses the online style. Without either map, bearings, routes, and markers appear on a blank background.

Configuration and security

sdrmm serves the interface, receiver engine, REST API, WebSocket, and MCP in one process. By default it listens on 0.0.0.0:8080 without authentication.

Command-line options

sdrmm [OPTIONS]
OptionDefaultPurpose
--bind <ADDRESS>0.0.0.0:8080Address and port for HTTP and WebSocket traffic
--db <PATH>Platform data directorySQLite database for workspaces, presets, bookmarks, recording index, and decoder log
--recordings-dir <PATH>Platform data directoryDirectory containing SigMF recording pairs
--token <TOKEN>NoneRequire one shared bearer token for API, WebSocket, and MCP requests
--tls-cert <PATH>NonePEM certificate chain to serve HTTPS with; requires --tls-key
--tls-key <PATH>NonePEM private key for that chain
--tls-self-signedOffServe HTTPS with a self-signed certificate kept beside the database
--tls-name <NAME>Discovered addressesName or address that certificate must cover; repeatable
--routing-backend <NAME>open-route-serviceRouting service: open-route-service or graph-hopper
--routing-url <URL>The backend's own serviceBase URL, for a self-hosted instance
--routing-key <KEY>NoneAPI key for that service
--dev-corsOffAllow a separate frontend development origin
--doctorOffPrint environment diagnostics and exit
--doctor-ratesOffProbe connected receivers' sample rates and exit
--helpShow CLI help
--versionShow the build version

Use absolute database and recording paths for services so storage does not depend on the working directory.

Persistent data

Back up the database and recordings for a complete installation:

/srv/sdrmm/
├── sdrmm.db
└── recordings/
    ├── <capture>.sigmf-meta
    └── <capture>.sigmf-data

The database holds settings and decoded history; recording files hold IQ and audio. Stop the server before copying its database, or use SQLite's backup mechanism. Finish recordings before copying their files.

Logging

Set the RUST_LOG filter to adjust logging:

RUST_LOG=info sdrmm
RUST_LOG=sdrmm=trace,info sdrmm

Use trace logging for short diagnostic sessions; it can produce substantial output.

Shared-token authentication

Set a long random token before allowing untrusted clients to reach the server:

export SDRMM_TOKEN='replace-with-a-long-random-secret'
sdrmm

--token sets the same value, but the environment variable keeps it out of the process arguments. The browser prompts for the token and stores it for that origin.

REST and MCP clients send:

Authorization: Bearer replace-with-a-long-random-secret

WebSocket handshakes and browser downloads can use ?token=.... The application shell and GET /api/auth stay public so clients can load the login prompt. Other API, documentation, WebSocket, and MCP routes require the token.

All authenticated clients have the same permissions, including changing the active receiver. There are no per-user accounts or read-only roles.

HTTPS

Use a certificate chain and matching private key:

sdrmm --tls-cert /etc/sdrmm/fullchain.pem --tls-key /etc/sdrmm/privkey.pem

Both files must be PEM. Put the leaf certificate first, followed by intermediates. Keys may use PKCS#8, PKCS#1, or SEC1. Missing, unreadable, or mismatched files prevent startup.

For a local setup without a certificate authority:

sdrmm --tls-self-signed

The certificate covers localhost, loopback, and discovered LAN addresses. It is saved under tls beside the database and reused until renewal is needed. Compare the logged SHA-256 fingerprint when first accepting it on a client.

For containers, NAT, or a DNS name, specify the addresses clients actually use:

sdrmm --tls-self-signed --tls-name radio.example --tls-name 192.168.1.20

SDRMM_TLS_NAMES accepts the same comma-separated list. Explicit names replace discovered addresses; loopback remains covered. Changing names regenerates the certificate. Stable names avoid repeated certificate changes when a container address changes.

Prefer an authority-issued certificate where available. Self-signed certificates require clients to establish trust manually.

Network security

Use HTTPS to protect tokens and receiver traffic. For a reverse proxy:

  • Bind SDR-- to loopback or firewall its direct port.
  • Serve the application at the origin root.
  • Forward WebSocket upgrades for /api/ws.

An authenticated tunnel is another option. Rotate the shared token if it may have leaked.

Turn-by-turn routing

Field mode uses OpenRouteService or GraphHopper for driving routes. Set --routing-key, choose the service with --routing-backend, and use --routing-url for a self-hosted instance. The key stays on the server and is sent in an authorization header.

Without a reachable backend, field mode reports the problem and keeps heading guidance. Navigate in Maps can open the target in the phone's navigation app.

Development CORS

--dev-cors permits requests from a separate frontend origin during development. Leave it off for production and when the interface is served directly by sdrmm.

Containers and remote radios

Run SDR-- beside the radio and connect through a desktop browser. The server sends audio, decoded data, and display frames over the network, keeping raw device IQ local.

Docker Compose

On Linux:

git clone https://github.com/Newspicel/sdrminusminus.git
cd sdrminusminus
docker compose pull
docker compose up -d

Open http://<host>:8080. The supplied service restarts unless stopped and keeps data in the sdrmm-data volume. Use :nightly instead of :latest only to test unreleased changes.

USB devices

The supplied service includes:

devices:
  - /dev/bus/usb:/dev/bus/usb
device_cgroup_rules:
  - "c 189:* rmw"
group_add: ["46"]

The bus mapping exposes USB devices. The cgroup rule allows devices to reconnect with new minor numbers. Host udev rules still control access.

Set group_add to the numeric group IDs owning your radio nodes. 46 is commonly plugdev on Debian and Ubuntu. Check on the host:

stat -c '%g %G %a' /dev/bus/usb/*/*

Install the receiver's udev rules and use the reported group. An unconfigured node may belong to group 0. Check hardware reports inaccessible nodes and ownership from inside the container.

SoapySDR modules

The image includes the SoapySDR core and bladeRF, LimeSDR, and SoapyRemote modules. Built-in drivers cover other supported radios; see hardware requirements.

To add a module, build a derived image:

FROM ghcr.io/newspicel/sdrminusminus:latest
USER root
RUN apt-get update \
    && apt-get install -y --no-install-recommends soapysdr-module-audio \
    && rm -rf /var/lib/apt/lists/*
USER sdrmm

Replace the example module with the one you need.

SDRplay receivers

Install the vendor API on the host and keep sdrplay_apiService running. Add its library and shared IPC to the service:

volumes:
  - sdrmm-data:/data
  - /usr/local/lib/libsdrplay_api.so.3:/usr/local/lib/libsdrplay_api.so.3:ro
ipc: host

The API needs host shared memory to communicate with its service. ipc: host also exposes other host IPC objects, so use this setup only with a trusted image and host. See SDRplay for library diagnostics.

Data and authentication

The image stores its database and recordings under /data. Keep that volume when replacing the container. Supply a token through a protected .env file:

SDRMM_TOKEN=replace-with-a-long-random-secret

Add this to the service and keep .env out of version control:

services:
  sdrmm:
    env_file: .env

Back up the volume. Configure HTTPS and access control for remote use.

HTTPS

Mount a certificate directory read-only and pass the certificate options:

volumes:
  - sdrmm-data:/data
  - /srv/sdrmm/certs:/certs:ro
command: ["--bind", "0.0.0.0:8080", "--tls-cert", "/certs/fullchain.pem", "--tls-key", "/certs/privkey.pem"]

The container runs as UID 10001; grant it read access to both files. Certificate symlink targets must also be available inside the container.

For a self-signed certificate, specify the hostname clients use:

command: ["--bind", "0.0.0.0:8080", "--tls-self-signed", "--tls-name", "radio.example"]

The certificate persists in /data/tls. Back it up with the database to preserve client trust. The bundled health check supports HTTP and HTTPS.

Run the portable server as a service

Use a dedicated account with USB access and explicit storage paths:

/usr/local/bin/sdrmm \
  --bind 0.0.0.0:8080 \
  --db /var/lib/sdrmm/sdrmm.db \
  --recordings-dir /var/lib/sdrmm/recordings

Configure your service manager to send a normal termination signal so active recordings can finish. Use SDRMM_TOKEN for authentication and the TLS options for HTTPS.

Connect to a network receiver

On Device, open Network, choose a protocol, and enter its address:

ProtocolDefault port
rtl_tcp1234
SpyServer5555
AD936x / iiod30431

Use a hostname, IPv4 address, or bracketed IPv6 address, with an optional port. The workspace saves the endpoint as the receiver identity. Use only the sample rate you need and watch overruns; network IQ can require substantial bandwidth.

SoapyRemote

Install SoapyRemote where SDR-- runs and start SoapySDRServer beside the hardware. Choose the remote receiver from the normal Device search. The container includes the module; desktop and portable packages use the host's installation.

Browser deployment

Serve the interface at the origin root with /api/*, /api/ws, and /mcp on the same origin. A reverse proxy must forward WebSocket upgrades.

Use HTTPS or localhost for browser location and AudioWorklet playback. Plain LAN HTTP can play audio through a fallback, but busy displays may interrupt it. Manual band-region selection remains available without browser location.

API and automation

REST, WebSocket, and MCP control the same live receiver as the interface. Changes affect every connected client.

Interactive reference

EndpointPurpose
/api/docsSwagger UI
/api/openapi.jsonOpenAPI schema
/api/wsWebSocket
/mcpMCP over streamable HTTP

The checked-in OpenAPI schema can generate clients without a running server. Swagger lists request bodies, responses, and errors.

When authentication is enabled, these endpoints require the shared token:

curl \
  -H "Authorization: Bearer $SDRMM_TOKEN" \
  http://receiver.local:8080/api/state

REST resources

AreaRoutes and operations
Discovery and state/api/devices, /api/channeltypes, /api/state, /api/clients
Live receiver/api/devicesets, settings, channels, scanning, recording, playback
Workspaces/api/workspaces, activate, apply, undo, redo, export, import
Saved setups/api/templates, /api/presets, /api/bookmarks
Data/api/decoderlog, exports, /api/recordings, downloads
Reference/api/bandplan/regions, /api/about, /api/doctor

Errors use JSON with error and optional detail fields.

WebSocket events and streams

The WebSocket carries commands, state invalidations, decoder events, scanner progress, and binary spectrum, audio, and video. Stream IDs belong to one connection; do not reuse them across clients.

Refetch durable state through REST after an invalidation. High-rate samples and events arrive on the stream. Use the generated types and existing web client as the protocol reference.

MCP

Connect an MCP client to http://<server>:8080/mcp, adding the bearer header when required. Tools cover:

  • Device discovery, opening, closing, and tuning.
  • Channel creation and removal.
  • Scanning, recording, decoded history, and spectrum snapshots.
  • Measurement tools, antenna dimensions, and NanoVNA discovery, sweeps, and calibration.

MCP operates the shared live receiver with the same permissions as the interface.

Generated-code workflow

Shared types live in crates/wire. After changing API types or server routes, run:

cargo xtask codegen

Commit openapi.json and the generated TypeScript declarations under web/src/generated. cargo xtask check detects drift from the Rust source.

Build and test

Build the web interface, then the Rust server. Local development and CI use the same cargo xtask commands.

Prerequisites

ToolRequirement
RustInstall through rustup; use rust-toolchain.toml
Node26
pnpm11; exact version in web/package.json
Native build toolsC/C++ compiler and CMake

On Debian or Ubuntu:

sudo apt-get update
sudo apt-get install -y build-essential cmake

On macOS:

brew install cmake

Cargo installs the pinned nightly compiler and components automatically. The workspace uses -Zpolonius=next, so the pinned toolchain is required. SoapySDR loads at runtime and needs no build-time development package.

Build and run

git clone https://github.com/Newspicel/sdrminusminus.git
cd sdrminusminus
pnpm --dir web install --frozen-lockfile
pnpm --dir web build
cargo run -p sdrmm

Open http://localhost:8080. Distributable builds embed web/dist; build the frontend first. Backend-only builds can compile with a placeholder interface if that directory is missing.

For frontend hot reload and automatic backend restarts:

cargo xtask dev --watch

Open http://localhost:5173. Vite proxies API and WebSocket traffic to port 8080. Omit --watch to leave backend restarts manual.

Backend feature flags

The server defaults enable soapy, sdrplay, cr8, rtlsdr, hackrf, airspy, airspyhf, ad936x, net-client, and gpu-fft. Packaged releases use a selected subset; see hardware requirements.

Disable hardware backends:

cargo run -p sdrmm --no-default-features

Keep direct rtl_tcp and SpyServer clients:

cargo run -p sdrmm --no-default-features --features net-client

SigMF playback remains available in both builds.

Development signal sources

Debug builds expose the signal generator and synthetic array/transceiver sources. Release builds hide them and keep recording playback available.

To test audio in a debug build, select Signal Generator (virtual) on Device, connect an NFM channel at 300 kHz above the Device centre, then connect its audio to Speaker. Starting playback produces a 1 kHz tone.

Local gates

CommandWhat it runs
cargo xtask checkToolchain checks, generated-data checks, Rust format and Clippy, frontend format/lint/type-check, release-shaped builds, web build, codegen drift
cargo xtask testRust and frontend unit/integration tests using virtual devices
cargo xtask smokePlaywright against a real sdrmm process and the virtual signal generator
cargo xtask auditcargo-deny and the RustSec advisory database
cargo xtask desktopTauri desktop compile gate without building installers
cargo xtask sanitizeDecoder tests with the vendored C under AddressSanitizer and UndefinedBehaviorSanitizer
cargo xtask fuzzlibFuzzer against every decoder, channel settings, and the dPMR vocoder chain

Install the tools needed for your checks:

cargo install --locked cargo-nextest cargo-deny cargo-fuzz
pnpm --dir web exec playwright install chromium

test needs cargo-nextest, audit needs cargo-deny, and fuzz needs cargo-fuzz. Sanitizer tests also require clang. Automated tests use virtual devices and never require real hardware.

Generated files

Regenerate and commit outputs when their sources change:

Source changeCommandGenerated output
REST routes or wire typescargo xtask codegenopenapi.json, web/src/generated/schema.d.ts
Dependency lockfilescargo xtask licensesTHIRD_PARTY_NOTICES.md, embedded notices JSON
web/pnpm-lock.yamlcargo xtask nix-hashThe pnpm store hash in packaging/nix/package.nix
A git dependency's revcargo xtask nix-hashThe cargo git hashes in packaging/nix/package.nix
Decoder reference signalscargo xtask fixturesSigMF pairs under fixtures/
Band-plan source importscargo xtask bandplanEmbedded regional tables
assets/icon.svgcargo xtask iconsDesktop and web icon variants

cargo xtask check detects stale contracts and metadata. nix-hash uses Nix on Linux or a nixos/nix container elsewhere. It updates the pnpm store hash and Cargo git-dependency hashes. Local checks compare lockfile digests and commits; Nix CI verifies the hashes by building.

Desktop prerequisites

The Tauri app is outside the default workspace members. Linux needs WebKitGTK and desktop integration libraries. Use cargo xtask desktop for the compile gate and follow Desktop bundles to create installers.

Before opening a pull request

Format, lint, check, and test the affected parts. For documentation, run mdbook build docs and check local links and anchors. For code, the full gates are:

cargo xtask check
cargo xtask test

Add browser, desktop, DSP performance, or hardware validation when the change needs it. See Contributing.

Architecture

The desktop app and headless binary share the Rust server and receiver engine. Both serve the same React interface.

React client ↔ REST / WebSocket / MCP ↔ Server control plane
                                              ↓ commands
Radio / network / recording → DSP engine → audio, events, spectrum, IQ

Crate boundaries

CrateResponsibility
sdrmm-dspAllocation-free signal-processing primitives; no I/O or internal project dependencies
sdrmm-modemReusable modem algorithms depending only on DSP
sdrmm-modem-test-supportModem measurement catalogs, simulations, and baseline tooling; tests and developer tools only
sdrmm-wireShared settings, DTOs, events, patch graph, and OpenAPI schemas
sdrmm-deviceHardware-independent device traits, capabilities, settings, and registry
sdrmm-device-virtualSignal generators and SigMF playback
sdrmm-device-rtlsdrNative RTL-SDR driver
sdrmm-device-airspy, sdrmm-device-airspyhfNative Airspy drivers
sdrmm-device-hackrfNative HackRF driver
sdrmm-device-ad936xAntSDR, PlutoSDR and other AD936x boards, speaking iiod over ethernet or USB
sdrmm-device-soapyLocal hardware through SoapySDR
sdrmm-device-sdrplaySDRplay RSP receivers through the vendor API, loaded at runtime
sdrmm-device-rtltcpDirect rtl_tcp client
sdrmm-device-spyserverDirect SpyServer client
sdrmm-device-cr8Dragon Labs CR-8 through the vendor SDK, loaded at runtime
sdrmm-device-arrayAlready-open streams composed as logical lanes; no hardware opens
sdrmm-channelsAnalog demodulators, protocol decoders, and their descriptors
sdrmm-recorderSigMF writing, reading, scanning, and export
sdrmm-engineDevice supervision, channelization, scanning, streams, recording, and state snapshots
sdrmm-serverREST, WebSocket, MCP, persistence, band plans, auth, and embedded assets

apps/sdrmm owns CLI configuration and process lifetime. apps/desktop starts the server on an ephemeral loopback port and opens a Tauri WebView. Both isolate SoapySDR discovery in a short-lived child process.

One source of truth for wire types

Define REST bodies, WebSocket messages, settings, and patch types in crates/wire. OpenAPI schemas derive from those types; cargo xtask codegen generates TypeScript declarations.

The client reads device capabilities, channel descriptors, and the node palette from the server, keeping controls aligned with the running build.

Data plane and control plane

The DSP path uses command queues for settings and bounded snapshots or buffers for output. It performs no I/O, locking, allocation, or async work in hot processing.

The control plane owns HTTP handlers, SQLite, workspace reconciliation, subscriptions, recording indexes, and serialization. It may allocate or block as needed.

Spectrum, audio, and video use binary WebSocket frames; browser audio is Opus-compressed. Decoder events use typed JSON. Durable state is fetched through REST after WebSocket invalidations.

Coherent processing

Each capture block carries its first sample index, including gaps from reported hardware loss. Coherent processing taps each lane into a ring and selects the sample range common to all lanes. After a gap, it advances to the next shared index before applying calibrated delays and weights.

Beamforming sums weighted lanes into a normal capture ring. Channels, recorders, and scopes consume that beam through the ordinary single-lane path.

An Array node combines streams already owned by Device nodes. device-array provides logical ingress lanes; the engine forwards corrected IQ, coordinates tuning, and handles member recovery. The array adapter never opens hardware.

Media and recording outputs cross preallocated single-producer/single-consumer buffer pools. Workers allocate transport payloads and publish them. Full queues never block DSP: media loss is reported and recordings fail explicitly. Shutdown drains pending buffers. Some decoder algorithms still allocate variable-sized results.

channels depends on dsp, modem, and wire. Shared modem algorithms belong in modem. Allocation, throughput, and modem measurement tooling belongs in test-support crates outside the application dependency graph. cargo xtask check enforces boundaries; cargo xtask perf checks DSP throughput, allocation, decoder searches, and engine publication.

Workspaces and live engine state

The workspace graph describes desired state. Applying it binds saved Device references to discovered radios, restores settings, and reconciles channels and engine objects.

Saved references use backend, serial, key, and variant identity. Engine IDs are temporary and never stored in the graph. Disconnected radios retain their nodes and settings until reconnection.

Failure and backpressure

Queues are bounded. Overruns, dropped frames, recording faults, truncated exports, WebSocket lag, and reconnection state surface to clients. Slow consumers cannot block capture or grow memory without a limit.

Testing layers

LayerCoverage
DSPAnalytic and golden vectors, allocation and throughput gates
DecodersRecorded IQ and expected output, plus generated vectors
EngineEnd-to-end virtual-device tests
ServerHandlers, persistence, streams, authentication, OpenAPI, codegen drift
ClientUnit tests and browser smoke flows

CI builds release configurations without enumerating host radios. Test at the narrowest layer that proves the behaviour, adding end-to-end coverage for cross-layer workflows.

Standard tables and their provenance

Some decoder constants come directly from specifications:

ConstantsLocation
DAB puncturing and protection profilescrates/channels/src/dab/protection.rs
DAB phase referencecrates/channels/src/dab/ofdm.rs
DVB-S puncturing and Reed–Solomon parameterscrates/channels/src/datv/dvbs.rs
DVB-S2 LDPC accumulator addressescrates/channels/src/datv/dvbs2/tables/
VL-SNR header sequencecrates/channels/src/datv/dvbs2/vlsnr.rs

Sources are ETSI EN 300 401 (DAB), TS 102 563 (DAB+), EN 300 421 (DVB-S), EN 302 307-1 and -2 (DVB-S2/S2X), TS 102 606 (GSE), and ES 201 980 (DRM).

Table values were cross-checked against welle.io (GPL-2.0-or-later) and GNU Radio's gr-dtv (GPL-3.0-or-later). This attribution concerns table verification, not copied decoder code. The 7,378 DVB-S2 accumulator addresses were transformed mechanically. The VL-SNR 896-bit seed and Walsh–Hadamard rows were transcribed from the standard; their sixteen generated patterns match gr-dtv's tables.

Tests check independent properties such as puncturing density, polynomial roots, published CRC values, and parity checks on encoded words. These checks help detect transcription errors.

Release process

Tagged releases publish portable servers, desktop installers, signed update bundles, and container images. A scheduled workflow updates the rolling nightly when main changes.

Versioning

The root workspace version is the source of truth. Set it with:

cargo xtask set-version 1.2.3

Stable tags use v<major>.<minor>.<patch>. Nightlies use the UTC date as YY.M.D. Windows MSI requires major and minor to fit in eight bits and patch in sixteen bits; prerelease suffixes are unsupported. The task validates these limits.

Portable archives

cargo xtask dist
cargo xtask dist --target aarch64-unknown-linux-gnu

The task installs a missing Rust target, builds the frontend and release binary, verifies embedded assets, and writes a .tar.gz or .zip under dist/ with README and license files.

Archives load SoapySDR at runtime without linking or bundling it. Verify startup on a clean machine both with and without a system SoapySDR installation.

Desktop bundles

Run the compile gate:

cargo xtask desktop

To create installers, install the Tauri CLI:

cargo install --locked tauri-cli
cargo xtask desktop --bundles dmg

Use deb,appimage on Linux and msi,nsis on Windows. Installers use system SoapySDR at runtime.

The AppImage bundles the GStreamer WebKit plays audio through, and bundles only what the build machine has: an appimage build needs patchelf and the GStreamer plugin packages installed, or the app it produces is silently mute.

Desktop updates

The app checks the latest stable GitHub release at startup. Update archives use a Tauri updater signature separate from platform code signing. Preserve the private updater key; installed clients trust its compiled public key.

Without a local signing key, the bundle task uses --no-sign. Those installers cannot serve as application updates. Release CI requires signatures and builds the update manifest:

cargo xtask updater-manifest \
  --version 1.2.3 \
  --dir dist/release \
  --base-url https://github.com/Newspicel/sdrminusminus/releases/download/v1.2.3

Containers

Releases publish Linux amd64 and arm64 images:

ghcr.io/newspicel/sdrminusminus:<version>
ghcr.io/newspicel/sdrminusminus:latest

Nightlies update only :nightly. Smoke tests check the binary, SoapySDR modules, server startup, and embedded frontend.

Homebrew tap

The release workflow updates the sdrmm formula and sdrminusminus cask in Newspicel/homebrew-tap after publishing stable downloads:

cargo xtask homebrew-tap \
  --version 1.2.3 \
  --sums SHA256SUMS \
  --repo Newspicel/sdrminusminus \
  --out ../homebrew-tap

The generator checks required artifacts against SHA256SUMS. The tap job needs a writable HOMEBREW_TAP_TOKEN; without it, the job is skipped. Other release jobs continue.

Validate generator changes:

brew style newspicel/tap
brew audit --strict --online newspicel/tap/sdrmm
brew audit --strict --online --cask newspicel/tap/sdrminusminus

Building a pull request

Add build_nightly to a same-repository pull request to build the full release matrix. The run uploads portable archives, installers, update bundles, latest.json, and container tarballs. A pull-request comment links to the artifacts.

This rehearsal publishes no release, tag, or registry image. Containers can be imported with docker load. The build uses the manifest version, currently 0.0.0.

Each push rebuilds and cancels the older run. Remove the label to stop. Forks cannot use this workflow because the bundle jobs require signing secrets.

Release checklist

  1. Run cargo xtask check, test, smoke, and audit.
  2. Run cargo xtask desktop and build the container.
  3. Check generated API, license, fixture, icon, and band-plan outputs.
  4. Validate hardware with the candidate package, including reconnect and recording.
  5. Confirm updater and platform signing credentials.
  6. Tag the reviewed commit and check every artifact job.
  7. Install a published artifact and run sdrmm --version and sdrmm --doctor.

Manual workflow dispatch rehearses the artifact matrix without publishing a GitHub release.