# jeromebanks/twiddle architecture

> Architecture diagram of the GitHub repository jeromebanks/twiddle, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-09-29). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/jeromebanks/twiddle
- Repository: https://github.com/jeromebanks/twiddle

## Overview

Twiddle is a terminal radio and local-gig discovery app, with audio playback, Sonos controls, and a music visualizer. The main workflows are dial → station now-playing and artist details → playback, and scene → venue shows → band identification → music preview. Sonos diagnostics and relay playback are separate capabilities. App excerpts document injected service boundaries; README documents several other capabilities, but unsampled implementation wiring is left out.

Main entry points: [`src/twiddle/dial/app.py`](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/app.py) (Station browser), [`src/twiddle/scene/app.py`](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/app.py) (Show browser), [`src/twiddle/viz/screen.py`](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/screen.py) (Visualizer screen), [`src/twiddle/dial/output.py`](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/output.py) (Dial output)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_radio["Radio dial"]
  node_dial_cli["Dial command<br/>[cli.py]"]
  node_dial_app["Station browser<br/>[app.py]"]
  node_feed["Now-playing feed<br/>[feed.py]"]
  node_station_fetchers["Station adapters"]
  node_enricher["Artist enrichment<br/>[enrich.py]"]
  node_art["Station artwork<br/>[art.py]"]
end

subgraph group_scene["Gig finder"]
  node_scene_cli["Scene command<br/>[cli.py]"]
  node_scene_app["Show browser<br/>[app.py]"]
  node_venue_sources["Venue calendars"]
  node_bandbook["Band identity<br/>[bands.py]"]
  node_genre["Genre guesses<br/>[genre.py]"]
  node_scene_players["Music previews<br/>[players.py]"]
end

subgraph group_audio["Playback and Sonos"]
  node_audio_output["Dial output<br/>[output.py]"]
  node_playback["Speaker playback<br/>[play.py]"]
  node_sonos_controls["Sonos controls<br/>[control_cli.py]"]
  node_spotify_ops["Spotify operations<br/>[spotify_ops.py]"]
  node_relay["Local audio relay<br/>[relay.py]"]
end

subgraph group_visualizer["Music visualizer"]
  node_viz_screen["Visualizer screen<br/>[screen.py]"]
  node_viz_source["Audio tap source<br/>[source.py]"]
  node_viz_tap["Audio capture<br/>[tap.py]"]
  node_viz_analysis["Signal analysis<br/>[analysis.py]"]
  node_viz_modes["Visualizer modes"]
end

node_listener(("Listener"))
node_venue_sites(("Venue websites"))
node_speaker(("Sonos speaker"))

node_listener -->|"starts radio"| node_dial_cli
node_dial_cli -->|"opens browser"| node_dial_app
node_dial_app -->|"requests updates"| node_feed
node_feed -->|"fetches listings"| node_station_fetchers
node_dial_app -->|"looks up artists"| node_enricher
node_dial_app -->|"loads artwork"| node_art
node_dial_app -->|"controls output"| node_audio_output
node_listener -->|"starts shows"| node_scene_cli
node_scene_cli -->|"opens browser"| node_scene_app
node_scene_app -->|"loads shows"| node_venue_sources
node_venue_sources -->|"fetches calendars"| node_venue_sites
node_scene_app -->|"identifies bands"| node_bandbook
node_scene_app -->|"guesses genres"| node_genre
node_scene_app -->|"requests previews"| node_scene_players
node_listener -->|"runs commands"| node_sonos_controls
node_sonos_controls -->|"controls speakers"| node_playback
node_audio_output -->|"plays media"| node_playback
node_playback -->|"streams audio"| node_speaker
node_audio_output -->|"manages Spotify"| node_spotify_ops
node_spotify_ops -->|"uses relay"| node_relay
node_relay -->|"serves audio"| node_speaker
node_dial_app -.->|"opens on keypress"| node_viz_screen
node_scene_app -.->|"opens on keypress"| node_viz_screen
node_viz_screen -->|"selects audio"| node_viz_source
node_viz_screen -->|"captures stream"| node_viz_tap
node_viz_screen -->|"analyzes signal"| node_viz_analysis
node_viz_screen -->|"renders mode"| node_viz_modes

