feat(bassdrive): add the drum & bass relay and a shared wordmark font

This commit is contained in:
beefcarver committed 2026-11-03 18:44:40 -05:00
1 parent 84ec3b08f6
commit 50a9a735f7
14 files changed
+1166 -79

No files matched your search

+53 -26
View File
@@ -8,9 +8,10 @@ own ANSI palette on the terminal's default background so it fits any theme
(including transparent terminals).
The station catalogue is pluggable. Beefy Player ships with
**[SomaFM](https://somafm.com)** (≈50 curated channels) and
**[SomaFM](https://somafm.com)** (≈50 curated channels),
**[tilderadio](https://tilderadio.org)** (the tildeverse's single community
stream), and the UI never talks to a station catalogue directly: it asks a
stream) and **[Bassdrive](https://bassdrive.com)** (worldwide drum & bass), and
the UI never talks to a station catalogue directly: it asks a
[provider](#providers) for stations, song history and stream URLs. Press `p` to
switch catalogue without restarting. Adding another internet-radio catalogue is
a self-contained module, not a rewrite.
@@ -41,7 +42,9 @@ implemented here from public endpoints and open protocols.
iTerm2 inline-image protocol. Images occupy exactly the ON AIR art cell box
and fall back to the procedural ANSI mosaic for other terminals, failed
loads, or blocked graphics passthrough. Images are cached in
`~/.cache/beefy-player/artwork`.
`~/.cache/beefy-player/artwork`, letterboxed rather than cropped, and an
image that carries an alpha channel is composited onto black — a transparent
PNG drawn over the mosaic reads as noise behind a station logo.
- **Now playing** — artist, title, album, DJ, listeners, format/bitrate and
"played N minutes ago", with procedural ANSI-shaded album art that changes
per track. When the current track can be found on Bandcamp, a clickable
@@ -57,9 +60,10 @@ implemented here from public endpoints and open protocols.
- **Pluggable catalogues** — one `providers` module per radio network, with a
`p` picker in the UI, `--provider` on the command line, and the choice
remembered per catalogue (including the last station you tuned).
- **A hero per catalogue** — the top-left art comes from the provider: SomaFM's
block wordmark, or tilderadio's radio icon converted from its logo PNG to
half-block ANSI by `tools/ansi_art.py`.
- **A hero per catalogue** — the top-left art is the catalogue's own name in a
shared half-block font: SomaFM in the spectrum gradient, tilderadio in the
terminal's default colour, Bassdrive in its two blues. Every hero is three
rows tall, so the header keeps one height.
- **Schedule pane** — a single-stream catalogue gets a broadcast schedule
instead of a list of one station: who is on air now, since/until when, and
the upcoming shows grouped by day.
@@ -193,35 +197,45 @@ layout and the guessed icecast mirrors all live in
`beefy_player/providers/somafm/`. Nothing outside that package imports it; the
app only ever holds a `RadioProvider`.
Two catalogues ship today:
Three catalogues ship today:
| slug | catalogue | station list | history | streams |
| --- | --- | --- | --- | --- |
| `somafm` | [SomaFM](https://somafm.com) | `channels.json` (≈50) | `songs/<station>.json` | `.pls` → icecast mirrors |
| `tilderadio` | [tilderadio](https://tilderadio.org) | `/api/now/` (1) | `now_playing` + `history[]` | AzuraCast mounts (mp3/ogg, 320k) |
| `bassdrive` | [Bassdrive](https://bassdrive.com) | icecast status (1) | rolling `display-title` | three mounts + mirrors |
They are deliberately different shapes: one has fifty channels and PLS
playlists, the other has a single stream whose whole catalogue arrives in the
same response as its history. That is the seam working.
They are deliberately different shapes: fifty channels with PLS playlists, a
single AzuraCast stream that carries its own history, and an Icecast relay whose
"now playing" is the *show* rather than the track. That is the seam working.
The tilderadio package also exercises the two optional capabilities: its left
pane is the broadcast schedule (`/api/schedule/`, with the live DJ coming from
`/api/now/`) rather than a list containing one station, and its header art is
the station's own radio icon converted to half blocks. The icon is generated,
not hand-drawn — regenerate it after the logo changes:
The single-stream catalogues also use the optional capabilities: their left
pane is the broadcast schedule rather than a list containing one station, and
they opt out of Bandcamp lookups. Bassdrive is the extreme case — Icecast
reports one title and keeps no history, so the provider maps the title to
`Station.dj`/`presenter` and keeps a rolling record of the shows it has heard.
```bash
uv run python tools/ansi_art.py \
--url https://tilderadio.org/logos/tilderadio.png \
--cols 22 --rows 7 \
--module beefy_player/providers/tilderadio/hero.py
### Header wordmarks
All three heroes come from one font, `beefy_player/providers/wordmark.py`:
four-column capitals six pixels tall, emitted as three rows of half blocks
(`▀`/`▄`/`█`) — the shape of the original SomaFM logo. A provider's `hero.py`
is one line of data:
```python
# beefy_player/providers/bassdrive/hero.py
HERO = render("BASSDRIVE", ["bright_cyan"] * 4 + ["cyan"] * 5)
```
`tools/ansi_art.py` crops the transparent margin, resizes to 22×14 pixels (two
per text cell), quantises to the stock 16-colour ANSI palette and emits
run-length encoded rich markup — so the art follows the terminal's palette
instead of pinning RGB values. `--preview out.png` draws the result, which is
far easier to judge than ▀ and ▄.
`render()` takes one optional style per character and groups equal neighbours
into markup spans; with no styles the wordmark inherits the terminal's default
foreground (tilderadio). Add a glyph to `GLYPHS` when a catalogue needs a letter
the font does not have yet — `▀█▀` is the I, one pixel thick on purpose.
Image logos are a different problem: `tools/ansi_art.py` converts a PNG into
half-block art (`--key-white` for logos on white, `--preview out.png` to see the
result). No shipped catalogue needs it today, but it is the tool to reach for
when a station's identity is a picture rather than a word.
Adding a catalogue:
@@ -347,8 +361,10 @@ beefy_player/
config.py XDG paths, settings, BEEFY_* env helpers
providers/
base.py RadioProvider interface + HTTP/playlist toolkit
wordmark.py the half-block font every hero is drawn with
somafm/ the SomaFM catalogue (API, hero, bundled fallback list)
tilderadio/ the tilderadio catalogue (one station, hero, schedule)
bassdrive/ Bassdrive (Icecast status, show schedule, rolling history)
player.py mpv over a Unix-socket JSON IPC channel
spectrum.py analyser (parec / Core Audio tap / ffmpeg / sim)
macos_tap.py macOS output capture (ctypes + Core Audio)
@@ -382,6 +398,14 @@ tools/
the provider in place: the old stream is stopped, workers for the previous
catalogue are cancelled, the hero height and left pane follow the new
provider, and the station list (or schedule) reloads from cache first.
- `beefy_player/providers/bassdrive/api.py` — Icecast's keyless
`status-json.xsl` (mounts, bitrates, listeners, `display-title`) plus the
homepage schedule grid, whose slots carry absolute `<local-time datetime>`
values. A memoised fetch and a lock keep a cold start to one homepage
download; the display-title's show is matched against the running slot for
"live from <city>".
- `beefy_player/providers/wordmark.py` — the half-block capitals behind every
hero, with per-character style spans.
- `beefy_player/widgets/schedule.py` — the left pane for single-stream
catalogues: the live slot with its `since`/`until` window, then upcoming
shows grouped by local day, with `… +N more` when the pane runs out.
@@ -419,7 +443,8 @@ tools/
~/.cache/beefy-player/channels-<id>.json last good list, other catalogues
~/.cache/beefy-player/schedule-<id>.json last good broadcast schedule
~/.cache/beefy-player/playlists.json resolved stream URLs
~/.cache/beefy-player/songs/<id>.json song history snapshots
~/.cache/beefy-player/songs/<id>.json SomaFM song history snapshots
~/.cache/beefy-player/songs/<p>-<id>.json history for every other catalogue
~/.cache/beefy-player/artwork/<sha1> cached album/channel artwork
```
@@ -488,6 +513,8 @@ so favourites and volume survive the rename.
- SomaFM is listener-supported: if you enjoy it, [donate](https://somafm.com/support/).
- tilderadio is a volunteer-run community station: [drop by](https://tilderadio.org),
or show up on the [schedule](https://tilderadio.org/schedule/) with a set.
- Bassdrive is free and listener-supported too: [bassdrive.com](https://bassdrive.com)
has the shows, archives and the shop that keeps the relays running.
## NixOS (optional aside)