Skip to content

Repository files navigation

Aether

An AI-native radio — FM broadcast and AM airband. RTL-SDR hardware, an Elixir/OTP core, and no AI inside, because the AI is the one holding the remote. Aether is built to be operated by AI agents (Claude Code, Codex, cron'd scripts, you name it): they tune it, they pull the last minute of broadcast out of it as WAV, and they decide what it means.

Aether: the fifth element of classical alchemy, and the medium radio waves were once believed to travel through. An elixir among elements.

        RF                          PCM (48kHz s16le)
  ┌───────────┐   ┌──────────────┐   ┌────────────────────┐   ┌──────┐
  │ RTL-SDR   │──▶│ demodulator  │──▶│  Aether.Player     │──▶│ play │──▶ 🔊
  │ dongle    │   │ (airspy-     │   │  (GenServer)       │   │ (sox)│
  └───────────┘   │  fmradion or │   │        │           │   └──────┘
                  │  rtl_fm)     │   │        ▼           │
                  └──────────────┘   │  RingBuffer (60s)  │──▶ capture → .wav → 🤖
                                     └────────────────────┘
                                              ▲
                           Unix socket ───────┘
                           (tune / status / capture / ... — you)

Why "AI-native"?

A classic radio assumes a human: knobs, presets, frequencies to memorize. Aether assumes an agent:

  • A control socket, not a UI. Every operation is one line over a Unix domain socket, with a json mode for parsing. One binary acts as both daemon and client.
  • A live audio tap, not a speaker-only path. The last 60 seconds of broadcast are always in memory; capture hands them to the agent as WAV, while playback keeps going. The agent transcribes and reasons with its own tools.
  • A station knowledge base, not frequency knobs. tune nhk works; so do tune 82.5 and tune atis. The roster (Tokyo & Fukuoka FM, Tokyo airband built in) is data an agent can read and choose from; airband entries are AM, the intermittent ones with squelch defaults. Raw frequencies are first-class too: known ones resolve back to their roster entry, unknown airband frequencies (108-137 MHz) default to AM with squelch, and anything outside the tuner's 24-1766 MHz is rejected.
  • AGENTS.md ships in the repo — ground rules telling the agent: you pick the station, you verify what's on air by listening, you own reception quality.

Requirements

  • An RTL-SDR USB dongle (developed against RTL-SDR Blog V4 / R828D)
  • macOS or Linux with:
    • airspy-fmradion — the preferred demodulator (SDR++-grade DSP, AGC, FM stereo, AM airband, Japanese 50 µs de-emphasis); the formula in this tap builds it from source
    • rtl_fm — FM-only fallback; brew install rtl-sdr (librtlsdr ≥ 2.0.1 for the V4)
    • playbrew install sox
  • Optional: a speech-to-text tool for the "what's playing?" loop — the agent transcribes captured WAVs itself (e.g. brew install openai-whisper)
  • For development: Elixir ≥ 1.18 (JSON module), OTP ≥ 27

Install

Via Homebrew — the repository doubles as a tap; the formula pulls the release binary plus every runtime dependency (airspy-fmradion built from source, librtlsdr, sox):

brew tap kentaro/aether https://github.com/kentaro/aether
brew install aether        # short name works once the tap is added

Or grab a single-file binary from GitHub Releases (built by Burrito for macOS/Linux × arm64/x86_64), or build from source:

mix deps.get
mix check                       # format + credo + tests
MIX_ENV=prod mix release        # needs zig 0.16 → burrito_out/aether_*

Use

One executable, two roles:

aether serve &                      # the radio daemon (holds the SDR)

aether stations                     # what can I tune?
aether tune nhk                     # fuzzy: name, alias, or MHz
aether tune atis                    # airband (AM): Haneda ATIS/Tower, Tokyo Approach
aether status
aether volume 40                    # output volume (1-100)
aether capture 30 /tmp/onair.wav    # the last 30s, as WAV
aether json status                  # every command, machine-readable
aether stop                         # stop playback
aether shutdown                     # stop the daemon

The same protocol is available over the socket directly (echo "tune nhk" | nc -U /tmp/aether.sock), interactively (mix aether), and as an Elixir API:

iex> Aether.listen("j-wave")
{:ok, %Aether.Stations.Station{name: "J-WAVE", mhz: 81.3, ...}}
iex> Aether.capture(30, "/tmp/onair.wav")
{:ok, "/tmp/onair.wav"}
iex> Aether.stop()
:ok

Tuning options: aether tune 82.5 accepts whatever Aether.listen/2 does — gain=40.2 for manual tuner gain, stereo=true for FM stereo, mode=am squelch=24 for airband (ppm= frequency correction applies to the rtl_fm fallback only). Region defaults to :tokyo (config :aether, region: :fukuoka to change).

Agent Skill

Inside this repository, agents learn the radio from AGENTS.md automatically. For every other directory, install the bundled skill (embedded in the binary — no checkout needed):

aether install-skill        # or from the repo: mix aether.install_skill

This writes ~/.claude/skills/aether/SKILL.md for Claude Code, and — when ~/.codex exists — appends the same instructions to ~/.codex/AGENTS.md between marker comments (idempotent; re-run to update). After that, any session responds to "play the radio", "what's playing?", "listen to ATC" — in English or Japanese — by driving the daemon. Other agents can read the same document from skills/aether/SKILL.md.

Design notes

  • Demodulation runs in an external backend — airspy-fmradion when present (AGC, FM stereo, AM airband with squelch), else rtl_fm (FM mono, tuned flags: integer-ratio 240k capture, -F 9 FIR, 15 kHz sinc low-pass in the sink); Elixir orchestrates processes and owns the stream. The PCM rate (96 KB/s mono, 192 KB/s stereo) is trivial for the BEAM, so the audio literally flows through the Aether.Player GenServer, which tees it into the ring buffer on the way to sox.
  • External programs are ports with explicit SIGTERM cleanup — a dead pipeline can't wedge the SDR device.
  • Everything is swappable via application env, which is how the test suite (no hardware, no audible output) runs the full pipeline against shell-script fakes.
  • Protocol logic (Aether.Control.Command) is pure command-line → response rendering, independent of the socket transport.

License

MIT

About

An AI-native radio (FM broadcast + AM airband) built on RTL-SDR and Elixir — no AI inside; designed to be operated by AI agents

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages