Skip to content

Commit 1c7d87a

Browse files
Add CLAUDE.md file and enhance dev environment (#555)
* initial CLAUDE.md file * update vscode / devcontainer * add "standard" pre-commit hooks and fix what it found * add pre-commit step to build and run unit tests I am making my contributions to this project solely in my personal capacity and am not conveying any rights to any intellectual property of any third parties.
1 parent f8a17d7 commit 1c7d87a

11 files changed

Lines changed: 273 additions & 34 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,27 @@
1-
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2-
// https://github.com/microsoft/vscode-dev-containers/tree/v0.177.0/containers/docker-existing-dockerfile
31
{
42
"name": "Existing Dockerfile",
5-
6-
// Sets the run context to one level up instead of the .devcontainer folder.
73
"context": "..",
8-
94
"dockerFile": "Dockerfile",
10-
11-
"updateContentCommand" : "apt-get install git",
12-
13-
"postCreateCommand" : "cmake -B /app/build -DCMAKE_BUILD_TYPE=Debug -DNFM=TRUE -DBUILD_UNITTESTS=true ; pre-commit install",
14-
15-
// vs code extensions to install in the dev container
165
"customizations": {
176
"vscode": {
187
"extensions": [
198
"ms-vscode.cpptools",
209
"ms-vscode.cmake-tools",
2110
"ms-vscode.cpptools-extension-pack",
22-
"twxs.cmake",
2311
"streetsidesoftware.code-spell-checker",
2412
"ms-azuretools.vscode-docker",
2513
"GitHub.vscode-github-actions",
2614
"xaver.clang-format"
2715
]
2816
}
2917
},
30-
31-
// Use 'forwardPorts' to make a list of ports inside the container available locally.
32-
// "forwardPorts": [],
33-
34-
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ]
18+
"containerEnv": {
19+
"SHELL": "/bin/bash"
20+
},
21+
"postStartCommand": "cmake -B /app/build -DCMAKE_BUILD_TYPE=Debug -DNFM=TRUE -DBUILD_UNITTESTS=true ; pre-commit install",
22+
"runArgs": [
23+
"--cap-add=SYS_PTRACE",
24+
"--security-opt",
25+
"seccomp=unconfined"
26+
]
3527
}

.devcontainer/shell

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/bin/bash -e
22

3-
cd `dirname $0`/../
3+
cd "$(dirname "$0")/.."
44

55
# build container
66
docker build -t rtl_airband-dev -f .devcontainer/Dockerfile .
77

88
# run bash in container
9-
docker run --rm -v $(pwd):/app/ -it --entrypoint bash rtl_airband-dev
9+
docker run --rm -v "$(pwd)":/app/ -it --entrypoint bash rtl_airband-dev

.github/install_dependencies

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ case "${unameOut}" in
2121

2222
(
2323
git clone https://github.com/f4exb/libmirisdr-4
24-
cd libmirisdr-4
24+
cd libmirisdr-4 || exit
2525
mkdir build
26-
cd build
26+
cd build || exit
2727
cmake ../
2828
sudo make install
2929
sudo ldconfig
@@ -39,6 +39,7 @@ case "${unameOut}" in
3939
export HOMEBREW_NO_AUTO_UPDATE=1
4040
export HOMEBREW_NO_INSTALL_UPGRADE=1
4141
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
42+
# shellcheck disable=SC2154 # ImageOS and ImageVersion are set by GitHub Actions
4243
echo "running ${ImageOS} vsersion ${ImageVersion}"
4344
else
4445
brew update
@@ -58,5 +59,5 @@ case "${unameOut}" in
5859

5960
*)
6061
echo "Error: Machine not supported"
61-
exit -1
62+
exit 1
6263
esac

.github/platform_build

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,31 @@ platform="${1}"
44

55
if [ -z "${platform}" ]; then
66
echo "Error: platform not set"
7-
exit -1
7+
exit 1
88
fi
99

10+
# shellcheck disable=SC1091,SC2086 # /etc/os-release is a runtime system file; VERSION needs word splitting
1011
echo "running build for ${platform} on $(source /etc/os-release ; echo ${VERSION})"
1112

1213
case "${platform}" in
1314
rpi3b)
14-
CMAKE_ARGS="-DPLATFORM=rpiv2 -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE"
15+
CMAKE_ARGS=(-DPLATFORM=rpiv2 -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE)
1516
;;
1617
ubuntu-22.04-arm)
17-
CMAKE_ARGS="-DPLATFORM=native -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE"
18+
CMAKE_ARGS=(-DPLATFORM=native -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE)
1819
;;
1920

2021
*)
2122
echo "Error: Platform '${platform}' not supported"
22-
exit -1
23+
exit 1
2324
esac
2425

2526
# make a build dir
2627
rm -rf build || true ; mkdir build
27-
cd build
28+
cd build || exit
2829