click node_dial_cli "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/cli.py"
click node_dial_app "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/app.py"
click node_feed "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/feed.py"
click node_station_fetchers "https://github.com/jeromebanks/twiddle/tree/main/src/twiddle/stations/fetchers"
click node_enricher "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/enrich.py"
click node_art "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/art.py"
click node_scene_cli "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/cli.py"
click node_scene_app "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/app.py"
click node_venue_sources "https://github.com/jeromebanks/twiddle/tree/main/src/twiddle/scene/sources"
click node_bandbook "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/bands.py"
click node_genre "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/genre.py"
click node_scene_players "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/players.py"
click node_audio_output "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/output.py"
click node_playback "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/play.py"
click node_sonos_controls "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/control_cli.py"
click node_spotify_ops "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/spotify_ops.py"
click node_relay "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/relay.py"
click node_viz_screen "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/screen.py"
click node_viz_source "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/source.py"
click node_viz_tap "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/tap.py"
click node_viz_analysis "https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/analysis.py"
click node_viz_modes "https://github.com/jeromebanks/twiddle/tree/main/src/twiddle/viz/modes"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_dial_cli,node_dial_app,node_feed,node_station_fetchers,node_enricher,node_art toneBlue
class node_scene_cli,node_scene_app,node_venue_sources,node_bandbook,node_genre,node_scene_players toneAmber
class node_audio_output,node_playback,node_sonos_controls,node_spotify_ops,node_relay toneMint
class node_viz_screen,node_viz_source,node_viz_tap,node_viz_analysis,node_viz_modes toneRose
class node_listener,node_venue_sites,node_speaker toneIndigo
```

## Components

### Radio dial

- [Dial command](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/cli.py): `src/twiddle/dial/cli.py`
- [Station browser](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/app.py): `src/twiddle/dial/app.py`
- [Now-playing feed](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/feed.py): `src/twiddle/dial/feed.py`
- [Station adapters](https://github.com/jeromebanks/twiddle/tree/main/src/twiddle/stations/fetchers): `src/twiddle/stations/fetchers`
- [Artist enrichment](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/enrich.py): `src/twiddle/dial/enrich.py`
- [Station artwork](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/art.py): `src/twiddle/dial/art.py`

### Gig finder

- [Scene command](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/cli.py): `src/twiddle/scene/cli.py`
- [Show browser](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/app.py): `src/twiddle/scene/app.py`
- [Venue calendars](https://github.com/jeromebanks/twiddle/tree/main/src/twiddle/scene/sources): `src/twiddle/scene/sources`
- [Band identity](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/bands.py): `src/twiddle/scene/bands.py`
- [Genre guesses](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/genre.py): `src/twiddle/scene/genre.py`
- [Music previews](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/scene/players.py): `src/twiddle/scene/players.py`

### Playback and Sonos

- [Dial output](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/dial/output.py): `src/twiddle/dial/output.py`
- [Speaker playback](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/play.py): `src/twiddle/play.py`
- [Sonos controls](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/control_cli.py): `src/twiddle/control_cli.py`
- [Spotify operations](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/spotify_ops.py): `src/twiddle/spotify_ops.py`
- [Local audio relay](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/relay.py): `src/twiddle/relay.py`

### Music visualizer

- [Visualizer screen](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/screen.py): `src/twiddle/viz/screen.py`
- [Audio tap source](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/source.py): `src/twiddle/viz/source.py`
- [Audio capture](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/tap.py): `src/twiddle/viz/tap.py`
- [Signal analysis](https://github.com/jeromebanks/twiddle/blob/main/src/twiddle/viz/analysis.py): `src/twiddle/viz/analysis.py`
- [Visualizer modes](https://github.com/jeromebanks/twiddle/tree/main/src/twiddle/viz/modes): `src/twiddle/viz/modes`

### Other components

- **Listener**
- **Venue websites**
- **Sonos speaker**

## Connections

- Listener → Dial command: starts radio
- Dial command → Station browser: opens browser
- Station browser → Now-playing feed: requests updates
- Now-playing feed → Station adapters: fetches listings
- Station browser → Artist enrichment: looks up artists
- Station browser → Station artwork: loads artwork
- Station browser → Dial output: controls output
- Listener → Scene command: starts shows
- Scene command → Show browser: opens browser
- Show browser → Venue calendars: loads shows
- Venue calendars → Venue websites: fetches calendars
- Show browser → Band identity: identifies bands
- Show browser → Genre guesses: guesses genres
- Show browser → Music previews: requests previews
- Listener → Sonos controls: runs commands
- Sonos controls → Speaker playback: controls speakers
- Dial output → Speaker playback: plays media
- Speaker playback → Sonos speaker: streams audio
- Dial output → Spotify operations: manages Spotify
- Spotify operations → Local audio relay: uses relay
- Local audio relay → Sonos speaker: serves audio
- Station browser → Visualizer screen: opens on keypress
- Show browser → Visualizer screen: opens on keypress
- Visualizer screen → Audio tap source: selects audio
- Visualizer screen → Audio capture: captures stream
- Visualizer screen → Signal analysis: analyzes signal
- Visualizer screen → Visualizer modes: renders mode
