{"id":49330700,"url":"https://github.com/gbozo/no-sdr","last_synced_at":"2026-05-20T02:05:06.435Z","repository":{"id":352748091,"uuid":"1216270075","full_name":"gbozo/no-sdr","owner":"gbozo","description":"Multi-user WebSDR for RTL-SDR — waterfall, spectrum, stereo FM, stereo AM and 15+ demod modes in your browser","archived":false,"fork":false,"pushed_at":"2026-04-21T00:57:57.000Z","size":279,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-21T01:34:51.755Z","etag":null,"topics":["am","amstereo","fm","fmstereo","openwebrx","radio","rtl-sdr","sdr","websdr"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gbozo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-20T18:32:52.000Z","updated_at":"2026-04-21T00:58:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gbozo/no-sdr","commit_stats":null,"previous_names":["gbozo/no-sdr"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/gbozo/no-sdr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbozo%2Fno-sdr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbozo%2Fno-sdr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbozo%2Fno-sdr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbozo%2Fno-sdr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gbozo","download_url":"https://codeload.github.com/gbozo/no-sdr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gbozo%2Fno-sdr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32314116,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T21:09:39.134Z","status":"ssl_error","status_checked_at":"2026-04-26T21:09:21.240Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["am","amstereo","fm","fmstereo","openwebrx","radio","rtl-sdr","sdr","websdr"],"created_at":"2026-04-26T22:01:06.726Z","updated_at":"2026-04-26T22:01:07.297Z","avatar_url":"https://github.com/gbozo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"client/public/favicon.svg\" width=\"80\" height=\"80\" alt=\"no-sdr logo\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eno-sdr\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eNo SDR hardware on your desk? No problem.\u003cbr/\u003eMulti-user web receiver with real-time waterfall, stereo FM, and digital mode decoding — all served from Node.js to your browser.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/gbozo/no-sdr/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/gbozo/no-sdr?style=social\" alt=\"GitHub Stars\" /\u003e\u003c/a\u003e\n  \u0026nbsp;\n  \u003ca href=\"https://github.com/gbozo/no-sdr/network/members\"\u003e\u003cimg src=\"https://img.shields.io/github/forks/gbozo/no-sdr?style=social\" alt=\"Forks\" /\u003e\u003c/a\u003e\n  \u0026nbsp;\n  \u003ca href=\"https://github.com/gbozo/no-sdr/watchers\"\u003e\u003cimg src=\"https://img.shields.io/github/watchers/gbozo/no-sdr?style=social\" alt=\"Watchers\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/gbozo/no-sdr/actions\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/gbozo/no-sdr/ci.yml?branch=main\u0026label=build\" alt=\"Build Status\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/gbozo/no-sdr/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/gbozo/no-sdr?include_prereleases\u0026label=version\u0026color=blue\" alt=\"Version\" /\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/node-%3E%3D22-brightgreen\" alt=\"Node.js 22+\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/typescript-5-blue\" alt=\"TypeScript 5\" /\u003e\n  \u003ca href=\"https://github.com/gbozo/no-sdr/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/github/license/gbozo/no-sdr?color=green\" alt=\"MIT License\" /\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/RTL--SDR-supported-orange\" alt=\"RTL-SDR\" /\u003e\n  \u003ca href=\"https://github.com/gbozo/no-sdr/issues\"\u003e\u003cimg src=\"https://img.shields.io/github/issues/gbozo/no-sdr\" alt=\"Open Issues\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/gbozo/no-sdr/pulls\"\u003e\u003cimg src=\"https://img.shields.io/github/issues-pr/gbozo/no-sdr\" alt=\"Pull Requests\" /\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/github/last-commit/gbozo/no-sdr\" alt=\"Last Commit\" /\u003e\n  \u003cimg src=\"https://img.shields.io/github/repo-size/gbozo/no-sdr\" alt=\"Repo Size\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#quick-start\"\u003eQuick Start\u003c/a\u003e \u0026bull;\n  \u003ca href=\"#features\"\u003eFeatures\u003c/a\u003e \u0026bull;\n  \u003ca href=\"#demo-mode\"\u003eDemo Mode\u003c/a\u003e \u0026bull;\n  \u003ca href=\"#configuration\"\u003eConfiguration\u003c/a\u003e \u0026bull;\n  \u003ca href=\"#deployment\"\u003eDeployment\u003c/a\u003e \u0026bull;\n  \u003ca href=\"SPEC.md\"\u003eTechnical Spec\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003csub\u003eIf you find this project useful, please consider giving it a \u003ca href=\"https://github.com/gbozo/no-sdr\"\u003estar on GitHub\u003c/a\u003e — it helps others discover it and keeps the motivation flowing! :star:\u003c/sub\u003e\n\u003c/p\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"screenshots/no-sdr.jpeg\" alt=\"no-sdr interface — waterfall, spectrum analyzer, and stereo FM with RDS\" width=\"100%\" /\u003e\n\u003c/p\u003e\n\n**no-sdr** turns cheap RTL-SDR USB dongles into a full-featured web-based radio receiver. Multiple users connect through their browser and independently tune, demodulate, and listen to signals — all sharing the same hardware. No plugins, no installs, just open a URL.\n\nThink of it as your own private, open [WebSDR](http://websdr.org) that you can run at home, in a hackerspace, or on a docker container (compose). Works in Raspberry Pi too. \n\n*Made with ❤️ and patience, your friend George*\n\n### Codec Performance\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\"\u003e\u003cimg src=\"screenshots/no-sdr-compression.jpeg\" alt=\"no-sdr compression stats\" width=\"100%\" /\u003e\u003c/td\u003e\n\u003ctd valign=\"top\"\u003e\n\n**Waterfall (FFT)**\n| Codec | Ratio | Type |\n|-------|-------|------|\n| **Deflate** | 7.5–10:1 | Lossless (default) |\n| ADPCM | ~8:1 | Lossy |\n\n**Audio (IQ / Opus)**\n| Codec | Bandwidth | Type |\n|-------|-----------|------|\n| ADPCM | ~48 KB/s (default) | Lossy 4:1 on IQ |\n| **Opus** | ~4 KB/s | VBR 32kbps mono / 64kbps stereo |\n| **Opus HQ** | ~16 KB/s | VBR 128kbps mono / 192kbps stereo |\n\nOpus codecs use server-side demodulation with full stereo FM and C-QUAM support. Clients independently select their preferred codec — no restart needed.\nA typical HF / AM Profile with sampling rate of 2.4 MBPS , fft size of 4096 buckets with 8 frames per second and deflate compression 8-10 KB/s, with opus mono audio compression ~4 KB/s.\nSo a total of around 12-13 KB/s per client of bandwidht required with full audio and waterfall, spectrum.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n## Features\n\n### Radio\n\n- **8 analog demodulation modes** — WFM (stereo, RDS), NFM, AM (stereo), USB, LSB, CW, Raw IQ\n- **Stereo FM** — PLL-based 19kHz pilot detection, L-R DSB-SC demodulation with SNR-proportional stereo blend\n- **RDS** — client-side FM RDS decoder extracts station name (PS), radio text (RT), programme type, PI code, and clock time with overlay on waterfall\n- **AM Stereo (C-QUAM) [EXPERIMENTAL]** — auto-detected in AM mode via two-stage verification (25Hz Goertzel pilot + PLL lock confirmation). When a C-QUAM station is detected, stereo decoding activates automatically. *This feature needs testers with access to C-QUAM AM stereo broadcasts — please report results via GitHub issues!*\n- **9 digital decoders** — ADS-B, ACARS, VDL2, AIS, APRS, POCSAG, FT8, FT4, WSPR (via external binaries)\n- **Multi-user** — everyone shares the same waterfall; each user tunes independently within the dongle's bandwidth\n- **Multi-dongle** — configure multiple RTL-SDR devices, each with its own frequency profiles\n- **Three dongle source types** — local USB (`rtl_sdr`), remote TCP (`rtl_tcp`), or demo simulator\n- **Profile system** — admins define presets (FM broadcast, aviation, 2m ham, ADS-B, marine) per dongle; switching a profile changes it for all connected users\n- **Profile CRUD** — create, update, and delete profiles at runtime via REST API; changes persist to disk\n\n### Display\n\n- **Live waterfall** — Canvas 2D with 5 color themes (turbo, viridis, classic, grayscale, hot)\n- **Auto-range** — automatic dB scaling based on signal statistics, or manual min/max control\n- **Spectrum analyzer** — real-time power spectral density with tuning indicator and bandwidth overlay\n- **Frequency display** — LCD-style readout with scroll-to-tune digit groups\n- **S-meter** — bar or classic analog needle meter with warm backlit face, dual scale (S-units + dB), red needle with peak hold indicator\n- **Bandwidth meter** — real-time SVG sparkline showing WebSocket throughput + FFT frame rate\n- **3 UI themes** — LCD (cyan), CRT (phosphor green), VFD (amber)\n\n### Audio\n\n- **Client-side DSP** — demodulation runs entirely in the browser via pure TypeScript\n- **Stereo output** — stereo FM with SNR-proportional blend, auto-detected C-QUAM AM stereo (experimental)\n- **Noise reduction** — spectral subtraction (Wiener filter) + impulse noise blanker with adjustable strength\n- **AudioWorklet** — low-latency audio playback with adaptive jitter buffer (150ms min, 200ms target, ±1 sample/frame rate control)\n- **5-band parametric EQ** — LOW 80Hz, L-MID 500Hz, MID 1.5kHz, H-MID 4kHz, HIGH 12kHz (all ±12dB)\n- **Balance** — stereo pan control (-100% left to +100% right)\n- **Loudness** — dynamic compression with pre-boost for quiet signals\n- **Squelch** — adjustable noise gate based on signal level, with 500ms bypass after tune changes\n\n### Infrastructure\n\n- **Multi-codec compression** — per-client codec negotiation for both FFT and IQ streams\n  - **FFT**: None (Uint8, 4:1), ADPCM (~8:1), Delta+Deflate (7.5–10:1 lossless, default)\n  - **IQ**: None (raw Int16), ADPCM (4:1, default), Opus VBR (server-side demod, 32kbps mono / 64kbps stereo), Opus HQ (128kbps mono / 192kbps stereo)\n- **Server-side Opus audio** — full WFM stereo PLL and C-QUAM demod on server with dynamic mono↔stereo encoder switching (opusscript WASM)\n- **Server-side FFT rate cap** — configurable fps per profile (default 30) with inter-frame averaging\n- **IQ chunk accumulation** — server buffers IQ into fixed 20ms chunks for consistent WebSocket messages\n- **Client-side resampler** — linear interpolation upsamples SSB (24kHz) and CW (12kHz) to 48kHz\n- **WebSocket backpressure** — bufferedAmount-based frame skipping prevents server memory bloat\n- **Audio-gated IQ** — server only sends per-user IQ data after client enables audio playback\n- **Dongle hardware options** — directSampling, biasT, digitalAgc, offsetTuning, ifGain, tunerBandwidth via config\n- **Demo mode** — built-in signal simulator for development and demos, no hardware needed\n- **rtl_tcp support** — connect to remote RTL-SDR dongles over TCP (Docker sidecars, remote antennas)\n- **Docker ready** — multi-stage Dockerfile with USB passthrough for RTL-SDR, auto-publish to GHCR on release\n- **Raspberry Pi compatible** — runs on ARM64, tested on Pi 4/5\n- **Admin panel** — start/stop dongles, switch profiles, CRUD profiles, monitor status via REST API + UI\n- **YAML config** — validated at startup with Zod schemas, persisted on admin changes\n- **Per-client IQ extraction** — server-side NCO frequency shift + 4th-order Butterworth anti-alias filter + decimation\n- **Compression stats** — live wire bytes, raw equivalent, ratio, and savings displayed in UI\n\n## Quick Start\n\n### Prerequisites\n\n- **Node.js 22+** (LTS recommended)\n- **RTL-SDR dongle** + drivers, or a remote `rtl_tcp` server, or just use demo mode\n\n### Install \u0026 Run\n\n```bash\ngit clone https://github.com/gbozo/no-sdr.git\ncd no-sdr\nnpm install\nnpm run build\nnpm start\n```\n\nOpen `http://localhost:3000` in your browser.\n\n### Demo Mode (No Hardware)\n\nDon't have an RTL-SDR? No problem — demo mode simulates realistic radio signals:\n\n```bash\nnpm run dev:demo\n```\n\nThis starts the server with a signal simulator that generates FM stations, aviation communications, and ham radio signals. The waterfall, spectrum, and demodulation all work exactly as they would with real hardware.\n\n## Architecture\n\n```\nRTL-SDR Dongle ──► rtl_sdr / rtl_tcp / simulator ──► IQ samples\n                                                         │\n                    ┌────────────────────────────────────┘\n                    ▼\n             Server (Node.js)\n             ├─ FFT (shared) ──────────────► WebSocket ──► All clients (waterfall)\n             └─ IQ sub-band (per user) ─┐\n                NCO shift + Butterworth ├──► WebSocket ──► One client\n                + decimate ─────────────┘                     │\n                                                    Browser (SolidJS)\n                                                    ├─ Waterfall (Canvas 2D)\n                                                    ├─ Spectrum (Canvas 2D)\n                                                    ├─ Demodulator (TS DSP)\n                                                    │   └─ Stereo FM (PLL)\n                                                    └─ Audio (AudioWorklet)\n                                                        ├─ 5-band EQ\n                                                        ├─ Balance\n                                                        ├─ Loudness\n                                                        └─ Squelch gate\n```\n\n**Hybrid DSP model**: The server computes FFT and broadcasts it to all clients (shared waterfall). Per-user IQ sub-bands are extracted using a numerically-controlled oscillator (NCO) for frequency shifting, a 4th-order Butterworth anti-aliasing filter, and integer decimation. Each client receives its own narrowband IQ stream and performs demodulation locally. Server CPU cost scales with user count only for IQ extraction — demodulation is entirely client-side.\n\n## Tech Stack\n\n| Component | Technology |\n|-----------|-----------|\n| Runtime | Node.js 22 (ESM) |\n| Backend | [Hono](https://hono.dev) + [@hono/node-ws](https://github.com/honojs/middleware/tree/main/packages/node-ws) |\n| Frontend | [SolidJS](https://www.solidjs.com) |\n| Build | [Vite 6](https://vite.dev) |\n| Styling | [Tailwind CSS v4](https://tailwindcss.com) |\n| FFT | [fft.js](https://github.com/nicedoc/fft.js) (radix-4, pure JS) |\n| Opus | [opusscript](https://github.com/abalabahaha/opusscript) (server WASM) + [opus-decoder](https://github.com/eshaz/wasm-audio-decoders) (client WASM) |\n| Deflate | Node.js zlib (server) + [fflate](https://github.com/101arrowz/fflate) (client) |\n| Config | YAML ([js-yaml](https://github.com/nodeca/js-yaml)) + [Zod](https://zod.dev) |\n| Logging | [pino](https://getpino.io) |\n| Language | TypeScript 5 (strict) |\n\n## Configuration\n\nAll configuration lives in `config/config.yaml`. The file is validated against a Zod schema at startup — invalid configs fail fast with clear error messages.\n\n```yaml\nserver:\n  host: \"0.0.0.0\"\n  port: 3000\n  adminPassword: \"changeme\"\n\ndongles:\n  # Local USB dongle\n  - id: dongle-0\n    deviceIndex: 0\n    name: \"RTL-SDR #0\"\n    source:\n      type: local              # spawn rtl_sdr child process\n    autoStart: true\n    profiles:\n      - id: fm-broadcast\n        name: \"FM Broadcast\"\n        centerFrequency: 100000000\n        sampleRate: 2400000\n        fftSize: 2048\n        defaultMode: wfm\n        defaultBandwidth: 200000\n\n  # Remote dongle via rtl_tcp\n  - id: dongle-remote\n    name: \"Remote Antenna\"\n    source:\n      type: rtl_tcp\n      host: \"192.168.1.100\"\n      port: 1234\n    autoStart: true\n    profiles:\n      - id: aviation\n        name: \"Aviation VHF\"\n        centerFrequency: 125000000\n        sampleRate: 2400000\n        fftSize: 2048\n        defaultMode: am\n        defaultBandwidth: 8330\n        gain: 40\n\n  # Demo dongle (no hardware)\n  - id: dongle-demo\n    name: \"Simulator\"\n    source:\n      type: demo\n    profiles:\n      - id: fm-demo\n        name: \"FM Demo\"\n        centerFrequency: 100000000\n        sampleRate: 2400000\n        fftSize: 2048\n        defaultMode: wfm\n```\n\n### Source Types\n\n| Type | Description | Config |\n|------|-------------|--------|\n| `local` | Spawns `rtl_sdr` child process, reads IQ from stdout | `deviceIndex`, optional `binary`, `extraArgs` |\n| `rtl_tcp` | TCP client to a remote `rtl_tcp` server | `host`, `port` (required) |\n| `demo` | Built-in signal simulator, no hardware | No extra config needed |\n\n### Profile System\n\nEach dongle has multiple profiles. When an admin switches the active profile, **all connected clients viewing that dongle are switched automatically** — center frequency, sample rate, demodulation mode, and bandwidth all update in real time.\n\nProfiles can be created, updated, and deleted at runtime via the admin REST API. Changes are automatically persisted back to the YAML config file on disk.\n\n### Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `NODE_SDR_CONFIG` | `config/config.yaml` | Path to config file |\n| `NODE_SDR_DEMO` | — | Set to `1` to enable demo mode (overrides per-dongle source) |\n| `LOG_LEVEL` | `info` | pino log level |\n| `NODE_ENV` | — | Set to `production` for optimized serving |\n\n## Demodulation Modes\n\n### Analog (client-side, pure TypeScript)\n\n| Mode | Description | Bandwidth | Notes |\n|------|-------------|-----------|-------|\n| **WFM** | Wideband FM (broadcast radio) | 150–200 kHz | Stereo FM with PLL pilot detection, SNR-proportional blend, RDS decoding |\n| **NFM** | Narrowband FM (VHF/UHF comms) | 5–25 kHz | De-emphasis filter |\n| **AM** | Amplitude Modulation (aviation, shortwave) | 3–10 kHz | Envelope detection + AGC; auto-detects C-QUAM stereo |\n| **USB** | Upper Sideband (HF amateur, marine) | 1–4 kHz | BFO complex oscillator |\n| **LSB** | Lower Sideband (HF amateur, CB) | 1–4 kHz | Conjugate flip + BFO |\n| **CW** | Continuous Wave / Morse code | 50–1000 Hz | 700Hz BFO + narrow bandpass |\n| **RAW** | Raw IQ passthrough | Variable | I-channel audio output |\n\n### Digital (server-side, external binaries)\n\n| Mode | Binary | Description |\n|------|--------|-------------|\n| **ADS-B** | `dump1090` | Aircraft tracking at 1090 MHz |\n| **ACARS** | `acarsdec` | Aircraft data link messages |\n| **VDL2** | `dumpvdl2` | VHF digital data link |\n| **AIS** | `rtl_ais` | Ship tracking |\n| **APRS** | `direwolf` | Amateur packet radio |\n| **POCSAG** | `multimon-ng` | Pager decoding |\n| **FT8/FT4** | `jt9` | Weak-signal amateur modes |\n| **WSPR** | `wsprd` | Propagation reporting |\n\nDigital decoders are optional — install the binaries you need. The server auto-detects available binaries at startup.\n\n## Deployment\n\n### Docker\n\n```bash\ncd docker\ndocker compose up -d\n```\n\nThe Dockerfile uses a multi-stage build and includes `rtl-sdr`, `dump1090`, and `multimon-ng` in the runtime image. USB passthrough requires `privileged: true` on Linux.\n\n### Docker Compose with rtl_tcp Sidecar\n\n```yaml\nservices:\n  rtl_tcp:\n    image: kosniaz/rtl_tcp\n    devices:\n      - /dev/bus/usb:/dev/bus/usb\n    privileged: true\n    command: [\"-a\", \"0.0.0.0\", \"-p\", \"1234\"]\n\n  no-sdr:\n    build:\n      context: ..\n      dockerfile: docker/Dockerfile\n    ports:\n      - \"3000:3000\"\n    volumes:\n      - ../config:/app/config:ro\n    depends_on:\n      - rtl_tcp\n    restart: unless-stopped\n```\n\nConfigure the dongle source as `rtl_tcp` with `host: rtl_tcp` and `port: 1234`.\n\n### Raspberry Pi\n\nno-sdr runs well on Raspberry Pi 4/5 (ARM64). Install Node.js 22 via [NodeSource](https://github.com/nodesource/distributions) or [nvm](https://github.com/nvm-sh/nvm), then:\n\n```bash\nsudo apt install rtl-sdr\ngit clone https://github.com/gbozo/no-sdr.git\ncd no-sdr\nnpm install \u0026\u0026 npm run build \u0026\u0026 npm start\n```\n\n### Reverse Proxy (nginx)\n\n```nginx\nserver {\n    listen 80;\n    server_name sdr.example.com;\n\n    location / {\n        proxy_pass http://127.0.0.1:3000;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \"upgrade\";\n        proxy_set_header Host $host;\n    }\n}\n```\n\n## REST API\n\n| Method | Endpoint | Auth | Description |\n|--------|----------|------|-------------|\n| `GET` | `/api/status` | — | Server status, uptime, client count |\n| `GET` | `/api/dongles` | — | List all configured dongles |\n| `GET` | `/api/dongles/:id` | — | Get dongle info |\n| `GET` | `/api/dongles/:id/profiles` | — | List profiles for dongle |\n| `GET` | `/api/decoders` | — | List running decoders |\n| `GET` | `/api/decoders/check` | — | Check which decoder binaries are installed |\n| `POST` | `/api/admin/login` | — | Authenticate (body: `{ password }`) |\n| `POST` | `/api/admin/dongles/:id/start` | Admin | Start a dongle |\n| `POST` | `/api/admin/dongles/:id/stop` | Admin | Stop a dongle |\n| `POST` | `/api/admin/dongles/:id/profile` | Admin | Switch active profile (body: `{ profileId }`) |\n| `POST` | `/api/admin/dongles/:id/profiles` | Admin | Create new profile (body: profile object) |\n| `PUT` | `/api/admin/dongles/:id/profiles/:pid` | Admin | Update profile (body: partial profile) |\n| `DELETE` | `/api/admin/dongles/:id/profiles/:pid` | Admin | Delete profile |\n| `POST` | `/api/admin/save-config` | Admin | Persist current config to disk |\n| `GET` | `/api/admin/status` | Admin | Full status with memory usage |\n\nAdmin endpoints require `Authorization: Bearer \u003cpassword\u003e` header.\n\n## WebSocket Protocol\n\nThe WebSocket endpoint is at `/ws`. Server-to-client messages use a binary protocol with a type byte prefix. Client-to-server messages are JSON text.\n\nSee [SPEC.md](SPEC.md) for the complete protocol specification.\n\n## Development\n\n```bash\n# Start everything in demo mode with hot reload\nnpm run dev:demo\n\n# Build all workspaces\nnpm run build\n\n# Type check all workspaces\nnpm run typecheck\n\n# Clean build artifacts\nnpm run clean\n```\n\n### Project Structure\n\nThis is an npm workspaces monorepo with three packages:\n\n- **`shared/`** — Zero-dependency types, protocol constants, mode definitions, IMA-ADPCM codec\n- **`server/`** — Hono backend, hardware management, FFT, IQ extraction, Opus audio pipeline, WebSocket\n- **`client/`** — SolidJS frontend, Canvas renderers, DSP, stereo FM, RDS decoder, AudioWorklet + EQ\n\nBuild order: `shared` → `client` → `server` (the server serves the built client).\n\n## Contributing\n\nContributions are welcome. Please:\n\n1. Fork the repo and create a feature branch\n2. Run `npm run build \u0026\u0026 npm run typecheck` before submitting\n3. Keep PRs focused — one feature or fix per PR\n4. Add to `config/config.yaml` examples if adding new modes or decoders\n\n### Areas Where Help Is Needed\n\n- **AM Stereo (C-QUAM) testing** — auto-detection is experimental; we need testers near C-QUAM stations (~45 in the US, a handful in Italy, Japan, Philippines, Thailand). Requires direct sampling mod or HF-capable dongle. Please report results!\n- **Testing** — unit tests for DSP, protocol, config validation, ADPCM codec\n- **Spectral NR** — current Wiener filter has robotic artifacts; needs rework (RNNoise WASM, multi-band expander)\n- **WebGL waterfall** — GPU-accelerated rendering for large FFT sizes\n- **Recording** — IQ recording and playback (SigMF format)\n- **Bookmarks** — frequency bookmark management\n- **Mobile UI** — responsive design for tablets and phones\n- **New decoders** — WASM ports of C decoders (FT8, DAB, etc.)\n\n## License\n\nMIT\n\n## Acknowledgments\n\n- [OpenWebRX](https://github.com/jketterl/openwebrx) — the gold standard of open-source WebSDR, major architectural inspiration\n- [Intercept](https://github.com/smittix/intercept) — modern signal intelligence platform, UI/UX reference\n- [fft.js](https://github.com/nicedoc/fft.js) by Fedor Indutny — fast pure-JS radix-4 FFT\n- [RTL-SDR](https://www.rtl-sdr.com/) community — for making software-defined radio accessible to everyone\n- [SolidJS](https://www.solidjs.com/) — reactive UI without the VDOM overhead\n- [Hono](https://hono.dev/) — ultrafast web framework for the edge and Node.js\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgbozo%2Fno-sdr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgbozo%2Fno-sdr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgbozo%2Fno-sdr/lists"}