|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Working Norms |
| 6 | + |
| 7 | +- **Keep this file updated** as changes are made to the codebase. |
| 8 | +- **Never guess or make assumptions** — ask clarifying questions when requirements are unclear. |
| 9 | +- **All code changes should include unit tests.** When possible, write tests first and implement code to make them pass (TDD). |
| 10 | +- **After writing code, review comments** and remove any that don't explain non-obvious behavior — don't comment what the code already says. |
| 11 | + |
| 12 | +## Development Environment |
| 13 | + |
| 14 | +The repo is set up for development in VS Code using a devcontainer (`.devcontainer/`). When working inside the devcontainer, all compile, test, and run commands must be executed inside the container. |
| 15 | + |
| 16 | +## Wiki Documentation |
| 17 | + |
| 18 | +User-facing documentation lives in a separate repo: https://github.com/rtl-airband/RTLSDR-Airband/wiki |
| 19 | + |
| 20 | +Flag when code changes require wiki updates and provide suggested content — do not edit the wiki directly. |
| 21 | + |
| 22 | +## Code Review Guidelines |
| 23 | + |
| 24 | +When reviewing code: |
| 25 | +- Reference specific files and line numbers (`src/foo.cpp:42`) |
| 26 | +- Start with architecture-level concerns before line-level feedback |
| 27 | +- Consider SDR/DSP domain context (signal processing constraints, real-time threading, buffer management) |
| 28 | +- Verify the testing approach covers the behavior being changed |
| 29 | +- Structure feedback clearly: separate blocking issues from suggestions |
| 30 | +- Be pragmatic — prefer working correct code over theoretical perfection |
| 31 | +- Check for consistency with surrounding code style and conventions |
| 32 | + |
| 33 | +## Project Overview |
| 34 | + |
| 35 | +RTLSDR-Airband is a C++ SDR (Software-Defined Radio) application that receives analog radio voice channels from SDR devices (RTL-SDR, SoapySDR, MiriSDR) and produces MP3 audio streams for Icecast, file recording, UDP, and PulseAudio. |
| 36 | + |
| 37 | +## Build Commands |
| 38 | + |
| 39 | +Dependencies: libconfig++, libmp3lame, libshout, libfftw3f, librtlsdr, libsoapysdr, libpulse. Install via `.github/install_dependencies`. |
| 40 | + |
| 41 | +```bash |
| 42 | +# Standard debug build with unit tests |
| 43 | +cmake -B build_Debug -DCMAKE_BUILD_TYPE=Debug -DBUILD_UNITTESTS=TRUE |
| 44 | +cmake --build build_Debug -j4 |
| 45 | + |
| 46 | +# Release build with NFM and SoapySDR |
| 47 | +cmake -B build_Release -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DSOAPYSDR=ON |
| 48 | +cmake --build build_Release -j4 |
| 49 | + |
| 50 | +# Run unit tests |
| 51 | +./build_Debug/src/unittests |
| 52 | +./build_Release/src/unittests |
| 53 | + |
| 54 | +# Run the binary |
| 55 | +./build_Debug/src/rtl_airband -c /path/to/config.conf |
| 56 | +./build_Release/src/rtl_airband -c /path/to/config.conf |
| 57 | +``` |
| 58 | + |
| 59 | +Key CMake flags (all in `src/CMakeLists.txt`): |
| 60 | + |
| 61 | +| Flag | Default | Purpose | |
| 62 | +|------|---------|---------| |
| 63 | +| `NFM` | OFF | Enable Narrow FM demodulation | |
| 64 | +| `PLATFORM` | `native` | Optimization target: `native`, `generic`, `rpiv2` | |
| 65 | +| `RTLSDR` | ON | RTL-SDR driver | |
| 66 | +| `MIRISDR` | ON | Mirics SDR driver | |
| 67 | +| `SOAPYSDR` | ON | SoapySDR (vendor-neutral) driver | |
| 68 | +| `PULSEAUDIO` | ON | PulseAudio output | |
| 69 | +| `BUILD_UNITTESTS` | OFF | Build Google Test unit tests | |
| 70 | +| `BCM_VC` | OFF | Broadcom VideoCore GPU FFT (RPi v2 only) | |
| 71 | + |
| 72 | +## Code Formatting and Pre-commit |
| 73 | + |
| 74 | +Uses clang-format v14 with Chromium style (indent=4, column limit=200, config in `.clang-format`). |
| 75 | + |
| 76 | +```bash |
| 77 | +# Install pre-commit hooks (once, after cloning) |
| 78 | +pre-commit install |
| 79 | + |
| 80 | +# Run all pre-commit hooks manually |
| 81 | +pre-commit run --all-files |
| 82 | + |
| 83 | +# Format C++ source files manually (also used by CI) |
| 84 | +./scripts/reformat_code |
| 85 | +``` |
| 86 | + |
| 87 | +Pre-commit hooks (`.pre-commit-config.yaml`) run on every commit and check: |
| 88 | +- YAML/JSON validity, trailing whitespace, EOF newlines, shebang permissions, large files, merge conflict markers, private keys |
| 89 | +- clang-format on all `src/*.cpp` and `src/*.h` files |
| 90 | +- shellcheck on all bash scripts (excluding `init.d/`) |
| 91 | +- Build and unit tests (AM and NFM) when `src/*.cpp`, `src/*.h`, or `CMakeLists.txt` are modified (`scripts/run_tests`) |
| 92 | + |
| 93 | +## CI and Pull Request Checks |
| 94 | + |
| 95 | +Two workflows run on every PR (`.github/workflows/`): |
| 96 | + |
| 97 | +**`code_formatting.yml`** — runs `./scripts/reformat_code` and fails if any files differ. |
| 98 | + |
| 99 | +**`ci_build.yml`** — builds and tests four configurations on Ubuntu (x86 and ARM) and macOS: |
| 100 | +```bash |
| 101 | +cmake -B build_Debug -DCMAKE_BUILD_TYPE=Debug -DBUILD_UNITTESTS=TRUE |
| 102 | +cmake -B build_Debug_NFM -DCMAKE_BUILD_TYPE=Debug -DNFM=TRUE -DBUILD_UNITTESTS=TRUE |
| 103 | +cmake -B build_Release -DCMAKE_BUILD_TYPE=Release -DBUILD_UNITTESTS=TRUE |
| 104 | +cmake -B build_Release_NFM -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE |
| 105 | +``` |
| 106 | +Then runs `unittests` for all four, installs the Release+NFM build, and smoke-tests `rtl_airband -v`. |
| 107 | + |
| 108 | +**Before submitting a PR**, the pre-commit hooks cover most checks automatically. For build system or config changes not touching `src/`, verify all four cmake configurations build cleanly by hand. |
| 109 | + |
| 110 | +## Architecture |
| 111 | + |
| 112 | +### Reception Pipeline |
| 113 | + |
| 114 | +``` |
| 115 | +SDR device (input-*.cpp) |
| 116 | + → RX thread → circular sample buffer |
| 117 | + → demod thread: FFT (FFTW3) → demod (AM/NFM) → filter → CTCSS → squelch → AGC |
| 118 | + → channel output handlers |
| 119 | + → output thread: MP3 encode (lame) → Icecast / file / UDP / PulseAudio |
| 120 | +``` |
| 121 | + |
| 122 | +### Key Source Files |
| 123 | + |
| 124 | +| File | Purpose | |
| 125 | +|------|---------| |
| 126 | +| `src/rtl_airband.cpp` | Main entry point, demod loop, thread management | |
| 127 | +| `src/rtl_airband.h` | All major struct/enum definitions (`device_t`, `channel_t`, `mixer_t`, `output_t`) | |
| 128 | +| `src/config.cpp` | libconfig++ parsing for devices, channels, mixers, outputs | |
| 129 | +| `src/output.cpp` | MP3 encoding, Icecast connections, file/UDP output | |
| 130 | +| `src/mixer.cpp` | Multi-channel mixer with ampfactor/balance | |
| 131 | +| `src/input-*.cpp` | SDR device drivers (rtlsdr, soapysdr, mirisdr, file) | |
| 132 | +| `src/input-common.cpp/h` | Input device abstraction (`input_t` function-pointer interface) | |
| 133 | +| `src/filters.cpp/h` | IIR lowpass and notch filters | |
| 134 | +| `src/squelch.cpp/h` | Noise-power-based voice activity detection | |
| 135 | +| `src/ctcss.cpp/h` | CTCSS tone detection | |
| 136 | + |
| 137 | +### Device Modes |
| 138 | + |
| 139 | +Each device operates in one of two modes, set via `mode = "multichannel"` (default) or `mode = "scan"` in config. |
| 140 | + |
| 141 | +**`R_MULTICHANNEL`** — The SDR is tuned to a fixed center frequency and multiple channels are demodulated simultaneously from the same wideband capture. Each channel has a single `freq` value that must fall within the SDR's bandwidth. This is the common case for monitoring several frequencies at once. |
| 142 | + |
| 143 | +**`R_SCAN`** — The device has exactly one channel, but that channel holds a `freqs` list of frequencies to cycle through. A controller thread monitors the squelch: after ~2 seconds of no signal (10 × 200 ms polls), it retunes the SDR hardware to the next frequency via `input_set_centerfreq()`. When a signal is detected, it stays on the current frequency. Per-frequency labels, squelch thresholds, modulation, notch filters, and CTCSS settings are all supported in the `freqs` list. (`rtl_airband.cpp:101-140`, `config.cpp:825`) |
| 144 | + |
| 145 | +### Threading Model |
| 146 | + |
| 147 | +- **RX thread** (1 per device, always) — reads SDR samples into the circular buffer (`input-common.cpp`) |
| 148 | +- **Controller thread** (1 per device, `R_SCAN` mode only) — scanning/squelch state machine for devices that scan across frequencies; not created for `R_MULTICHANNEL` devices (`rtl_airband.cpp:1005-1013`) |
| 149 | +- **Demod thread** (1 total by default; 1 per device if `multiple_demod_threads=true` in config) — FFT, demodulation, filter, CTCSS, squelch, AGC for all assigned devices (`rtl_airband.cpp:1044`) |
| 150 | +- **Output thread** (1 total by default; 1 per device + 1 for mixers if `multiple_output_threads=true`) — MP3 encoding and streaming |
| 151 | +- **Mixer thread** (1 total, only if any mixers are configured) — processes all mixers; not per-mixer (`rtl_airband.cpp:1091-1092`) |
| 152 | + |
| 153 | +### Configuration Format |
| 154 | + |
| 155 | +Config files use libconfig++ syntax. Sample configs in `config/`. Top-level sections: |
| 156 | + |
| 157 | +``` |
| 158 | +devices: ( { type = "rtlsdr"; centerfreq = 120.0; gain = 25; |
| 159 | + channels: ( { freq = 119.5; modulation = "AM"; |
| 160 | + outputs: ( { type = "icecast"; ... } ); } ); } ); |
| 161 | +mixers: ( { name = "mix1"; inputs: ( { device=0; channel=0; ampfactor=1.0; } ); outputs: (...); } ); |
| 162 | +``` |
| 163 | + |
| 164 | +Output types: `icecast`, `file`, `rawfile`, `udp_stream`, `mixer`, `pulse`. |
| 165 | + |
| 166 | +### Unit Tests |
| 167 | + |
| 168 | +Tests use Google Test (fetched via CMake FetchContent). Test files in `src/test_*.cpp` cover filters, squelch, CTCSS, helper functions, and signal generation. `src/test_base_class.h` provides test utilities. |
0 commit comments