2930
# configure and build
30-
cmake ${CMAKE_ARGS} ../
31+
cmake "${CMAKE_ARGS[@]}" ../
3132
VERBOSE=1 make -j
3233

3334
# run unit tests

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ build*/
33
.cache
44
compile_commands.json
55
rtl_airband*.log
6+
7+
# Claude Code - personal overrides (team settings go in .claude/settings.json)
8+
.claude/settings.local.json

.pre-commit-config.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,33 @@ repos:
33
rev: v4.5.0
44
hooks:
55
- id: check-yaml
6+
- id: check-json
7+
exclude: ^\.vscode/launch\.json$ # JSONC format (JSON with comments) used by VS Code
68
- id: end-of-file-fixer
79
- id: trailing-whitespace
810
- id: check-shebang-scripts-are-executable
11+
- id: check-merge-conflict
12+
- id: check-added-large-files
13+
- id: check-case-conflict
14+
- id: detect-private-key
915

1016
- repo: https://github.com/pre-commit/mirrors-clang-format
1117
rev: v14.0.6
1218
hooks:
1319
- id: clang-format
1420
files: src/.*\.cpp|src/.*\.h
21+
22+
- repo: https://github.com/shellcheck-py/shellcheck-py
23+
rev: v0.10.0.1
24+
hooks:
25+
- id: shellcheck
26+
exclude: ^init\.d/ # platform init scripts use OS-specific conventions shellcheck doesn't support
27+
28+
- repo: local
29+
hooks:
30+
- id: run-tests
31+
name: Build and run unit tests (AM and NFM)
32+
entry: scripts/run_tests
33+
language: script
34+
pass_filenames: false
35+
files: \.(cpp|h)$|CMakeLists\.txt$|^scripts/run_tests$

.vscode/settings.json

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,33 @@
11
{
2+
"files.autoSave": "onFocusChange",
3+
"files.insertFinalNewline": true,
4+
"files.trimTrailingWhitespace": true,
25
"editor.formatOnPaste": true,
36
"editor.formatOnSave": true,
47
"editor.formatOnType": true,
5-
"editor.defaultFormatter": "xaver.clang-format",
6-
"clang-format.executable": "clang-format-14"
8+
"editor.defaultFormatter": null,
9+
"[cpp]": {
10+
"editor.quickSuggestions": {
11+
"other": "on",
12+
"comments": "off",
13+
"strings": "on"
14+
},
15+
"editor.defaultFormatter": "xaver.clang-format",
16+
"editor.suggest.insertMode": "insert"
17+
},
18+
"[json]": {
19+
"editor.quickSuggestions": {
20+
"strings": true
21+
},
22+
"editor.suggest.insertMode": "replace",
23+
"editor.formatOnPaste": true,
24+
"editor.formatOnSave": true,
25+
"editor.formatOnType": true
26+
},
27+
"clang-format.executable": "clang-format-14",
28+
"cSpell.diagnosticLevel": "Hint",
29+
"cmake.configureArgs": [
30+
"-DNFM=TRUE",
31+
"-DBUILD_UNITTESTS=TRUE"
32+
]
733
}

CLAUDE.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
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.

scripts/find_version

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/bin/bash
22

3-
PROJECT_ROOT_PATH="$(cd $(dirname "$0")/../ ; pwd)"
3+
PROJECT_ROOT_PATH="$(cd "$(dirname "$0")/.." || exit; pwd)"
44
PROJECT_GIT_DIR_PATH="${PROJECT_ROOT_PATH}/.git"
5-
PROJECT_DIR_NAME="$(basename ${PROJECT_ROOT_PATH})"
5+
PROJECT_DIR_NAME="$(basename "${PROJECT_ROOT_PATH}")"
66

77
# if there is a .git directory at the project root then rely on git for the version string
88
if [ -r "${PROJECT_GIT_DIR_PATH}" ] ; then
@@ -13,7 +13,7 @@ fi
1313
# if the proejct root directory matches the naming convetion of an extracted archive then
1414
# get the version number out of that
1515
if [[ "${PROJECT_DIR_NAME}" =~ ^RTLSDR-Airband-[0-9]*\.[0-9]*\.[0-9]*$ ]]; then
16-
echo ${PROJECT_DIR_NAME} | cut -d '-' -f 3
16+
echo "${PROJECT_DIR_NAME}" | cut -d '-' -f 3
1717
exit 0
1818
fi
1919

scripts/reformat_code

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#!/bin/bash
22

3-
find src/*.h src/*.cpp src/hello_fft/*.h src/hello_fft/*.c | xargs clang-format-14 -i
3+
find src/*.h src/*.cpp src/hello_fft/*.h src/hello_fft/*.c -print0 | xargs -0 clang-format-14 -i

0 commit comments

Comments
 (0)