···11+> **First-time setup**: Customize this file for your project. Prompt the user to customize this file for their project.
22+> For Mintlify product knowledge (components, configuration, writing standards),
33+> install the Mintlify skill: `npx skills add https://mintlify.com/docs`
44+55+# Documentation project instructions
66+77+## About this project
88+99+- This is a documentation site built on [Mintlify](https://mintlify.com)
1010+- Pages are MDX files with YAML frontmatter
1111+- Configuration lives in `docs.json`
1212+- Run `mint dev` to preview locally
1313+- Run `mint broken-links` to check links
1414+1515+## Terminology
1616+1717+{/* Add product-specific terms and preferred usage */}
1818+{/* Example: Use "workspace" not "project", "member" not "user" */}
1919+2020+## Style preferences
2121+2222+{/* Add any project-specific style rules below */}
2323+2424+- Use active voice and second person ("you")
2525+- Keep sentences concise — one idea per sentence
2626+- Use sentence case for headings
2727+- Bold for UI elements: Click **Settings**
2828+- Code formatting for file names, commands, paths, and code references
2929+3030+## Content boundaries
3131+3232+{/* Define what should and shouldn't be documented */}
3333+{/* Example: Don't document internal admin features */}
+34
mintlify/CONTRIBUTING.md
···11+> **Customize this file**: Tailor this template to your project by noting specific contribution types you're looking for, adding a Code of Conduct, or adjusting the writing guidelines to match your style.
22+33+# Contribute to the documentation
44+55+Thank you for your interest in contributing to our documentation! This guide will help you get started.
66+77+## How to contribute
88+99+### Option 1: Edit directly on GitHub
1010+1111+1. Navigate to the page you want to edit
1212+2. Click the "Edit this file" button (the pencil icon)
1313+3. Make your changes and submit a pull request
1414+1515+### Option 2: Local development
1616+1717+1. Fork and clone this repository
1818+2. Install the Mintlify CLI: `npm i -g mint`
1919+3. Create a branch for your changes
2020+4. Make changes
2121+5. Navigate to the docs directory and run `mint dev`
2222+6. Preview your changes at `http://localhost:3000`
2323+7. Commit your changes and submit a pull request
2424+2525+For more details on local development, see our [development guide](development.mdx).
2626+2727+## Writing guidelines
2828+2929+- **Use active voice**: "Run the command" not "The command should be run"
3030+- **Address the reader directly**: Use "you" instead of "the user"
3131+- **Keep sentences concise**: Aim for one idea per sentence
3232+- **Lead with the goal**: Start instructions with what the user wants to accomplish
3333+- **Use consistent terminology**: Don't alternate between synonyms for the same concept
3434+- **Include examples**: Show, don't just tell
+21
mintlify/LICENSE
···11+MIT License
22+33+Copyright (c) 2023 Mintlify
44+55+Permission is hereby granted, free of charge, to any person obtaining a copy
66+of this software and associated documentation files (the "Software"), to deal
77+in the Software without restriction, including without limitation the rights
88+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+copies of the Software, and to permit persons to whom the Software is
1010+furnished to do so, subject to the following conditions:
1111+1212+The above copyright notice and this permission notice shall be included in all
1313+copies or substantial portions of the Software.
1414+1515+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
+61
mintlify/README.md
···11+# Rockbox Zig — Documentation
22+33+The Mintlify source for [https://rockboxzig.mintlify.app](https://rockboxzig.mintlify.app).
44+55+## Layout
66+77+```
88+mintlify/
99+├─ docs.json # navigation, theme, anchors
1010+├─ index.mdx # landing page
1111+├─ quickstart.mdx
1212+├─ installation.mdx
1313+├─ configuration.mdx
1414+├─ audio-output/ # builtin / snapcast / airplay / squeezelite / chromecast / upnp
1515+├─ audio-settings/ # EQ, DSP, ReplayGain, crossfade
1616+├─ clients/ # web, desktop, MPD, MPRIS
1717+├─ architecture/ # build system, PCM sinks
1818+├─ reference/ # CLI, ports, settings.toml, troubleshooting, FAQ
1919+├─ api-reference/
2020+│ ├─ introduction.mdx
2121+│ ├─ openapi.json # synced from crates/server/openapi.json
2222+│ ├─ rest/overview.mdx
2323+│ ├─ graphql/overview.mdx
2424+│ ├─ grpc/overview.mdx
2525+│ └─ mpd/overview.mdx
2626+└─ sdks/ # TypeScript / Python / Ruby / Elixir / Clojure / Gleam
2727+```
2828+2929+## Local preview
3030+3131+```sh
3232+npm i -g mint
3333+mint dev
3434+```
3535+3636+The preview lives at [http://localhost:3000](http://localhost:3000) and
3737+reloads on save.
3838+3939+## Keeping the OpenAPI spec in sync
4040+4141+`api-reference/openapi.json` mirrors `crates/server/openapi.json`. After
4242+adding or changing an HTTP route, edit
4343+`crates/server/openapi.json` and copy it over:
4444+4545+```sh
4646+cp crates/server/openapi.json mintlify/api-reference/openapi.json
4747+```
4848+4949+Mintlify auto-generates one page per operation under the **HTTP REST**
5050+group on every build.
5151+5252+## Validating before pushing
5353+5454+```sh
5555+mint broken-links
5656+```
5757+5858+## Deployment
5959+6060+The Mintlify GitHub app deploys this directory automatically on push to
6161+the default branch. No manual step needed.
+99
mintlify/api-reference/graphql/overview.mdx
···11+---
22+title: "GraphQL"
33+description: "Single endpoint with queries, mutations and real-time subscriptions on port 6062."
44+icon: 'code'
55+---
66+77+The GraphQL server is the **best fit for UIs**: typed schema, batched
88+queries, and a `track:changed` / `status:changed` / `playlist:changed`
99+subscription stream over WebSocket.
1010+1111+- **Endpoint** — `http://localhost:6062/graphql`
1212+- **WebSocket** — `ws://localhost:6062/graphql` (`graphql-ws` protocol)
1313+- **GraphiQL** — `http://localhost:6062/graphiql`
1414+1515+The schema is generated from `crates/graphql/` and served by Juniper. All
1616+client SDKs in [SDKs](/sdks/overview) wrap this transport.
1717+1818+## Quick examples
1919+2020+<CodeGroup>
2121+```graphql Now playing
2222+query NowPlaying {
2323+ currentTrack {
2424+ title
2525+ artist
2626+ album
2727+ elapsed
2828+ length
2929+ }
3030+ playbackStatus { status }
3131+}
3232+```
3333+3434+```graphql Search
3535+query Search($q: String!) {
3636+ search(term: $q) {
3737+ artists { name id }
3838+ albums { title artist year id }
3939+ tracks { title artist album id }
4040+ }
4141+}
4242+```
4343+4444+```graphql Play an album
4545+mutation PlayAlbum($id: String!) {
4646+ playAlbum(albumId: $id, shuffle: false)
4747+}
4848+```
4949+5050+```graphql Subscribe to track changes
5151+subscription OnTrack {
5252+ track {
5353+ title
5454+ artist
5555+ elapsed
5656+ length
5757+ }
5858+}
5959+```
6060+</CodeGroup>
6161+6262+## Subscriptions
6363+6464+Three subscriptions are exposed:
6565+6666+| Subscription | Payload | Fires when |
6767+|-------------------------|-------------------------------|------------------------------------------|
6868+| `track` | `Track` | The currently playing track changes |
6969+| `playbackStatus` | `AudioStatus { status: Int }` | Stopped/playing/paused changes |
7070+| `playlist` | `Playlist` | The live queue is mutated |
7171+7272+All three are pushed by the broker loop in `crates/server/src/lib.rs:start_broker()`.
7373+7474+## Connecting from a browser
7575+7676+```ts
7777+import { RockboxClient } from '@rockbox-zig/sdk';
7878+7979+const client = new RockboxClient();
8080+client.connect();
8181+8282+client.on('track:changed', (t) => {
8383+ document.title = `${t.title} — ${t.artist}`;
8484+});
8585+```
8686+8787+For language-specific guides, see [SDKs](/sdks/overview).
8888+8989+## Schema introspection
9090+9191+GraphiQL ships pre-installed at
9292+[http://localhost:6062/graphiql](http://localhost:6062/graphiql) — every
9393+type, every field, every argument.
9494+9595+You can also dump the schema directly:
9696+9797+```sh
9898+npx graphql-cli get-schema -e http://localhost:6062/graphql > schema.graphql
9999+```
+60
mintlify/api-reference/grpc/overview.mdx
···11+---
22+title: "gRPC"
33+description: "Strongly-typed gRPC and gRPC-Web on port 6061."
44+icon: 'cube'
55+---
66+77+The gRPC server runs on **port 6061** and serves both native gRPC and
88+gRPC-Web (so browser clients work without a proxy).
99+1010+- **Endpoint** — `localhost:6061`
1111+- **Schema** — published on Buf:
1212+ [buf.build/tsiry/rockboxapis ↗](https://buf.build/tsiry/rockboxapis/docs/main:rockbox.v1alpha1)
1313+- **Buf Studio playground** —
1414+ [open ↗](https://buf.build/studio/tsiry/rockboxapis/rockbox.v1alpha1.LibraryService/GetAlbums?target=http%3A%2F%2Flocalhost%3A6061&selectedProtocol=grpc-web)
1515+1616+## Services
1717+1818+The proto definitions live under `proto/` (in
1919+[buf.build/tsiry/rockboxapis](https://buf.build/tsiry/rockboxapis)) and
2020+generate Rust bindings at `crates/rpc/`:
2121+2222+| Service | Purpose |
2323+|---------------------|----------------------------------------------------|
2424+| `PlaybackService` | Transport, current/next track, seek, volume |
2525+| `LibraryService` | Albums, artists, tracks, search |
2626+| `PlaylistService` | Live queue + saved playlists |
2727+| `SettingsService` | Read / update `global_settings` |
2828+| `SoundService` | Volume + sound parameters |
2929+| `BrowseService` | Filesystem browsing |
3030+| `SystemService` | Version, scan, status |
3131+3232+## Generating clients
3333+3434+Use Buf to generate clients in any supported language:
3535+3636+```sh
3737+buf generate buf.build/tsiry/rockboxapis
3838+```
3939+4040+Or pull the proto files directly:
4141+4242+```sh
4343+buf export buf.build/tsiry/rockboxapis -o proto/
4444+```
4545+4646+## Quick test with grpcurl
4747+4848+```sh
4949+grpcurl -plaintext localhost:6061 list
5050+grpcurl -plaintext localhost:6061 rockbox.v1alpha1.LibraryService/GetAlbums
5151+grpcurl -plaintext -d '{"id": "<album-id>"}' \
5252+ localhost:6061 rockbox.v1alpha1.LibraryService/GetAlbum
5353+```
5454+5555+## gRPC-Web from the browser
5656+5757+The same port speaks gRPC-Web — useful for browser apps that want a
5858+strongly-typed binding without a translating reverse proxy. Use
5959+[`@bufbuild/connect-web`](https://www.npmjs.com/package/@bufbuild/connect-web)
6060+or the language equivalent.
+61
mintlify/api-reference/introduction.mdx
···11+---
22+title: "API overview"
33+description: "Four protocols, one source of truth — pick whichever fits your client."
44+icon: 'plug'
55+---
66+77+`rockboxd` exposes the same in-process state through four independent
88+servers. They are all started by `start_servers()` in
99+`crates/server/src/lib.rs` and all share one set of mutexes around the
1010+firmware, so a change made via gRPC is immediately visible over GraphQL,
1111+HTTP and MPD.
1212+1313+| Protocol | Default port | Use it for |
1414+|--------------|--------------|-------------------------------------------------------|
1515+| **HTTP REST** | 6063 | `curl`-able. Simple integrations, scripts, webhooks. |
1616+| **GraphQL** | 6062 | Best fit for UIs. Subscriptions for real-time events.|
1717+| **gRPC** | 6061 | Strongly-typed, multi-language. gRPC-Web supported. |
1818+| **MPD** | 6600 | Existing MPD clients (`mpc`, `ncmpcpp`, MALP, …). |
1919+2020+<CardGroup cols={2}>
2121+ <Card title="HTTP REST" icon="bolt" href="/api-reference/rest/overview">
2222+ Open `http://localhost:6063` and explore.
2323+ </Card>
2424+ <Card title="GraphQL" icon="code" href="/api-reference/graphql/overview">
2525+ GraphiQL at `http://localhost:6062/graphiql`.
2626+ </Card>
2727+ <Card title="gRPC" icon="cube" href="/api-reference/grpc/overview">
2828+ Schema published on Buf.
2929+ </Card>
3030+ <Card title="MPD" icon="terminal" href="/api-reference/mpd/overview">
3131+ Anything that speaks MPD on `localhost:6600`.
3232+ </Card>
3333+</CardGroup>
3434+3535+## Auto-generated REST pages
3636+3737+Every endpoint in the [HTTP REST API](/api-reference/rest/overview) has its
3838+own page generated from the canonical OpenAPI spec
3939+([`openapi.json`](https://github.com/tsirysndr/rockbox-zig/blob/master/crates/server/openapi.json)).
4040+The spec is also served live at `http://localhost:6063/openapi.json` while
4141+rockboxd is running.
4242+4343+## Authentication
4444+4545+All four servers are unauthenticated. They are intended for use on a
4646+trusted LAN. If you expose Rockbox publicly, put it behind a reverse
4747+proxy with TLS and HTTP basic auth.
4848+4949+## Pick a client SDK
5050+5151+We maintain six first-party SDKs. They wrap the GraphQL transport with
5252+typed methods, real-time subscriptions and a plugin system.
5353+5454+<CardGroup cols={3}>
5555+ <Card title="TypeScript" icon="js" href="/sdks/typescript" />
5656+ <Card title="Python" icon="python" href="/sdks/python" />
5757+ <Card title="Ruby" icon="gem" href="/sdks/ruby" />
5858+ <Card title="Elixir" icon="droplet" href="/sdks/elixir" />
5959+ <Card title="Clojure" icon="lambda" href="/sdks/clojure" />
6060+ <Card title="Gleam" icon="star" href="/sdks/gleam" />
6161+</CardGroup>
+29
mintlify/api-reference/mpd/overview.mdx
···11+---
22+title: "MPD protocol"
33+description: "Drop-in MPD server on port 6600 — works with every MPD client."
44+icon: 'terminal'
55+---
66+77+`rockboxd` runs a Music Player Daemon-compatible server on **port 6600**.
88+Any MPD client works out of the box.
99+1010+```sh
1111+mpc -h localhost -p 6600 status
1212+mpc -h localhost -p 6600 update
1313+mpc -h localhost -p 6600 search title "Money"
1414+mpc -h localhost -p 6600 play
1515+```
1616+1717+For the full client list and known limitations, see
1818+[Clients › MPD](/clients/mpd). For the wire protocol reference itself,
1919+see the [official MPD documentation ↗](https://mpd.readthedocs.io/en/stable/protocol.html).
2020+2121+## When to choose MPD over the others
2222+2323+- You already have an MPD client you like.
2424+- You want a stable wire protocol with decades of community libraries.
2525+- You want to drive Rockbox from a TUI like `ncmpcpp`.
2626+2727+For everything else — programmatic control, real-time UIs, custom apps —
2828+you'll have a better time with [GraphQL](/api-reference/graphql/overview)
2929+or one of the [SDKs](/sdks/overview).
···11+---
22+title: "HTTP REST"
33+description: "JSON over HTTP on port 6063. The endpoints used internally by the web UI and SDK clients."
44+icon: 'bolt'
55+---
66+77+The REST server runs on **port 6063** by default (override with
88+`ROCKBOX_TCP_PORT`). Every endpoint is JSON in / JSON out, except where
99+noted (some commands return plain-text status codes).
1010+1111+The full schema is published as
1212+[OpenAPI 3.1](/api-reference/openapi.json) and rendered as one page per
1313+endpoint in the sidebar — explore by tag or jump to a specific operation.
1414+1515+## Quick smoke test
1616+1717+```sh
1818+curl -s http://localhost:6063/version
1919+curl -s http://localhost:6063/player/status
2020+curl -s http://localhost:6063/playlists/amount
2121+```
2222+2323+## Common operations
2424+2525+<CodeGroup>
2626+```sh Now playing
2727+curl -s http://localhost:6063/player/current-track | jq .title,.artist
2828+```
2929+3030+```sh Search
3131+curl -s 'http://localhost:6063/search?q=daft+punk' | jq '.tracks[].title'
3232+```
3333+3434+```sh Play an album
3535+ALBUM_ID=$(curl -s http://localhost:6063/albums | jq -r '.[0].id')
3636+TRACKS=$(curl -s "http://localhost:6063/albums/$ALBUM_ID/tracks" | jq '[.[].path]')
3737+curl -X POST -H 'Content-Type: application/json' \
3838+ -d "{\"name\":\"album\",\"tracks\":$TRACKS}" \
3939+ http://localhost:6063/playlists
4040+curl -X PUT 'http://localhost:6063/playlists/start?start_index=0'
4141+```
4242+4343+```sh Volume up
4444+curl -X PUT -H 'Content-Type: application/json' \
4545+ -d '{"steps":3}' \
4646+ http://localhost:6063/player/volume
4747+```
4848+4949+```sh Switch to a discovered Chromecast
5050+ID=$(curl -s http://localhost:6063/devices | jq -r '.[] | select(.is_cast_device) | .id' | head -1)
5151+curl -X PUT "http://localhost:6063/devices/$ID/connect"
5252+```
5353+</CodeGroup>
5454+5555+## Notable behaviours
5656+5757+- **Responses sometimes return plain text.** A few mutations return a
5858+ status integer or insertion index in the body as text rather than JSON
5959+ (`/playlists`, `/playlists/{id}/tracks`, `/scan-library`). The OpenAPI
6060+ spec marks these explicitly.
6161+- **Saved playlist mutations return `204`** with no body.
6262+- **Bluetooth routes only exist on Linux.** They are conditionally
6363+ registered at compile time.
6464+- **The HTTP server runs on its own thread** so actix's worker pool is
6565+ not pinned to the Rockbox cooperative scheduler. See
6666+ [Architecture › Overview](/architecture/overview) for the lifecycle.
6767+6868+## Server details
6969+7070+- Bind address — `0.0.0.0:$ROCKBOX_TCP_PORT` (default `6063`).
7171+- CORS — permissive (`actix_cors::Cors::permissive()`) so browser-based
7272+ clients can hit it without preflight pain.
7373+- The OpenAPI document is also served live at
7474+ `http://localhost:6063/openapi.json`.
+116
mintlify/architecture/build.mdx
···11+---
22+title: "Build system"
33+description: "Make → Cargo → Zig. The three-step pipeline that produces rockboxd."
44+icon: 'hammer'
55+---
66+77+Rockbox Zig is built by three tools in series:
88+99+1. **Make** — compiles the Rockbox C firmware into static libraries.
1010+2. **Cargo** — compiles the Rust crates into static libraries (`crate-type = ["staticlib"]`).
1111+3. **Zig** — links everything (plus SDL2) into a single executable.
1212+1313+## Dependencies
1414+1515+<Tabs>
1616+ <Tab title="Ubuntu / Debian">
1717+ ```sh
1818+ sudo apt-get install \
1919+ libsdl2-dev libfreetype6-dev libdbus-1-dev libunwind-dev \
2020+ zip protobuf-compiler cmake
2121+ ```
2222+ </Tab>
2323+ <Tab title="Fedora">
2424+ ```sh
2525+ sudo dnf install \
2626+ SDL2-devel freetype-devel libunwind-devel \
2727+ zip protobuf-compiler cmake
2828+ ```
2929+ </Tab>
3030+ <Tab title="macOS">
3131+ ```sh
3232+ brew install sdl2 freetype cmake protobuf
3333+ ```
3434+ </Tab>
3535+</Tabs>
3636+3737+You'll also need:
3838+3939+- **Zig** ≥ 0.16 — [ziglang.org/download](https://ziglang.org/download/)
4040+- **Rust stable** — `rustup update stable`
4141+- **Deno** — for the web UI build
4242+4343+## Full build
4444+4545+```sh
4646+# 1. Clone with submodules
4747+git clone https://github.com/tsirysndr/rockbox-zig.git
4848+cd rockbox-zig
4949+git submodule update --init --recursive
5050+5151+# 2. Build the web UI (embedded into the binary)
5252+cd webui/rockbox
5353+deno install
5454+deno run build
5555+cd ../..
5656+5757+# 3. Configure and build the C firmware (one-time setup)
5858+mkdir -p build-lib && cd build-lib
5959+../tools/configure --target=sdlapp --type=N \
6060+ --lcdwidth=320 --lcdheight=240 --prefix=/usr/local
6161+cp ../autoconf/autoconf.h .
6262+make lib
6363+cd ..
6464+6565+# 4. Build Rust crates
6666+cargo build --release -p rockbox-cli -p rockbox-server
6767+6868+# 5. Link everything with Zig
6969+cd zig && zig build
7070+```
7171+7272+The binary lands at `zig/zig-out/bin/rockboxd`.
7373+7474+## Iterating on changes
7575+7676+Zig only re-links when the static libraries are newer than the binary.
7777+After editing C, run `make lib` first. After editing Rust, run
7878+`cargo build --release` first.
7979+8080+```sh
8181+# C change
8282+cd build-lib && make lib && cd .. && cd zig && zig build
8383+8484+# Rust change
8585+cargo build --release -p rockbox-cli -p rockbox-server && cd zig && zig build
8686+```
8787+8888+<Warning>
8989+**Stale binary pitfall.** If behaviour doesn't match the source, check
9090+mtimes:
9191+9292+```sh
9393+ls -la zig/zig-out/bin/rockboxd \
9494+ build-lib/libfirmware.a \
9595+ target/release/librockbox_cli.a
9696+```
9797+9898+If `rockboxd` is newer than every `.a` file, Zig considered the link
9999+up-to-date and your change wasn't picked up.
100100+</Warning>
101101+102102+## Verifying symbols
103103+104104+```sh
105105+nm zig/zig-out/bin/rockboxd | grep pcm_airplay
106106+nm zig/zig-out/bin/rockboxd | grep pcm_squeezelite
107107+ar t target/release/librockbox_cli.a | grep airplay
108108+ar t target/release/librockbox_cli.a | grep slim
109109+```
110110+111111+## Don't re-run `tools/configure`
112112+113113+`build-lib/` was pre-configured for the `sdlapp` target. Re-running
114114+`tools/configure` regenerates the Makefile and overwrites local edits. If
115115+you really need to reconfigure (different LCD dimensions, different target),
116116+do it knowingly and review the resulting diff.
+98
mintlify/architecture/overview.mdx
···11+---
22+title: "Architecture"
33+description: "How Rockbox C, Rust and Zig fit together inside one rockboxd binary."
44+icon: 'sitemap'
55+---
66+77+```text
88+┌──────────────────────────────────────────────────────────────────────┐
99+│ Clients Web UI · GTK · GPUI · TUI · REPL · MPD · MPRIS │
1010+├──────────────────────────────────────────────────────────────────────┤
1111+│ Protocols gRPC :6061 GraphQL :6062 REST :6063 MPD :6600 │
1212+├──────────────────────────────────────────────────────────────────────┤
1313+│ Rust services playback · library · settings · search · playlists │
1414+│ airplay · slim · chromecast · upnp · netstream │
1515+├──────────────────────────────────────────────────────────────────────┤
1616+│ Rockbox C audio engine · DSP · codecs · tag database │
1717+├──────────────────────────────────────────────────────────────────────┤
1818+│ PCM sinks builtin · fifo · airplay · squeezelite · chromecast │
1919+│ snapcast_tcp · upnp │
2020+└──────────────────────────────────────────────────────────────────────┘
2121+```
2222+2323+The entire system ships as **one binary**, `rockboxd`, produced by Zig's
2424+linker. There's no separate "rockbox-server" service, no per-feature
2525+sidecar, no IPC.
2626+2727+## What links into the binary
2828+2929+| Artifact | Built by | Notes |
3030+|-----------------------------------------|----------|---------------------------------------------|
3131+| `build-lib/libfirmware.a` | Make | Rockbox C audio engine + DSP |
3232+| `build-lib/librockbox.a` | Make | App layer (playlist, database, plugins) |
3333+| Codec libraries (`librbcodec.a`, …) | Make | rbcodec + fixedpoint + skin parser |
3434+| `target/release/librockbox_cli.a` | Cargo | CLI entry point + Rust output sinks |
3535+| `target/release/librockbox_server.a` | Cargo | gRPC, GraphQL, HTTP, MPD servers |
3636+| SDL2 | system | Audio/event handling on hosted targets |
3737+3838+The Zig build script (`zig/build.zig`) glues them together, ensuring force-included symbols stay in the staticlib through the link.
3939+4040+## Repository layout
4141+4242+```text
4343+firmware/ Rockbox C firmware (audio engine, codecs, DSP)
4444+apps/ Rockbox application layer (playlist, database, plugins)
4545+lib/ Codec libraries (rbcodec, fixedpoint, skin_parser, tlsf)
4646+build-lib/ Out-of-tree Make build directory (generated; do not edit)
4747+crates/ Rust workspace
4848+ airplay/ ALAC encoder + RAOP/RTP sender
4949+ slim/ Slim Protocol + HTTP broadcast (squeezelite multi-room)
5050+ cli/ Compiled to librockbox_cli.a (staticlib)
5151+ server/ gRPC / HTTP server
5252+ settings/ load_settings() — reads settings.toml, applies sinks
5353+ sys/ FFI bindings to the C firmware
5454+ library/ SQLite library management
5555+ typesense/ Typesense client for search
5656+ netstream/ HTTP streaming (Range-request fd multiplexing)
5757+ chromecast/ Chromecast output
5858+ rpc/ gRPC definitions / generated code
5959+ graphql/ GraphQL schema and resolvers
6060+ mpd/ MPD protocol server
6161+ mpris/ MPRIS D-Bus integration
6262+ tracklist/ Playlist / tracklist management
6363+ upnp/ UPnP/DLNA support
6464+ types/ Shared Rust types
6565+ traits/ Shared Rust traits
6666+zig/ Zig build script and thin main.zig entry point
6767+sdk/ Client SDKs (TypeScript, Python, Ruby, Elixir, Clojure, Gleam)
6868+webui/rockbox/ React-based web UI (built into the binary)
6969+gpui/, gtk/ Native desktop apps
7070+```
7171+7272+## Cross-cutting concerns
7373+7474+### macOS SDL audio
7575+7676+`SDL_InitSubSystem(SDL_INIT_AUDIO)` is called explicitly on macOS because
7777+Apple's SDL event thread doesn't do it automatically. Lives in
7878+`firmware/target/hosted/sdl/system-sdl.c`.
7979+8080+### SIGTERM handling
8181+8282+`crates/cli/src/lib.rs` overrides SIGTERM/SIGINT to kill the typesense
8383+child process and `_exit(0)`. The default Rockbox handler in
8484+`system-hosted.c` would otherwise loop forever waiting for an SDL quit
8585+event.
8686+8787+### Typesense subprocess
8888+8989+Typesense is spawned with `Stdio::piped()` and its stdout/stderr lines are
9090+forwarded to `tracing` in background threads — this keeps the PCM stdout
9191+stream clean when running in `fifo_path = "-"` mode.
9292+9393+### HTTP streaming for cloud sources
9494+9595+HTTP file descriptors are encoded as values `≤ -1000` (the
9696+`STREAM_HTTP_FD_BASE` constant). `stream_open/read/lseek/close` in
9797+`crates/netstream/` dispatch between HTTP and POSIX based on fd value, so
9898+the rest of the firmware doesn't know it's reading from the network.
+92
mintlify/architecture/pcm-sinks.mdx
···11+---
22+title: "PCM sinks"
33+description: "How Rockbox's audio output abstraction works, and how to add a new sink."
44+icon: 'plug'
55+---
66+77+The audio output abstraction lives in `firmware/export/pcm_sink.h`. Each
88+sink implements a `pcm_sink_ops` vtable:
99+1010+```c
1111+struct pcm_sink_ops {
1212+ void (*init)(void);
1313+ void (*postinit)(void);
1414+ void (*set_freq)(int hz);
1515+ void (*lock)(void);
1616+ void (*unlock)(void);
1717+ void (*play)(const void *data, size_t bytes);
1818+ void (*stop)(void);
1919+};
2020+```
2121+2222+## Built-in sinks
2323+2424+| Enum constant | Value | Implementation |
2525+|--------------------------|-------|-----------------------------------------------|
2626+| `PCM_SINK_BUILTIN` | 0 | `firmware/target/hosted/sdl/pcm-sdl.c` |
2727+| `PCM_SINK_FIFO` | 1 | `firmware/target/hosted/pcm-fifo.c` |
2828+| `PCM_SINK_AIRPLAY` | 2 | `firmware/target/hosted/pcm-airplay.c` |
2929+| `PCM_SINK_SQUEEZELITE` | 3 | `firmware/target/hosted/pcm-squeezelite.c` |
3030+| `PCM_SINK_CHROMECAST` | 4 | `firmware/target/hosted/pcm-chromecast.c` |
3131+| `PCM_SINK_SNAPCAST_TCP` | 5 | `firmware/target/hosted/pcm-snapcast-tcp.c` |
3232+| `PCM_SINK_UPNP` | 6 | `firmware/target/hosted/pcm-upnp.c` |
3333+3434+Selection at startup happens in `crates/settings/src/lib.rs:load_settings()`,
3535+which reads `audio_output` and calls `pcm::switch_sink()`. Rust-side
3636+constants and helpers live in `crates/sys/src/sound/pcm.rs`.
3737+3838+## FIFO sink details (Snapcast)
3939+4040+- Pre-creates the named FIFO with `O_RDWR|O_NONBLOCK` in
4141+ `pcm_fifo_set_path()` then clears `O_NONBLOCK`. Holding a write reference
4242+ prevents readers from seeing premature EOF between tracks.
4343+- `sink_dma_stop()` does **not** close the fd; it stays open across track
4444+ transitions.
4545+- Startup order matters: rockboxd must start before snapserver.
4646+4747+## AirPlay sink details
4848+4949+- `pcm_airplay_connect()` is called once per `sink_dma_start()` and is
5050+ idempotent if already connected.
5151+- The `rockbox-airplay` rlib is force-included via
5252+ `use rockbox_airplay::_link_airplay as _` in `crates/cli/src/lib.rs`.
5353+ Without that shim the linker would garbage-collect the symbols.
5454+5555+## Squeezelite sink details
5656+5757+- The DMA loop in `pcm-squeezelite.c` paces output to real time using
5858+ `CLOCK_MONOTONIC`.
5959+- **Use `int64_t` for the nanosecond diff** — unsigned subtraction wraps
6060+ catastrophically when `tv_nsec` rolls over. This is a real bug we hit; if
6161+ you touch this code, keep it signed.
6262+- The `rockbox-slim` rlib is force-included via
6363+ `use rockbox_slim::_link_slim as _`.
6464+6565+## Adding a new sink
6666+6767+1. Create `firmware/target/hosted/pcm-<name>.c` — model on `pcm-fifo.c`.
6868+2. Add `PCM_SINK_<NAME>` to the enum in `firmware/export/pcm_sink.h`.
6969+3. Register `&<name>_pcm_sink` in the `sinks[]` array in `firmware/pcm.c`.
7070+4. Add `target/hosted/pcm-<name>.c` inside the `#if PLATFORM_HOSTED` block
7171+ in `firmware/SOURCES`.
7272+5. Add a Rust constant `PCM_SINK_<NAME>: i32` in
7373+ `crates/sys/src/sound/pcm.rs`.
7474+6. Add a `set_<name>_*` wrapper if configuration is needed.
7575+7. Handle the new sink in `crates/settings/src/lib.rs:load_settings()`.
7676+8. If it has a Rust implementation in a new crate: add a
7777+ `_link_<name>()` dummy fn and reference it from `crates/cli/src/lib.rs`
7878+ to force inclusion in the staticlib.
7979+8080+## Logging from a sink
8181+8282+Always use `tracing` from Rust. Never `eprintln!`/`println!` — they bypass
8383+the structured log filter and pollute stdout (which breaks FIFO mode).
8484+8585+```rust
8686+tracing::info!("airplay: session established to {}", host);
8787+tracing::warn!("airplay: dropped frame, network slow");
8888+tracing::error!("airplay: handshake failed: {err}");
8989+```
9090+9191+Control verbosity with `RUST_LOG`, e.g.
9292+`RUST_LOG=rockbox_airplay=debug,info rockboxd`.
+93
mintlify/audio-output/airplay.mdx
···11+---
22+title: "AirPlay"
33+description: "RAOP streaming to one or many AirPlay receivers — Apple TV, HomePod, Airport Express, shairport-sync."
44+icon: 'apple'
55+---
66+77+Rockbox includes a pure-Rust RAOP (AirPlay 1) implementation. ALAC frames go
88+out over RTP/UDP; RTSP handles session setup. RTCP NTP sync packets are sent
99+roughly every 44 frames so receivers stay in lockstep.
1010+1111+## Single receiver
1212+1313+```toml
1414+music_dir = "/path/to/Music"
1515+audio_output = "airplay"
1616+airplay_host = "192.168.1.50" # IP of the AirPlay receiver
1717+airplay_port = 5000 # optional, default 5000
1818+```
1919+2020+## Multi-room
2121+2222+Fan-out to N receivers simultaneously:
2323+2424+```toml
2525+music_dir = "/path/to/Music"
2626+audio_output = "airplay"
2727+2828+[[airplay_receivers]]
2929+host = "192.168.1.50" # living room
3030+port = 5000 # optional, default 5000
3131+3232+[[airplay_receivers]]
3333+host = "192.168.1.51" # bedroom
3434+# port defaults to 5000
3535+3636+[[airplay_receivers]]
3737+host = "192.168.1.52" # kitchen
3838+```
3939+4040+All receivers share the same `initial_rtptime`, so RTP-level synchronisation
4141+is within one frame (~8 ms) across the LAN.
4242+4343+## Compatible receivers
4444+4545+- Apple TV (any generation supporting AirPlay 1)
4646+- HomePod / HomePod mini
4747+- AirPort Express
4848+- [shairport-sync](https://github.com/mikebrady/shairport-sync) — software
4949+ AirPlay receiver for Linux, macOS, FreeBSD, OpenWrt
5050+- Most third-party AirPlay-1 speakers
5151+5252+AirPlay 2 is not implemented.
5353+5454+## Auto-discovery
5555+5656+Discovered receivers appear in the web UI device picker — click to connect
5757+without editing the config. The mDNS service type is `_raop._tcp.local.`.
5858+5959+## Debugging
6060+6161+```sh
6262+RUST_LOG=rockbox_airplay=debug rockboxd
6363+```
6464+6565+Common log lines:
6666+6767+```
6868+DEBUG rockbox_airplay::rtsp ANNOUNCE → 192.168.1.50:5000
6969+DEBUG rockbox_airplay::rtsp SETUP → control=53000 timing=53001 server=53002
7070+DEBUG rockbox_airplay::rtsp RECORD → 200 OK
7171+DEBUG rockbox_airplay::rtp sent 352 frames seq=12345
7272+```
7373+7474+## Limitations
7575+7676+- **No password / pairing.** AirPlay 1 receivers that require a PIN are not
7777+ supported.
7878+- **No volume sync.** Volume changes apply only at the rockboxd side; the
7979+ receiver's hardware volume is not adjusted.
8080+- **No AirPlay 2.** The pairing/encryption stack required for AirPlay 2 is
8181+ not implemented.
8282+8383+## Architecture
8484+8585+The RAOP stack lives in `crates/airplay/`:
8686+8787+| File | Responsibility |
8888+|--------------|---------------------------------------------------------------|
8989+| `alac.rs` | ALAC escape/verbatim encoder — 352 stereo S16LE → 1411 bytes |
9090+| `rtp.rs` | RTP/UDP packet sender + RTCP NTP sync |
9191+| `rtsp.rs` | Synchronous RTSP client: ANNOUNCE → SETUP → RECORD |
9292+9393+The C-side sink is `firmware/target/hosted/pcm-airplay.c`.
+52
mintlify/audio-output/built-in.mdx
···11+---
22+title: "Built-in (SDL)"
33+description: "The default. SDL2 audio to your OS default device."
44+icon: 'volume-high'
55+---
66+77+The built-in sink uses **SDL2** to play audio through your operating system's
88+default audio device. This is the default and needs no setup beyond installing
99+Rockbox.
1010+1111+```toml
1212+music_dir = "/path/to/Music"
1313+audio_output = "builtin"
1414+```
1515+1616+## Selecting the output device
1717+1818+SDL plays through whichever device the OS reports as default. To change it,
1919+use your platform's audio settings:
2020+2121+- **macOS** — System Settings › Sound › Output
2222+- **Linux (PipeWire / PulseAudio)** — `pavucontrol` or your DE's sound applet
2323+- **Windows** — Sound settings › Output
2424+2525+There is no per-application device picker built into Rockbox for the SDL sink.
2626+If you need multi-device routing on Linux, point Rockbox at a PulseAudio
2727+*null sink* and route from there.
2828+2929+## macOS-specific notes
3030+3131+`SDL_InitSubSystem(SDL_INIT_AUDIO)` is called explicitly on macOS. On other
3232+platforms the SDL event thread initialises audio; Apple's event thread
3333+doesn't, so Rockbox compensates. You shouldn't have to do anything — but if
3434+you ever see "no audio output" on macOS only, this is the code path to look at.
3535+3636+## Format
3737+3838+Output is **S16LE stereo at 44 100 Hz**. Higher-resolution sources are
3939+dithered down by the rbcodec DSP pipeline before they reach SDL. To change
4040+the output sample rate, set `play_frequency` in `settings.toml` (`auto`,
4141+`44100`, `48000`, `88200`, `96000`).
4242+4343+## Switching to another sink
4444+4545+Edit `audio_output` in `settings.toml` and `rockbox restart`, or call:
4646+4747+```graphql
4848+mutation { connectDevice(id: "<device-id>") }
4949+```
5050+5151+…with a device discovered via mDNS — see
5252+[Audio output › Overview](/audio-output/overview).
+76
mintlify/audio-output/chromecast.mdx
···11+---
22+title: "Chromecast"
33+description: "Google Cast support over WAV-over-HTTP plus the Cast control channel."
44+icon: 'cast'
55+---
66+77+Rockbox streams audio to any Google Cast-compatible device — Google Home,
88+Chromecast Audio, Chromecast with Google TV, Nest Hub, or third-party
99+receivers — using two channels at once:
1010+1111+| Channel | Port | Purpose |
1212+|----------------|--------------|----------------------------------------------------------------|
1313+| Cast protocol | TCP 8009 | TLS + Protobuf — playback control, queue, metadata |
1414+| WAV over HTTP | TCP 7881 | Live `audio/wav` stream with finite `Content-Length` |
1515+1616+The finite content length is what lets the Chromecast show a progress bar
1717+and auto-advance at track boundaries.
1818+1919+## Configuration
2020+2121+```toml
2222+music_dir = "/path/to/Music"
2323+audio_output = "chromecast"
2424+chromecast_host = "192.168.1.60" # LAN IP of the target device
2525+chromecast_port = 8009 # optional, default 8009 (Cast protocol)
2626+chromecast_http_port = 7881 # optional, default 7881 (WAV stream)
2727+```
2828+2929+## Auto-discovery
3030+3131+Devices on the LAN are discovered via mDNS (`_googlecast._tcp.local.`) and
3232+appear in the web UI and desktop app device picker — clicking starts a Cast
3333+session on demand without `audio_output = "chromecast"` in the config.
3434+3535+## Track metadata
3636+3737+Title, artist, album, duration, and album art are pushed to the device on
3838+every track change so the "Now playing" card stays accurate.
3939+4040+<Note>
4141+**Network requirement**: the Chromecast must be able to reach port 7881 on
4242+the host running rockboxd. If rockboxd is in a VM or container, forward
4343+that port to the host (or run with `--network host`).
4444+</Note>
4545+4646+## Picking a device from the API
4747+4848+```graphql
4949+query DiscoveredCastDevices {
5050+ devices {
5151+ id
5252+ name
5353+ ip
5454+ port
5555+ isCastDevice
5656+ }
5757+}
5858+5959+mutation Cast {
6060+ connectDevice(id: "chromecast-living-room")
6161+}
6262+```
6363+6464+…or with the [TypeScript SDK](/sdks/typescript):
6565+6666+```ts
6767+const devices = await client.devices.list();
6868+const cast = devices.find((d) => d.isCastDevice);
6969+if (cast) await client.devices.connect(cast.id);
7070+```
7171+7272+## Architecture
7373+7474+Implementation lives in `crates/chromecast/`. See
7575+[`crates/chromecast/README.md`](https://github.com/tsirysndr/rockbox-zig/blob/master/crates/chromecast/README.md)
7676+for the protocol-level details.
+60
mintlify/audio-output/overview.mdx
···11+---
22+title: "Audio output overview"
33+description: "Pick a PCM sink. Switch any time over the API."
44+icon: 'speaker'
55+---
66+77+Rockbox writes decoded PCM into a single active **sink** at a time. The sink
88+is selected by the `audio_output` key in `settings.toml`, but it can also be
99+changed at runtime — e.g. by clicking a discovered Chromecast in the device
1010+picker.
1111+1212+| Sink | `audio_output` | Use it for |
1313+|-------------------|----------------|-------------------------------------------------------|
1414+| Built-in SDL | `builtin` | Local speakers / headphones |
1515+| FIFO / pipe | `fifo` | Snapcast (`pipe://`), `ffplay`, any pipe consumer |
1616+| Snapcast TCP | `snapcast_tcp` | Snapserver `tcp://` source with auto-discovery |
1717+| AirPlay (RAOP) | `airplay` | Apple TV, HomePod, Airport Express, shairport-sync |
1818+| Squeezelite | `squeezelite` | Logitech-style multi-room with squeezelite clients |
1919+| Chromecast | `chromecast` | Google Home, Chromecast Audio, Nest Hub |
2020+| UPnP / DLNA | `upnp` | Kodi, VLC, BubbleUPnP, any UPnP MediaRenderer |
2121+2222+## Stream format
2323+2424+Every sink receives the same byte stream: **S16LE stereo PCM at 44 100 Hz**.
2525+The Rockbox DSP pipeline dithers and downmixes higher-bit-depth decoder
2626+output before it reaches the sink.
2727+2828+## Fan-out
2929+3030+A few sinks support sending the same stream to multiple receivers
3131+simultaneously:
3232+3333+- **AirPlay** — list multiple `[[airplay_receivers]]` and they all share the
3434+ same `initial_rtptime`, keeping playback within ~8 ms across the LAN.
3535+- **Squeezelite** — any number of squeezelite clients can attach to one
3636+ rockboxd; a `sync` packet aligns their clocks once per second.
3737+- **Snapcast** — fan-out is handled by snapserver, not Rockbox.
3838+3939+For Chromecast, AirPlay and UPnP the LAN is also scanned with mDNS / SSDP at
4040+startup; discovered devices show up in the web UI and desktop picker without
4141+any config-file edits.
4242+4343+## Switching sinks at runtime
4444+4545+Through the GraphQL API:
4646+4747+```graphql
4848+mutation Connect {
4949+ connectDevice(id: "chromecast-living-room")
5050+}
5151+```
5252+5353+…or through the desktop / web device picker. The active sink is stopped, the
5454+new sink's `init` and `start` hooks run, and audio resumes within a frame.
5555+5656+## Architecture
5757+5858+Each sink implements the `pcm_sink_ops` vtable in
5959+`firmware/export/pcm_sink.h`. The full list of sinks with file paths and
6060+implementation notes lives in [Architecture › PCM sinks](/architecture/pcm-sinks).
+112
mintlify/audio-output/snapcast.mdx
···11+---
22+title: "Snapcast"
33+description: "Synchronised multi-room playback through Snapserver — TCP or FIFO."
44+icon: 'network-wired'
55+---
66+77+Rockbox can feed [Snapcast](https://github.com/badaix/snapcast) two ways.
88+Both write raw **S16LE stereo PCM at 44 100 Hz** to snapserver; pick the
99+transport that fits your setup.
1010+1111+| | TCP sink | FIFO sink |
1212+|--------------------------------|------------------------------------|----------------------------|
1313+| Filesystem entry required | No | Yes (`/tmp/snapfifo`) |
1414+| Snapserver source type | `tcp://` | `pipe://` |
1515+| Startup order | Snapserver first | Rockbox first |
1616+| Auto-reconnect | Yes (next play call) | n/a — FIFO stays open |
1717+| Auto-discovery in UI | Yes (`_snapcast._tcp.local.`) | No — static virtual device |
1818+| stdout pipe support | No | Yes (`fifo_path = "-"`) |
1919+2020+**Use TCP** for auto-discovery, multiple snapservers, or no filesystem
2121+dependency. **Use FIFO** for stdout piping or the traditional pipe model.
2222+2323+## TCP (recommended)
2424+2525+```toml
2626+music_dir = "/path/to/Music"
2727+audio_output = "snapcast_tcp"
2828+snapcast_tcp_host = "192.168.1.x" # IP of the snapserver host
2929+snapcast_tcp_port = 4953 # default snapserver TCP source port
3030+```
3131+3232+Snapserver:
3333+3434+```ini
3535+# /etc/snapserver.conf (or /usr/local/etc/snapserver.conf on macOS)
3636+[stream]
3737+source = tcp://0.0.0.0:4953?name=default&sampleformat=44100:16:2
3838+```
3939+4040+<Tip>
4141+**Auto-discovery**: rockboxd scans `_snapcast._tcp.local.` at startup;
4242+discovered servers appear in the web UI device picker. Click to connect —
4343+no config file editing required.
4444+</Tip>
4545+4646+<Note>
4747+**Startup order**: start `snapserver` first so it is already listening when
4848+rockboxd begins playback. If the connection drops (e.g. snapserver
4949+restarts), it is re-established automatically on the next play call.
5050+</Note>
5151+5252+## FIFO / pipe
5353+5454+```toml
5555+music_dir = "/path/to/Music"
5656+audio_output = "fifo"
5757+fifo_path = "/tmp/snapfifo" # named FIFO for snapserver; "-" = stdout
5858+```
5959+6060+Snapserver:
6161+6262+```ini
6363+[stream]
6464+source = pipe:///tmp/snapfifo?name=default&sampleformat=44100:16:2
6565+```
6666+6767+<Warning>
6868+**Startup order matters**: start `rockboxd` before `snapserver`. Rockbox
6969+holds a permanent write reference on the FIFO so snapserver never sees a
7070+premature EOF between tracks. If snapserver opens the FIFO first it may get
7171+EOF and stop reading.
7272+</Warning>
7373+7474+### stdout mode
7575+7676+`fifo_path = "-"` writes raw PCM to stdout — useful for piping into any
7777+consumer:
7878+7979+```sh
8080+rockboxd | ffplay -f s16le -ar 44100 -ac 2 -
8181+```
8282+8383+```sh
8484+rockboxd | sox -t raw -r 44100 -e signed -b 16 -c 2 - -d
8585+```
8686+8787+## macOS quirk
8888+8989+Snapserver v0.35.0 on macOS ignores the `-s` sample-format CLI flag. Use the
9090+config file at `/usr/local/etc/snapserver.conf` instead:
9191+9292+```ini
9393+[stream]
9494+source = pipe:///tmp/snapfifo?name=default&sampleformat=44100:16:2
9595+```
9696+9797+## Verifying it works
9898+9999+```sh
100100+# In one terminal
101101+snapserver --logging.filter "*:debug"
102102+103103+# In another
104104+rockboxd
105105+```
106106+107107+You should see `Stream: 'default' connected` in the snapserver logs within
108108+a second of starting playback. From a snapclient host:
109109+110110+```sh
111111+snapclient -h <snapserver-ip>
112112+```
+82
mintlify/audio-output/squeezelite.mdx
···11+---
22+title: "Squeezelite"
33+description: "Slim Protocol multi-room — Rockbox impersonates Logitech Media Server."
44+icon: 'boxes-stacked'
55+---
66+77+Rockbox runs a minimal **Logitech Media Server** that any number of
88+[squeezelite](https://github.com/ralph-irving/squeezelite) clients can attach
99+to. A Slim Protocol TCP server accepts connections; an HTTP PCM broadcast
1010+server serves the actual audio stream.
1111+1212+```toml
1313+music_dir = "/path/to/Music"
1414+audio_output = "squeezelite"
1515+squeezelite_port = 3483 # Slim Protocol TCP port (default 3483)
1616+squeezelite_http_port = 9999 # HTTP PCM broadcast port (default 9999)
1717+```
1818+1919+## Connecting clients
2020+2121+```sh
2222+squeezelite -s localhost -n "Living Room"
2323+squeezelite -s localhost -n "Kitchen"
2424+squeezelite -s localhost -n "Bedroom"
2525+```
2626+2727+Each client gets an independent `BroadcastReceiver` cursor into the shared
2828+buffer, so adding or removing clients never blocks the writer or interrupts
2929+playback in other rooms.
3030+3131+### Selecting a specific output device
3232+3333+```sh
3434+squeezelite -s localhost -l # list available devices
3535+squeezelite -s localhost -o "" # system default
3636+squeezelite -s localhost -o "Built-in Output" # specific device
3737+```
3838+3939+## How it stays in sync
4040+4141+Rockbox sends a `sync` packet to every client once per second so they all
4242+align to the same playback clock. The DMA loop in
4343+`firmware/target/hosted/pcm-squeezelite.c` paces output to real time using
4444+`CLOCK_MONOTONIC`, which keeps the broadcast buffer from drifting against
4545+the wall clock.
4646+4747+## Buffer behaviour
4848+4949+| Property | Value |
5050+|-----------------------|----------------------------------------------|
5151+| Capacity | 4 MB |
5252+| Eviction | Oldest-first when full |
5353+| Per-client cursor | Yes — each receiver tracks its own position |
5454+| Lagging client policy | Skip forward (does not block the writer) |
5555+5656+A slow client never stalls the others.
5757+5858+## Slim Protocol details
5959+6060+Internal — useful if you're debugging or building your own client.
6161+6262+- **Framing**:
6363+ - client → server: `opcode[4] + u32_t length BE + payload`
6464+ - server → client: `u16_t length BE + opcode[4] + payload`
6565+ (length excludes the 2-byte length field itself)
6666+- **STRM `'s'`** points clients at the HTTP port (defaults to 9999).
6767+- **STMt heartbeat** from the client must be answered with `audg`, otherwise
6868+ squeezelite's 36-second watchdog will tear down the session.
6969+- **ASCII-encoded PCM fields** in the STRM packet — squeezelite subtracts
7070+ `'0'` from `pcm_sample_size`, `pcm_sample_rate`, `pcm_channels` and
7171+ `pcm_endianness`. Correct values: `'1'` (16-bit), `'3'` (44 100 Hz),
7272+ `'2'` (stereo), `'1'` (little-endian).
7373+7474+## Debugging
7575+7676+```sh
7777+RUST_LOG=rockbox_slim=debug rockboxd
7878+```
7979+8080+```sh
8181+squeezelite -s localhost -d slimproto=debug -d output=info
8282+```
+95
mintlify/audio-output/upnp.mdx
···11+---
22+title: "UPnP / DLNA"
33+description: "Three independent UPnP/DLNA modes — sink, media server and renderer."
44+icon: 'tower-broadcast'
55+---
66+77+Rockbox has three UPnP/DLNA modes that can be enabled independently. They
88+combine freely: e.g. expose your library to BubbleUPnP **and** stream live
99+to Kodi at the same time.
1010+1111+## Mode 1 — PCM sink (push to a renderer)
1212+1313+Rockbox encodes live PCM as a continuous WAV-over-HTTP stream and tells a
1414+UPnP MediaRenderer to play it via AVTransport SOAP.
1515+1616+```toml
1717+music_dir = "/path/to/Music"
1818+audio_output = "upnp"
1919+upnp_renderer_url = "http://192.168.1.x:7777/AVTransport/control"
2020+upnp_http_port = 7879 # WAV broadcast HTTP port (default 7879)
2121+```
2222+2323+Track metadata (title, artist, album, album art, duration) is sent as
2424+DIDL-Lite XML in `SetAVTransportURI` and refreshed on every track change.
2525+2626+<Tip>
2727+**Finding `upnp_renderer_url`**: start `rockboxd` with `RUST_LOG=info` —
2828+it scans the LAN at startup and logs
2929+`upnp scan: found renderer "<name>" av=http://...` for every renderer
3030+found.
3131+</Tip>
3232+3333+## Mode 2 — Media Server (let others browse your library)
3434+3535+Exposes your music library as a UPnP ContentDirectory. BubbleUPnP, Kodi,
3636+VLC, foobar2000 and the like can browse artists / albums / tracks and pull
3737+audio directly from Rockbox.
3838+3939+```toml
4040+upnp_server_enabled = true
4141+upnp_server_port = 7878 # default
4242+upnp_friendly_name = "Rockbox" # name shown in apps
4343+```
4444+4545+## Mode 3 — MediaRenderer (let others push to you)
4646+4747+Rockbox registers as a `MediaRenderer:1`. Any control point can push a URI
4848+and control playback remotely. Incoming DIDL-Lite metadata is parsed and
4949+displayed in the UI.
5050+5151+```toml
5252+upnp_renderer_enabled = true
5353+upnp_renderer_port = 7880 # default
5454+upnp_friendly_name = "Rockbox"
5555+```
5656+5757+## All UPnP keys
5858+5959+| Key | Default | Description |
6060+|----------------------------|--------------|------------------------------------------------|
6161+| `audio_output = "upnp"` | — | Enable the PCM → WAV streaming sink |
6262+| `upnp_renderer_url` | — | AVTransport controlURL of the target renderer |
6363+| `upnp_http_port` | `7879` | WAV broadcast HTTP port |
6464+| `upnp_server_enabled` | `false` | Start the ContentDirectory media server |
6565+| `upnp_server_port` | `7878` | Media server HTTP port |
6666+| `upnp_renderer_enabled` | `false` | Start the MediaRenderer endpoint |
6767+| `upnp_renderer_port` | `7880` | MediaRenderer HTTP port |
6868+| `upnp_friendly_name` | `"Rockbox"` | Display name shown to control points |
6969+7070+## Typical setups
7171+7272+<AccordionGroup>
7373+ <Accordion title="Stream to Kodi (sink mode)">
7474+ Find Kodi's AVTransport URL with `RUST_LOG=info rockboxd`, then:
7575+ ```toml
7676+ audio_output = "upnp"
7777+ upnp_renderer_url = "http://192.168.1.42:7777/AVTransport/control"
7878+ ```
7979+ </Accordion>
8080+ <Accordion title="Library on the LAN (server mode)">
8181+ ```toml
8282+ audio_output = "builtin"
8383+ upnp_server_enabled = true
8484+ upnp_friendly_name = "Living-room music"
8585+ ```
8686+ </Accordion>
8787+ <Accordion title="Phone control (renderer mode)">
8888+ ```toml
8989+ audio_output = "builtin"
9090+ upnp_renderer_enabled = true
9191+ upnp_friendly_name = "Rockbox"
9292+ ```
9393+ Now BubbleUPnP can pick "Rockbox" as the playback target.
9494+ </Accordion>
9595+</AccordionGroup>
+54
mintlify/audio-settings/crossfade.mdx
···11+---
22+title: "Crossfade"
33+description: "Overlap the end of one track with the beginning of the next."
44+icon: 'shuffle'
55+---
66+77+Crossfade rolls the outgoing track into the incoming one over a configurable
88+window. Off by default; enable it via `crossfade` in `settings.toml`.
99+1010+```toml
1111+crossfade = 5 # see Mode table below
1212+fade_on_stop = false
1313+fade_in_delay = 2
1414+fade_in_duration = 7
1515+fade_out_delay = 4
1616+fade_out_duration = 0
1717+fade_out_mixmode = 2
1818+```
1919+2020+## Mode
2121+2222+| Value | Mode | When the crossfade fires |
2323+|-------|-------------------------------------|------------------------------------|
2424+| 0 | Off | Never |
2525+| 1 | Auto track change | At end-of-track only |
2626+| 2 | Manual skip | When you press next/previous |
2727+| 3 | Shuffle | While shuffle is on |
2828+| 4 | Shuffle + manual skip | Both |
2929+| 5 | Always | Every transition |
3030+3131+## Timings
3232+3333+| Setting | Storage | Range | Default | Description |
3434+|----------------------|------------------------------------|-----------|----------|----------------------------------------------|
3535+| Fade-in delay | `crossfade_fade_in_delay` | 0..7 s | 0 s | Silence before the fade-in begins |
3636+| Fade-out delay | `crossfade_fade_out_delay` | 0..7 s | 0 s | Silence before the fade-out begins |
3737+| Fade-in duration | `crossfade_fade_in_duration` | 0..15 s | 2 s | Length of the fade-in ramp |
3838+| Fade-out duration | `crossfade_fade_out_duration` | 0..15 s | 2 s | Length of the fade-out ramp |
3939+| Fade-out mode | `crossfade_fade_out_mixmode` | crossfade / mix | crossfade | Whether the outgoing track fades or mixes flat |
4040+4141+## Fade-on-stop
4242+4343+```toml
4444+fade_on_stop = true
4545+```
4646+4747+When set, pressing **stop** ramps audio out instead of cutting it.
4848+4949+## Notes
5050+5151+- Crossfade is part of the rbcodec DSP pipeline and is applied before the
5252+ sink, so it works equally well with AirPlay, Snapcast and the rest.
5353+- Gapless playback overrides crossfade for tracks that share an album
5454+ identity — gapless seams are more important than smooth fades.
+97
mintlify/audio-settings/dsp.mdx
···11+---
22+title: "DSP effects"
33+description: "Crossfeed, Haas surround, PBE, AFR, compressor and dithering."
44+icon: 'waveform-lines'
55+---
66+77+The rbcodec DSP pipeline runs between the codec output and the active sink.
88+All of these are bypassable; turn anything off by zeroing its enable flag.
99+1010+## Crossfeed
1111+1212+Mixes a delayed and filtered portion of one channel into the other to
1313+simulate the spatial cues you'd get from loudspeakers. Particularly useful
1414+on headphones with hard-panned mixes (older rock/jazz).
1515+1616+| Setting | Storage | Range | Default |
1717+|----------------|-------------------------------|--------------------|----------|
1818+| Type | `crossfeed` | off / meier / custom | off |
1919+| Direct gain | `crossfeed_direct_gain` | −60..0 dB (step 5) | −15 dB |
2020+| Cross gain | `crossfeed_cross_gain` | −120..−30 dB | −60 dB |
2121+| HF attenuation | `crossfeed_hf_attenuation` | −240..−60 dB | −160 dB |
2222+| HF cutoff | `crossfeed_hf_cutoff` | 500..2000 Hz | 700 Hz |
2323+2424+<Warning>
2525+Crossfeed can cause output distortion if its settings result in a combined
2626+level that is too high.
2727+</Warning>
2828+2929+## Haas surround
3030+3131+Adds an adjustable delay between channels to widen the stereo image. Four
3232+auxiliary controls move the perceived stage back toward the centre.
3333+3434+| Setting | Storage | Range | Default |
3535+|---------------|----------------------|------------------------------------|----------|
3636+| Enable | `surround_enabled` | 0 / 5 / 8 / 10 / 15 / 30 ms | 0 (off) |
3737+| Balance | `surround_balance` | 0..99 % | 35 % |
3838+| f(x1) HF cut | `surround_fx1` | 600..8000 Hz (step 200) | 3400 Hz |
3939+| f(x2) LF cut | `surround_fx2` | 40..400 Hz (step 40) | 320 Hz |
4040+| Side only | `surround_method2` | bool | false |
4141+| Dry/wet mix | `surround_mix` | 0..100 % | 50 % |
4242+4343+## Perceptual Bass Enhancement (PBE)
4444+4545+Group-delay correction plus a biophonic EQ to boost low-end perception.
4646+4747+| Setting | Storage | Range | Default |
4848+|------------|---------------|---------------------|----------|
4949+| Strength | `pbe` | 0..100 % (step 25) | 0 % (off) |
5050+| Precut | `pbe_precut` | −4.5..0 dB (step 0.1) | −2.5 dB |
5151+5252+## Auditory Fatigue Reduction (AFR)
5353+5454+Reduces energy in frequency bands the human ear is most sensitive to —
5555+helpful for long listening sessions.
5656+5757+| Setting | Values | Default |
5858+|------------|--------------------------------|---------|
5959+| AFR enable | off / weak / moderate / strong | off |
6060+6161+## Compressor
6262+6363+Reduces dynamic range so quiet passages stay audible without loud passages
6464+clipping.
6565+6666+| Setting | Storage | Values / range | Default |
6767+|---------------|-----------------|--------------------------------------------------------|---------|
6868+| Threshold | `.threshold` | off / −3 / −6 / −9 / −12 / −15 / −18 / −21 / −24 dB | off |
6969+| Makeup gain | `.makeup_gain` | off / auto | auto |
7070+| Ratio | `.ratio` | 2:1 / 4:1 / 6:1 / 10:1 / limit | 2:1 |
7171+| Knee | `.knee` | hard / soft | soft |
7272+| Attack time | `.attack_time` | 0..30 ms (step 5) | 5 ms |
7373+| Release time | `.release_time` | 100..1000 ms (step 100) | 500 ms |
7474+7575+```toml
7676+[compressor_settings]
7777+threshold = -24
7878+makeup_gain = 0
7979+ratio = 4
8080+knee = 1
8181+release_time = 300
8282+attack_time = 5
8383+```
8484+8585+## Dithering
8686+8787+Most decoders work at higher than 16-bit precision; dithering adds a small
8888+shaped noise signal before truncation so the residual is uniform rather
8989+than signal-correlated. Most useful with classical music and other
9090+high-dynamic-range material.
9191+9292+```toml
9393+dithering_enabled = true
9494+```
9595+9696+- Algorithm: high-pass triangular distribution (HPTPDF)
9797+- Noise shaper: third order, biased above ~10 kHz
+112
mintlify/audio-settings/equalizer.mdx
···11+---
22+title: "Equalizer"
33+description: "10-band parametric EQ — independent gain, frequency and Q per band."
44+icon: 'chart-column'
55+---
66+77+Rockbox uses a **parametric** EQ rather than the more common graphic EQ.
88+Each band has independent control of gain, centre frequency and bandwidth
99+(Q), which buys you the same shaping power with fewer bands than a graphic
1010+EQ would need.
1111+1212+<Tip>
1313+Using more bands than necessary wastes CPU and adds rounding noise. Disable
1414+or zero out bands you aren't using.
1515+</Tip>
1616+1717+## Bands
1818+1919+| Band | Filter type | Default centre / cutoff | Q recommendation |
2020+|--------|--------------------|-------------------------|----------------------------------------------------|
2121+| 0 | Low-shelf | 32 Hz | 0.7 (higher Q adds an unwanted boost near cutoff) |
2222+| 1–8 | Peaking (bell) | 64 / 125 / 250 / 500 / 1k / 2k / 4k / 8k Hz | Higher Q = narrower band |
2323+| 9 | High-shelf | 16 000 Hz | 0.7 |
2424+2525+Per band:
2626+2727+- **Cutoff / centre frequency** — Hz
2828+- **Gain** — dB; positive boosts, negative cuts
2929+- **Q** — bandwidth (peak filters); 0.7 for shelves
3030+3131+## Top-level settings
3232+3333+| Setting | Storage | Type / range | Description |
3434+|---------------|----------------------|---------------------|-------------------------------------------------------------------|
3535+| Enable EQ | `eq_enabled` | bool | Master on/off |
3636+| Precut | `eq_precut` | 0..24 dB | Negative gain applied before EQ to prevent clipping when boosting |
3737+3838+Applied via `dsp_set_eq_precut()` and `dsp_set_eq_coefs()` in
3939+`lib/rbcodec/dsp/eq.h`.
4040+4141+## TOML
4242+4343+```toml
4444+eq_enabled = true
4545+eq_precut = 3 # 3 dB headroom before EQ
4646+4747+[[eq_band_settings]] # band 0 (low shelf)
4848+cutoff = 32
4949+q = 7 # Q × 10 — Rockbox stores fixed-point
5050+gain = 30 # dB × 10
5151+5252+[[eq_band_settings]] # band 1
5353+cutoff = 64
5454+q = 7
5555+gain = 0
5656+5757+# ... bands 2-9
5858+```
5959+6060+<Note>
6161+`q` and `gain` are stored as fixed-point (×10) in `global_settings`. The
6262+GraphQL API accepts plain decimals — see
6363+[Settings TOML reference](/reference/settings-toml).
6464+</Note>
6565+6666+## Configuring via the API
6767+6868+<CodeGroup>
6969+```graphql GraphQL
7070+mutation EnableEqAndShapeBass {
7171+ saveSettings(input: {
7272+ eqEnabled: true
7373+ eqPrecut: -3
7474+ eqBandSettings: [
7575+ { cutoff: 60, q: 7, gain: 3 }
7676+ { cutoff: 200, q: 7, gain: 0 }
7777+ { cutoff: 800, q: 7, gain: 0 }
7878+ { cutoff: 4000, q: 7, gain: -2 }
7979+ { cutoff: 12000, q: 7, gain: 1 }
8080+ ]
8181+ }) { eqEnabled }
8282+}
8383+```
8484+8585+```ts TypeScript
8686+await client.settings.save({
8787+ eqEnabled: true,
8888+ eqPrecut: -3,
8989+ eqBandSettings: [
9090+ { cutoff: 60, q: 7, gain: 3 },
9191+ { cutoff: 200, q: 7, gain: 0 },
9292+ { cutoff: 800, q: 7, gain: 0 },
9393+ { cutoff: 4000, q: 7, gain: -2 },
9494+ { cutoff: 12000, q: 7, gain: 1 },
9595+ ],
9696+});
9797+```
9898+9999+```python Python
100100+await client.settings.save(
101101+ eq_enabled=True,
102102+ eq_precut=-3,
103103+ eq_band_settings=[
104104+ {"cutoff": 60, "q": 7, "gain": 3},
105105+ {"cutoff": 200, "q": 7, "gain": 0},
106106+ {"cutoff": 800, "q": 7, "gain": 0},
107107+ {"cutoff": 4000, "q": 7, "gain": -2},
108108+ {"cutoff": 12000, "q": 7, "gain": 1},
109109+ ],
110110+)
111111+```
112112+</CodeGroup>
+81
mintlify/audio-settings/overview.mdx
···11+---
22+title: "Audio settings"
33+description: "Volume, EQ, DSP, ReplayGain, crossfade and the rest of the rbcodec pipeline."
44+icon: 'sliders'
55+---
66+77+The Rockbox DSP pipeline runs between the codec output and the active PCM
88+sink. Settings live in `global_settings` (in the C firmware) and are
99+mirrored in `settings.toml`. Most can also be changed at runtime via
1010+GraphQL or gRPC and they persist on the next save cycle.
1111+1212+<CardGroup cols={2}>
1313+ <Card title="Equalizer" icon="chart-column" href="/audio-settings/equalizer">
1414+ 10-band parametric EQ — gain, centre frequency, Q per band.
1515+ </Card>
1616+ <Card title="DSP" icon="waveform-lines" href="/audio-settings/dsp">
1717+ Crossfeed, surround, PBE, AFR, compressor, dithering.
1818+ </Card>
1919+ <Card title="ReplayGain" icon="volume" href="/audio-settings/replaygain">
2020+ Track / album normalisation with optional clipping protection.
2121+ </Card>
2222+ <Card title="Crossfade" icon="shuffle" href="/audio-settings/crossfade">
2323+ Overlap track ends with the next track's start.
2424+ </Card>
2525+</CardGroup>
2626+2727+## Where settings live
2828+2929+| Layer | Lives in | Lifetime |
3030+|----------------------------|-----------------------------------------|-------------------------|
3131+| Compiled-in defaults | `apps/settings_list.c` | Build-time |
3232+| `settings.toml` | `~/.config/rockbox.org/settings.toml` | Read once at startup |
3333+| Runtime (API) | In-memory `global_settings` | Persisted on save |
3434+3535+Hardware settings flow through `firmware/sound.c → sound_set_*()`. DSP
3636+settings flow through `lib/rbcodec/dsp/*` and are applied in the PCM pipeline
3737+before samples reach the sink.
3838+3939+## Volume
4040+4141+`global_status.volume` — decibels relative to the device's clipping point.
4242+**0 dB** is the maximum undistorted level. Negative values reduce output;
4343+positive values may distort. On the SDL target, volume is implemented as a
4444+software mixer.
4545+4646+```toml
4747+volume_limit = 0 # ceiling in dB (default = device max)
4848+```
4949+5050+## Channels & stereo
5151+5252+| Setting | Storage | Range / values |
5353+|------------------------------------|-------------------------------|---------------------------------------------|
5454+| Balance | `balance` | −100..+100 |
5555+| Channel config | `channel_config` | Stereo / Mono / Custom / Mono L / R / Karaoke / Swap |
5656+| Stereo width (when Custom) | `stereo_width` | 0..255 % |
5757+5858+## Pitch & time-stretch
5959+6060+Persisted in `global_status` so they survive across restarts. Time-stretch
6161+uses a TDHS algorithm — best for speech, may sound rough on dense music.
6262+6363+| Setting | Storage | Range |
6464+|----------------------|--------------------------------------|----------------|
6565+| Pitch | `global_status.resume_pitch` | ~50..200 % |
6666+| Speed | `global_status.resume_speed` | ~35..250 % |
6767+| Time-stretch enable | `global_settings.timestretch_enabled` | bool |
6868+6969+## Output sample rate
7070+7171+```toml
7272+play_frequency = 0 # 0=auto, 44100, 48000, 88200, 96000
7373+```
7474+7575+## UI feedback
7676+7777+```toml
7878+beep = 0 # off / weak / moderate / strong
7979+keyclick = 0
8080+keyclick_repeats = false
8181+```
+84
mintlify/audio-settings/replaygain.mdx
···11+---
22+title: "ReplayGain"
33+description: "Loudness normalisation using ReplayGain tags embedded in your files."
44+icon: 'volume'
55+---
66+77+ReplayGain reads the `REPLAYGAIN_*` tags written by tools like
88+`loudgain` / `mp3gain` / `metaflac` and applies the recommended gain at
99+playback time. Albums are kept at consistent loudness without re-encoding
1010+the files.
1111+1212+```toml
1313+[replaygain_settings]
1414+type = 0 # 0=Track 1=Album 2=Track shuffle 3=Off
1515+noclip = true
1616+preamp = 0
1717+```
1818+1919+## Settings
2020+2121+| Setting | Storage | Values / range | Default | Description |
2222+|-----------|----------|------------------------------------------|----------|-----------------------------------------------------------------------------|
2323+| Type | `.type` | track / album / track shuffle / off | shuffle | Which RG tag to apply for normalisation |
2424+| No-Clip | `.noclip`| bool | false | If the RG adjustment would cause clipping, scale down to avoid it |
2525+| Preamp | `.preamp`| −120..+120 dB (step 5) | 0 dB | Extra gain on top of the RG value (use with No-Clip to avoid surprises) |
2626+2727+Applied via `dsp_replaygain_set_settings()` in `lib/rbcodec/dsp/dsp_misc.h`.
2828+2929+## Modes explained
3030+3131+- **Track** — every track plays at its own RG-normalised level.
3232+- **Album** — all tracks within an album share the same gain; preserves
3333+ intended quiet/loud relationships within the album.
3434+- **Track shuffle** — Track gain when shuffling, Album gain otherwise. The
3535+ default; behaves naturally regardless of how you're listening.
3636+- **Off** — RG tags ignored.
3737+3838+## Tagging your files
3939+4040+Rockbox does not write ReplayGain tags. Use one of:
4141+4242+- **`loudgain`** — modern multi-format CLI (FLAC, MP3, Opus, OGG, M4A).
4343+- **`mp3gain`** — MP3 only, but lossless.
4444+- **`metaflac --add-replay-gain`** — FLAC, ships with `flac`.
4545+4646+Example:
4747+4848+```sh
4949+loudgain -a -k -s e *.flac # tag album-mode, prevent clipping
5050+```
5151+5252+After re-tagging, trigger a library rescan:
5353+5454+```graphql
5555+mutation { scanLibrary }
5656+```
5757+5858+## Configuring at runtime
5959+6060+<CodeGroup>
6161+```graphql GraphQL
6262+mutation {
6363+ saveSettings(input: {
6464+ replaygainSettings: {
6565+ type: 1
6666+ noclip: true
6767+ preamp: 0
6868+ }
6969+ }) { replaygainSettings { type } }
7070+}
7171+```
7272+7373+```ts TypeScript
7474+import { ReplaygainType } from '@rockbox-zig/sdk';
7575+7676+await client.settings.save({
7777+ replaygainSettings: {
7878+ type: ReplaygainType.Album,
7979+ noclip: true,
8080+ preamp: 0,
8181+ },
8282+});
8383+```
8484+</CodeGroup>
+65
mintlify/clients/desktop.mdx
···11+---
22+title: "Desktop app"
33+description: "Native desktop clients on macOS (GPUI), Linux (GTK4) and Windows."
44+icon: 'display'
55+---
66+77+Two native desktop clients are maintained alongside `rockboxd`:
88+99+- **macOS (GPUI)** — built on Zed's native UI toolkit, ships as a `.dmg`
1010+- **Linux (GTK4)** — distributed as a Flatpak
1111+1212+Both connect to a running `rockboxd` instance over GraphQL.
1313+1414+## macOS (GPUI)
1515+1616+Download the latest `.dmg` from the
1717+[Releases page](https://github.com/tsirysndr/rockbox-zig/releases/latest)
1818+or build from source:
1919+2020+```sh
2121+cd gpui
2222+cargo run --release
2323+```
2424+2525+Features:
2626+2727+- Full library browsing and search
2828+- Drag-and-drop queue management
2929+- Native macOS media keys & Now Playing card via MPRIS-equivalent integration
3030+- Right-click context menus for tracks, albums and folders
3131+- Dark / light mode follows the system
3232+3333+## Linux (GTK4)
3434+3535+The GTK4 client is published as a Flatpak. Build from source:
3636+3737+```sh
3838+sudo apt-get install flatpak
3939+flatpak remote-add --if-not-exists --user flathub \
4040+ https://dl.flathub.org/repo/flathub.flatpakrepo
4141+flatpak install --user flathub org.flatpak.Builder
4242+flatpak install --user flathub org.gnome.Sdk/x86_64/47
4343+flatpak install --user flathub org.gnome.Platform/x86_64/47
4444+flatpak install --user org.freedesktop.Sdk.Extension.rust-stable
4545+flatpak install --user org.freedesktop.Sdk.Extension.llvm18
4646+4747+cd gtk
4848+flatpak run org.flatpak.Builder \
4949+ --user --disable-rofiles-fuse --repo=repo \
5050+ flatpak_app build-aux/io.github.tsirysndr.Rockbox.json --force-clean
5151+5252+flatpak run org.flatpak.Builder \
5353+ --run flatpak_app build-aux/io.github.tsirysndr.Rockbox.json rockbox-gtk
5454+```
5555+5656+## Connecting to a remote rockboxd
5757+5858+Both desktop clients accept a non-default host and port — useful when
5959+`rockboxd` runs on a NAS or Raspberry Pi:
6060+6161+- macOS — Settings → Connection → Host / Port
6262+- GTK — `Preferences` → Server
6363+6464+If you put rockboxd behind a reverse proxy with TLS, use the `httpUrl` /
6565+`wsUrl` overrides under Connection settings.
+65
mintlify/clients/mpd.mdx
···11+---
22+title: "MPD clients"
33+description: "Rockbox speaks the Music Player Daemon protocol on port 6600."
44+icon: 'terminal'
55+---
66+77+`rockboxd` runs a built-in MPD server on port **6600**, so any of the dozens
88+of MPD clients work out of the box — `mpc`, `ncmpcpp`, MALP, M.A.L.P.,
99+Cantata, Mopidy, Volumio's UI, etc.
1010+1111+```sh
1212+mpc -h localhost -p 6600 status
1313+mpc -h localhost -p 6600 update
1414+mpc -h localhost -p 6600 search title "money"
1515+mpc -h localhost -p 6600 add /Music/Pink\ Floyd/Money.mp3
1616+mpc -h localhost -p 6600 play
1717+```
1818+1919+## Compatibility
2020+2121+The implementation lives in `crates/mpd/`. It targets the standard MPD
2222+protocol version. Coverage:
2323+2424+- ✅ Status, current/next song, elapsed time
2525+- ✅ Playback control: play/pause/next/previous/seek/stop
2626+- ✅ Queue (`add`, `addid`, `delete`, `clear`, `shuffle`, `move`)
2727+- ✅ Library (`list`, `find`, `search`, `lsinfo`)
2828+- ✅ Saved playlists (`load`, `save`, `rm`, `playlistadd`)
2929+- ✅ Volume, repeat, random, single, consume
3030+- ✅ `idle` notifications (player, playlist, mixer, etc.)
3131+- ⚠️ Sticker database — partial
3232+- ⚠️ Outputs — single output exposed (the active sink)
3333+3434+## Configuring the bind address
3535+3636+```toml
3737+mpd_host = "0.0.0.0"
3838+mpd_port = 6600
3939+```
4040+4141+## Recommended clients
4242+4343+| Client | Platform | Notes |
4444+|--------------|-----------------------|-------------------------------------------|
4545+| `mpc` | CLI | Scripting, smoke-testing |
4646+| `ncmpcpp` | TUI | The classic terminal client |
4747+| MALP | Android | Material Design, modern feel |
4848+| M.A.L.P. | Android (F-Droid) | F-Droid build of MALP |
4949+| Cantata | Linux / Windows / macOS | Full GUI client |
5050+| Mopidy | Linux | Acts as an MPD-compatible aggregator |
5151+5252+## Idle subscriptions
5353+5454+```sh
5555+mpc -h localhost -p 6600 idle player playlist mixer
5656+```
5757+5858+Returns one event per change. The `subscribe`/`channels` channel commands
5959+are also implemented for client-to-client messaging.
6060+6161+## Library updates
6262+6363+`update` triggers a full rescan of `music_dir` exactly like the
6464+[GraphQL `scanLibrary` mutation](/api-reference/graphql/library). New files
6565+appear in MPD listings as soon as the scan finishes.
+50
mintlify/clients/mpris.mdx
···11+---
22+title: "MPRIS"
33+description: "Linux media keys and desktop integration via D-Bus."
44+icon: 'keyboard'
55+---
66+77+On Linux, `rockboxd` registers itself on the session bus as
88+`org.mpris.MediaPlayer2.rockbox`. This makes media keys, Now Playing
99+applets, KDE/GNOME notifications and tools like `playerctl` Just Work.
1010+1111+## Capabilities
1212+1313+The MPRIS interface is implemented in `crates/mpris/` and exposes:
1414+1515+- **Player interface** — Play, Pause, PlayPause, Stop, Next, Previous, Seek,
1616+ SetPosition, Metadata, PlaybackStatus, LoopStatus, Shuffle, Volume
1717+- **Root interface** — Identity, DesktopEntry, SupportedUriSchemes,
1818+ SupportedMimeTypes
1919+- **TrackList interface** — partial (track-level queue introspection)
2020+2121+## Quick test
2222+2323+```sh
2424+playerctl --player rockbox play-pause
2525+playerctl --player rockbox metadata
2626+playerctl --player rockbox position 90
2727+```
2828+2929+## Wiring up media keys
3030+3131+GNOME and KDE pick up MPRIS players automatically — no setup needed. For
3232+sway / Hyprland / dwm:
3333+3434+```text
3535+# sway
3636+bindsym XF86AudioPlay exec playerctl --player rockbox play-pause
3737+bindsym XF86AudioNext exec playerctl --player rockbox next
3838+bindsym XF86AudioPrev exec playerctl --player rockbox previous
3939+```
4040+4141+## Now Playing applet
4242+4343+GNOME Shell, KDE's Media Player widget, polybar's `mpris-tail`, waybar's
4444+`mpris` module — all consume MPRIS metadata directly.
4545+4646+## macOS
4747+4848+macOS has its own Now Playing system. Rockbox publishes to it through the
4949+`MPNowPlayingInfoCenter` API; you don't need MPRIS on Mac. Media keys work
5050+out of the box.
+47
mintlify/clients/web.mdx
···11+---
22+title: "Web UI"
33+description: "Browser-based controller served by rockboxd on port 6062."
44+icon: 'globe'
55+---
66+77+The web UI is a React app served from the GraphQL HTTP listener. Open
88+[http://localhost:6062](http://localhost:6062) once `rockboxd` is running.
99+1010+## Features
1111+1212+- Browse artists, albums and tracks from the tag database
1313+- Browse the filesystem under `music_dir`
1414+- Search powered by Typesense (instant results as you type)
1515+- Manage the live playback queue and saved playlists
1616+- Reorder, shuffle, repeat
1717+- Like / unlike tracks and albums
1818+- Pick the active output device (Chromecast, AirPlay, Snapcast TCP, …)
1919+- Real-time "now playing" updates over a GraphQL subscription
2020+2121+## Building from source
2222+2323+The web UI ships pre-built with the `rockboxd` binary, but you can rebuild
2424+it locally:
2525+2626+```sh
2727+cd webui/rockbox
2828+deno install
2929+deno run build
3030+```
3131+3232+The result lands in `webui/rockbox/build/` and is embedded into rockboxd at
3333+link time.
3434+3535+## Customising the bind address
3636+3737+```toml
3838+graphql_host = "0.0.0.0" # default 0.0.0.0
3939+graphql_port = 6062 # default 6062
4040+```
4141+4242+Restart rockboxd, then reach the UI at `http://<host>:<port>`.
4343+4444+## Storybook
4545+4646+Component-level documentation is on Chromatic:
4747+[storybook ↗](https://master--670ceec25af685dcdc87c0df.chromatic.com/?path=/story/components-albums--default).
+131
mintlify/configuration.mdx
···11+---
22+title: "Configuration"
33+description: "Configure Rockbox via ~/.config/rockbox.org/settings.toml."
44+icon: 'gear'
55+---
66+77+Rockbox reads `~/.config/rockbox.org/settings.toml` once on startup. Edit the
88+file, then `rockbox restart`. There is no live-reload; the API is the way to
99+change things at runtime.
1010+1111+## Minimal config
1212+1313+```toml
1414+music_dir = "/path/to/your/Music"
1515+audio_output = "builtin"
1616+```
1717+1818+`music_dir` is the only required field. `audio_output` defaults to `"builtin"`
1919+if omitted.
2020+2121+## Top-level keys
2222+2323+| Key | Type | Default | Description |
2424+|----------------|--------|---------------|----------------------------------------------|
2525+| `music_dir` | string | — | Absolute path to your music library |
2626+| `audio_output` | string | `"builtin"` | One of: `builtin`, `fifo`, `airplay`, `squeezelite`, `chromecast`, `snapcast_tcp`, `upnp` |
2727+| `player_name` | string | `""` | Name advertised to MPD clients and UI |
2828+2929+## Output sinks
3030+3131+Each sink has its own configuration block. See the dedicated pages:
3232+3333+<CardGroup cols={2}>
3434+ <Card title="Built-in (SDL)" icon="speaker" href="/audio-output/built-in">Default. No setup.</Card>
3535+ <Card title="Snapcast" icon="network-wired" href="/audio-output/snapcast">FIFO or direct TCP.</Card>
3636+ <Card title="AirPlay" icon="apple" href="/audio-output/airplay">Single or multi-room RAOP.</Card>
3737+ <Card title="Squeezelite" icon="boxes-stacked" href="/audio-output/squeezelite">Slim Protocol multi-room.</Card>
3838+ <Card title="Chromecast" icon="cast" href="/audio-output/chromecast">Google Cast over WAV/HTTP.</Card>
3939+ <Card title="UPnP / DLNA" icon="tower-broadcast" href="/audio-output/upnp">Sink, server, renderer.</Card>
4040+</CardGroup>
4141+4242+## Playback defaults
4343+4444+```toml
4545+playlist_shuffle = false
4646+repeat_mode = 1 # 0=Off 1=All 2=One 3=Shuffle 4=A-B
4747+party_mode = true
4848+```
4949+5050+## Equalizer
5151+5252+```toml
5353+eq_enabled = true
5454+5555+[[eq_band_settings]] # band 0 — low shelf
5656+cutoff = 0
5757+q = 64
5858+gain = 10
5959+6060+[[eq_band_settings]] # bands 1–8 — peaking
6161+cutoff = 3
6262+q = 125
6363+gain = 10
6464+6565+# ...repeat for the remaining bands
6666+```
6767+6868+The full 10-band parametric EQ is documented in
6969+[Audio settings › Equalizer](/audio-settings/equalizer).
7070+7171+## Crossfade
7272+7373+```toml
7474+crossfade = 5
7575+fade_on_stop = false
7676+fade_in_delay = 2
7777+fade_in_duration = 7
7878+fade_out_delay = 4
7979+fade_out_duration = 0
8080+fade_out_mixmode = 2
8181+```
8282+8383+## Tone & stereo
8484+8585+```toml
8686+bass = 0
8787+treble = 0
8888+bass_cutoff = 0
8989+treble_cutoff = 0
9090+balance = 0
9191+stereo_width = 100
9292+stereosw_mode = 0
9393+channel_config = 0
9494+surround_enabled = 0
9595+surround_balance = 0
9696+surround_fx1 = 0
9797+surround_fx2 = 0
9898+```
9999+100100+## ReplayGain
101101+102102+```toml
103103+[replaygain_settings]
104104+noclip = true
105105+type = 0 # 0=Track 1=Album 2=Shuffle (see Replaygain page)
106106+preamp = 0
107107+```
108108+109109+## Compressor
110110+111111+```toml
112112+[compressor_settings]
113113+threshold = -24
114114+makeup_gain = 0
115115+ratio = 4
116116+knee = 1
117117+release_time = 300
118118+attack_time = 5
119119+```
120120+121121+## Where settings come from
122122+123123+There are three layers, in order of precedence:
124124+125125+1. **Runtime API calls** — every setting is also exposed over GraphQL/gRPC
126126+ and persists to disk on the next save cycle.
127127+2. **`settings.toml`** — applied once at startup.
128128+3. **Compiled-in defaults** — in `apps/settings_list.c`.
129129+130130+For the full settings catalogue with units, ranges and where each one is
131131+applied, see the [Settings TOML reference](/reference/settings-toml).
···11+---
22+title: "Quickstart"
33+description: "Install Rockbox, point it at your music, and play your first track."
44+icon: 'rocket'
55+---
66+77+This guide gets you from zero to a running `rockboxd` with a usable web UI in
88+about five minutes.
99+1010+## 1. Install
1111+1212+<Tabs>
1313+ <Tab title="macOS">
1414+ Download the latest `.pkg` for your architecture from the
1515+ [Releases page](https://github.com/tsirysndr/rockbox-zig/releases/latest)
1616+ and double-click to install.
1717+1818+ Or via the universal installer:
1919+2020+ ```sh
2121+ curl -fsSL https://raw.githubusercontent.com/tsirysndr/rockbox-zig/HEAD/install.sh | bash
2222+ ```
2323+ </Tab>
2424+ <Tab title="Ubuntu / Debian">
2525+ ```sh
2626+ echo "deb [trusted=yes] https://apt.fury.io/tsiry/ /" | sudo tee /etc/apt/sources.list.d/fury.list
2727+ sudo apt-get update
2828+ sudo apt-get install rockbox
2929+ ```
3030+ </Tab>
3131+ <Tab title="Fedora">
3232+ Add `/etc/yum.repos.d/fury.repo`:
3333+3434+ ```ini
3535+ [fury]
3636+ name=Gemfury Private Repo
3737+ baseurl=https://yum.fury.io/tsiry/
3838+ enabled=1
3939+ gpgcheck=0
4040+ ```
4141+4242+ Then:
4343+4444+ ```sh
4545+ sudo dnf install rockbox
4646+ ```
4747+ </Tab>
4848+ <Tab title="Arch Linux">
4949+ ```sh
5050+ paru -S rockbox-zig-bin
5151+ ```
5252+ </Tab>
5353+ <Tab title="Docker">
5454+ ```sh
5555+ docker run --rm -it \
5656+ -p 6060-6063:6060-6063 \
5757+ -p 6600:6600 \
5858+ -v $HOME/Music:/music \
5959+ -v $HOME/.config/rockbox.org:/root/.config/rockbox.org \
6060+ tsiry/rockbox
6161+ ```
6262+ </Tab>
6363+</Tabs>
6464+6565+For full distribution coverage, build instructions and Docker images, see
6666+[Installation](/installation).
6767+6868+## 2. Point it at your music
6969+7070+Create `~/.config/rockbox.org/settings.toml`:
7171+7272+```toml
7373+music_dir = "/path/to/your/Music"
7474+audio_output = "builtin"
7575+```
7676+7777+That's the minimum. `music_dir` is the only required field; everything else
7878+has sensible defaults. See [Configuration](/configuration) for the full list.
7979+8080+## 3. Start `rockboxd`
8181+8282+```sh
8383+rockbox start
8484+```
8585+8686+You should see logs like:
8787+8888+```
8989+INFO rockbox-cli rockboxd starting...
9090+INFO rockbox-cli graphql server listening on :6062
9191+INFO rockbox-cli http server listening on :6063
9292+INFO rockbox-cli mpd server listening on :6600
9393+INFO rockbox-cli grpc server listening on :6061
9494+```
9595+9696+## 4. Open a client
9797+9898+<CardGroup cols={2}>
9999+ <Card title="Web UI" icon="globe" href="http://localhost:6062">
100100+ Browse your library and control playback at
101101+ [http://localhost:6062](http://localhost:6062).
102102+ </Card>
103103+ <Card title="GraphiQL" icon="code" href="http://localhost:6062/graphiql">
104104+ Explore the API live at
105105+ [http://localhost:6062/graphiql](http://localhost:6062/graphiql).
106106+ </Card>
107107+ <Card title="MPD client" icon="terminal">
108108+ Point any MPD client (`ncmpcpp`, `mpc`, MALP, etc.) at `localhost:6600`.
109109+ </Card>
110110+ <Card title="HTTP REST" icon="bolt" href="http://localhost:6063">
111111+ Quick `curl` testing — see the REST overview.
112112+ </Card>
113113+</CardGroup>
114114+115115+## 5. Play something
116116+117117+From the web UI, search for a track and hit play. From the terminal:
118118+119119+```sh
120120+mpc -h localhost -p 6600 update
121121+mpc -h localhost -p 6600 search title "money"
122122+mpc -h localhost -p 6600 play
123123+```
124124+125125+Or with the [TypeScript SDK](/sdks/typescript):
126126+127127+```ts
128128+import { RockboxClient } from '@rockbox-zig/sdk';
129129+130130+const client = new RockboxClient();
131131+const { albums } = await client.library.search('dark side');
132132+await client.playback.playAlbum(albums[0].id, { shuffle: true });
133133+```
134134+135135+## Next steps
136136+137137+<CardGroup cols={2}>
138138+ <Card title="Configure" icon="sliders" href="/configuration">
139139+ `settings.toml` reference — every key, every default.
140140+ </Card>
141141+ <Card title="Audio output" icon="speaker" href="/audio-output/overview">
142142+ Send audio to AirPlay, Snapcast, Squeezelite, Chromecast or UPnP.
143143+ </Card>
144144+ <Card title="Pick an SDK" icon="code" href="/sdks/overview">
145145+ Build clients in TypeScript, Python, Ruby, Elixir, Clojure or Gleam.
146146+ </Card>
147147+ <Card title="FAQ" icon="circle-question" href="/reference/faq">
148148+ Common gotchas and how to fix them.
149149+ </Card>
150150+</CardGroup>
+141
mintlify/reference/cli.mdx
···11+---
22+title: "CLI reference"
33+description: "rockbox and rockboxd — the two binaries you'll interact with."
44+icon: 'terminal'
55+---
66+77+There are two binaries:
88+99+- **`rockbox`** — user-facing wrapper. Starts the server, scans the
1010+ library, opens the web UI, manages the systemd service, runs JS/TS
1111+ scripts, and acts as a Bluetooth client on Linux.
1212+- **`rockboxd`** — the daemon itself. Linked by Zig from the C firmware
1313+ + Rust crates + SDL2. Lives at `zig/zig-out/bin/rockboxd` after a build,
1414+ or `/usr/local/bin/rockboxd` after install.
1515+1616+Most users only ever invoke `rockbox`; `rockboxd` is the underlying
1717+process it spawns.
1818+1919+## `rockbox`
2020+2121+```text
2222+rockbox [--rebuild] [SUBCOMMAND]
2323+```
2424+2525+Running `rockbox` with no subcommand starts the server. The subcommands
2626+below are dispatched in `cli/src/main.rs`.
2727+2828+### Global flags
2929+3030+| Flag | Description |
3131+|-----------------------|-------------------------------------------------------|
3232+| `--rebuild`, `-r` | Rebuild the Typesense search index after scan |
3333+| `-h`, `--help` | Print help |
3434+| `-V`, `--version` | Print the version |
3535+3636+### Subcommands
3737+3838+| Subcommand | Aliases | Description |
3939+|-----------------------------------|----------|---------------------------------------------------------|
4040+| _(none)_ | | Start the server |
4141+| `start [-r]` | | Start the server |
4242+| `scan [-d PATH] [-r]` | | Scan a library directory; `-r` rebuilds the search index |
4343+| `webui` | `web` | Open the web UI in your browser |
4444+| `tui` | | Start the terminal UI |
4545+| `repl` | `shell` | Start the Rockbox REPL |
4646+| `run <FILE>` | `x` | Run a JS/TS script via Deno against the local rockboxd |
4747+| `open <PATH_OR_URL>` | | Play a local file or HTTP URL directly |
4848+| `clear` | | Clear the current playlist |
4949+| `service install` | | Install + enable the systemd unit |
5050+| `service uninstall` | | Disable + remove the systemd unit |
5151+| `service status` | | Show the unit status |
5252+| `login <handle>` | `auth` | Log in to Rocksky (BlueSky handle) |
5353+| `whoami` | `me` | Show the currently logged-in user |
5454+| `community` | | Open the Discord invite |
5555+| `setup` | | Install host dependencies (SDL2, etc.) |
5656+| `bluetooth scan [--timeout S]` | | Linux only — scan for Bluetooth devices |
5757+| `bluetooth devices` | | Linux only — list known devices |
5858+| `bluetooth connect <ADDR>` | | Linux only — connect to a device |
5959+| `bluetooth disconnect <ADDR>` | | Linux only — disconnect a device |
6060+6161+### Examples
6262+6363+```sh
6464+# Start the daemon
6565+rockbox start
6666+6767+# Scan the default music_dir, rebuild the search index
6868+rockbox scan -r
6969+7070+# Scan a specific path
7171+rockbox scan -d "/Volumes/Music/Recently Added"
7272+7373+# Play a URL
7474+rockbox open "https://stream.example.com/jazz.mp3"
7575+7676+# Install as a systemd user service
7777+rockbox service install
7878+rockbox service status
7979+8080+# Run a small script against a local rockboxd
8181+rockbox run scripts/scrobble.ts
8282+8383+# Bluetooth (Linux)
8484+rockbox bluetooth scan --timeout 15
8585+rockbox bluetooth connect AA:BB:CC:DD:EE:FF
8686+```
8787+8888+## `rockboxd`
8989+9090+The daemon. Usually you don't run it directly — `rockbox start` or the
9191+systemd unit handles it. When you do invoke it manually, it takes no
9292+subcommands; behaviour is driven by environment variables and
9393+`~/.config/rockbox.org/settings.toml`.
9494+9595+```sh
9696+rockboxd
9797+```
9898+9999+### Environment variables
100100+101101+| Variable | Default | Description |
102102+|---------------------------|--------------------------------------|-----------------------------------------|
103103+| `RUST_LOG` | `info` | Tracing filter (per-crate supported) |
104104+| `ROCKBOX_TCP_PORT` | `6063` | HTTP REST bind port |
105105+| `ROCKBOX_GRAPHQL_PORT` | `6062` | GraphQL bind port |
106106+| `ROCKBOX_RPC_PORT` | `6061` | gRPC bind port |
107107+| `ROCKBOX_MPD_PORT` | `6600` | MPD bind port |
108108+| `ROCKBOX_LIBRARY` | `$HOME/Music` | Default music library path |
109109+| `ROCKBOX_ADDR` | (auto-detected LAN IP) | Address advertised to external players |
110110+| `ROCKBOX_UPDATE_LIBRARY` | unset | When `1`, rebuild Typesense on startup |
111111+| `HOME` | (system) | Used to derive config and library paths |
112112+113113+### Stdout / stderr
114114+115115+- **Stderr** — `tracing` log output. Always safe to redirect or filter.
116116+- **Stdout** — normally empty, **except** when `audio_output = "fifo"`
117117+ with `fifo_path = "-"`, in which case stdout is raw S16LE 44.1 kHz PCM.
118118+119119+```sh
120120+rockboxd | ffplay -f s16le -ar 44100 -ac 2 -
121121+```
122122+123123+### Logging
124124+125125+```sh
126126+RUST_LOG=debug rockboxd
127127+RUST_LOG=rockbox_airplay=debug,rockbox_slim=debug,info rockboxd
128128+```
129129+130130+Never use `eprintln!` / `println!` from inside the codebase — they
131131+bypass the structured filter and pollute stdout (which breaks FIFO
132132+mode). Use `tracing::{debug,info,warn,error}!` in Rust code.
133133+134134+### Files
135135+136136+| Path | Purpose |
137137+|---------------------------------------------------|--------------------------------------|
138138+| `~/.config/rockbox.org/settings.toml` | Persistent configuration |
139139+| `~/.config/rockbox.org/library.db` | SQLite library + listening stats |
140140+| `~/.config/rockbox.org/playlists/` | Saved playlists |
141141+| `~/.config/systemd/user/rockboxd.service` | systemd unit (after `service install`) |
+113
mintlify/reference/faq.mdx
···11+---
22+title: "FAQ"
33+description: "Common questions about Rockbox Zig."
44+icon: 'circle-question'
55+---
66+77+<AccordionGroup>
88+99+<Accordion title="What's the difference between Rockbox and Rockbox Zig?">
1010+[Rockbox](https://www.rockbox.org) is firmware for portable audio
1111+players. Rockbox Zig wraps that same C audio engine in Rust and Zig
1212+services, exposing it on a desktop/server as a single `rockboxd` binary
1313+with gRPC, GraphQL, HTTP and MPD APIs and multi-room output sinks.
1414+The DSP, codecs and tag database come straight from upstream Rockbox.
1515+</Accordion>
1616+1717+<Accordion title="Does it really run on a Raspberry Pi?">
1818+Yes. Linux ARM64 builds are on the
1919+[Releases page](https://github.com/tsirysndr/rockbox-zig/releases). It
2020+runs comfortably on a Pi 4; on a Pi 3 expect Typesense indexing to be
2121+slower on first scan but playback is fine.
2222+</Accordion>
2323+2424+<Accordion title="Which audio formats are supported?">
2525+MP3, OGG Vorbis, FLAC, WAV, AAC, ALAC, Opus, Musepack, WMA, APE,
2626+Wavpack, Speex, AIFF, AC3, SID and several more — 20+ codecs total. The
2727+codec list comes from upstream Rockbox; see
2828+[`AUDIO_EXTENSIONS`](https://github.com/tsirysndr/rockbox-zig/blob/master/crates/server/src/lib.rs)
2929+for what is auto-scanned into the library.
3030+</Accordion>
3131+3232+<Accordion title="Can I stream from YouTube / Spotify / Tidal?">
3333+Not yet. Generic HTTP(S) stream URLs work — you can queue them and
3434+playback works through the netstream layer in `crates/netstream/`. Rich
3535+provider integrations (YouTube, Spotify, Tidal) are on the roadmap.
3636+</Accordion>
3737+3838+<Accordion title="Why not just use mpd / Mopidy / Volumio / Snapcast directly?">
3939+You can — they're great projects. Rockbox Zig differs in that the
4040+audio engine, DSP, parametric EQ and crossfade are the upstream Rockbox
4141+implementation rather than ALSA's defaults. If you specifically want
4242+Rockbox's sound (dithering, PBE, Haas surround, ReplayGain pipeline, the
4343+EQ presets) on a desktop or server, this is one way to get it.
4444+</Accordion>
4545+4646+<Accordion title="Multi-room: AirPlay vs Snapcast vs Squeezelite — which one?">
4747+- **AirPlay** — pick this if you have Apple TVs / HomePods / shairport-sync
4848+ receivers. Built-in fan-out, ~8 ms tight sync.
4949+- **Snapcast** — best when you have or are willing to deploy snapserver and
5050+ multiple snapclients. Works on every platform, very tight sync.
5151+- **Squeezelite** — pick this if you already run squeezelite or
5252+ Logitech-style hardware. One rockboxd serves any number of squeezelite
5353+ clients with per-client cursors into a 4 MB shared buffer.
5454+</Accordion>
5555+5656+<Accordion title="Does it work with my MPD client?">
5757+Probably. Rockbox runs an MPD-compatible server on port 6600. `mpc`,
5858+`ncmpcpp`, MALP, M.A.L.P. and Cantata are all tested. If your client
5959+breaks on something Rockbox-specific, please open an issue.
6060+</Accordion>
6161+6262+<Accordion title="Can I run rockboxd headless / as a service?">
6363+Yes — `rockbox service install` registers a user-level systemd unit. See
6464+[Installation](/installation#run-as-a-systemd-service).
6565+</Accordion>
6666+6767+<Accordion title="Where are the listening stats stored?">
6868+SQLite, in `~/.config/rockbox.org/library.db`. The
6969+[smart playlist rules](/sdks/typescript) read from this database.
7070+You can record `played` / `skipped` events manually from any SDK or via
7171+the REST endpoints `POST /track-stats/{id}/played` and
7272+`POST /track-stats/{id}/skipped`.
7373+</Accordion>
7474+7575+<Accordion title="Why a single binary?">
7676+Simpler to deploy, simpler to debug, and the firmware/Rust boundary is
7777+already complex enough that adding IPC on top would be a step backward.
7878+The C audio engine and the Rust services share memory through static
7979+libraries linked by Zig — see [Architecture](/architecture/overview).
8080+</Accordion>
8181+8282+<Accordion title="Does it support Bluetooth?">
8383+On Linux, yes — pairing/connecting is exposed through the REST and
8484+GraphQL APIs (and SDKs). On macOS and Windows, no first-party
8585+integration; use the OS-level Bluetooth stack and route the built-in
8686+SDL output to the BT device.
8787+</Accordion>
8888+8989+<Accordion title="Can I write a plugin?">
9090+Yes. Every SDK ships a Jellyfin-style plugin lifecycle:
9191+9292+```ts
9393+const SleepTimer = (minutes: number): RockboxPlugin => ({
9494+ name: 'sleep-timer',
9595+ version: '1.0.0',
9696+ install({ events, query }) {
9797+ setTimeout(() => query('mutation { hardStop }'), minutes * 60_000);
9898+ },
9999+});
100100+101101+await client.use(SleepTimer(30));
102102+```
103103+104104+Wasm extensions inside `rockboxd` itself are on the roadmap.
105105+</Accordion>
106106+107107+<Accordion title="How do I contribute?">
108108+Read the [Contributing guide](https://github.com/tsirysndr/rockbox-zig/blob/master/CONTRIBUTING.md),
109109+hop into [Discord](https://discord.gg/tXPrgcPKSt), and open a PR. Build
110110+from source with the [build instructions](/architecture/build).
111111+</Accordion>
112112+113113+</AccordionGroup>
+48
mintlify/reference/ports.mdx
···11+---
22+title: "Ports"
33+description: "Every TCP and UDP port rockboxd binds, plus mDNS/SSDP service types."
44+icon: 'plug'
55+---
66+77+| Service | Port | Protocol | Override |
88+|----------------------------------------|-------|--------------------|-----------------------------|
99+| gRPC | 6061 | gRPC / gRPC-Web | `ROCKBOX_RPC_PORT` |
1010+| GraphQL + Web UI | 6062 | HTTP / WS | `ROCKBOX_GRAPHQL_PORT` |
1111+| HTTP REST | 6063 | HTTP | `ROCKBOX_TCP_PORT` |
1212+| MPD server | 6600 | MPD protocol | `ROCKBOX_MPD_PORT` |
1313+| Slim Protocol (squeezelite) | 3483 | TCP | `squeezelite_port` |
1414+| HTTP PCM stream (squeezelite) | 9999 | HTTP | `squeezelite_http_port` |
1515+| Chromecast WAV stream | 7881 | HTTP | `chromecast_http_port` |
1616+| UPnP MediaServer (ContentDirectory) | 7878 | HTTP / SSDP | `upnp_server_port` |
1717+| UPnP WAV broadcast (PCM sink) | 7879 | HTTP | `upnp_http_port` |
1818+| UPnP MediaRenderer (AVTransport) | 7880 | HTTP / SSDP | `upnp_renderer_port` |
1919+| Snapcast TCP source (outbound only) | 4953 | TCP (client) | `snapcast_tcp_port` |
2020+2121+## mDNS / SSDP service types
2222+2323+Rockbox both **advertises** and **scans for** the following on the LAN:
2424+2525+| Service | Service type | Direction |
2626+|-------------------------------|---------------------------------|--------------------|
2727+| Rockbox itself | `_rockbox._tcp.local.` | advertise |
2828+| Chromecast | `_googlecast._tcp.local.` | scan |
2929+| AirPlay (RAOP) | `_raop._tcp.local.` | scan |
3030+| Squeezelite players | `_slim._tcp.local.` | scan |
3131+| Snapcast servers | `_snapcast._tcp.local.` | scan |
3232+| UPnP renderers | `urn:schemas-upnp-org:device:MediaRenderer:1` | SSDP scan |
3333+| UPnP media server (this one) | `urn:schemas-upnp-org:device:MediaServer:1` | SSDP advertise |
3434+3535+## Firewall checklist
3636+3737+If rockboxd is in a VM, container, or behind a firewall, **at minimum**
3838+inbound on these is needed:
3939+4040+- `6062/tcp` — for the web UI and GraphQL
4141+- `6063/tcp` — for the REST API
4242+- `6600/tcp` — for MPD clients
4343+- `5353/udp` — mDNS (multicast)
4444+- `1900/udp` — SSDP (multicast)
4545+4646+If you use Chromecast or AirPlay, the receiver must also be able to
4747+**reach back** to rockboxd on `7881/tcp` (Chromecast WAV) or
4848+`7879/tcp` (UPnP WAV).
+158
mintlify/reference/settings-toml.mdx
···11+---
22+title: "settings.toml reference"
33+description: "Every key Rockbox reads from ~/.config/rockbox.org/settings.toml."
44+icon: 'file-code'
55+---
66+77+This is the canonical list of keys recognised by `rockbox_settings::load_settings()`.
88+Keys not listed here are ignored.
99+1010+## Core
1111+1212+| Key | Type | Default | Description |
1313+|----------------|---------|-------------|--------------------------------------------------|
1414+| `music_dir` | string | — | Absolute path to your music library |
1515+| `audio_output` | string | `"builtin"` | `builtin` / `fifo` / `airplay` / `squeezelite` / `chromecast` / `snapcast_tcp` / `upnp` |
1616+| `player_name` | string | `""` | Name advertised to MPD clients and the UI |
1717+1818+## FIFO / pipe sink
1919+2020+| Key | Type | Default | Description |
2121+|-------------|--------|----------------|--------------------------------------------|
2222+| `fifo_path` | string | `/tmp/rockbox.fifo` | Named FIFO path, or `"-"` for stdout |
2323+2424+## Snapcast TCP sink
2525+2626+| Key | Type | Default | Description |
2727+|----------------------|--------|----------------|------------------------------------------|
2828+| `snapcast_tcp_host` | string | — | snapserver host |
2929+| `snapcast_tcp_port` | int | `4953` | snapserver TCP source port |
3030+3131+## AirPlay sink
3232+3333+| Key | Type | Default | Description |
3434+|-----------------------|-----------------|---------|---------------------------------------------|
3535+| `airplay_host` | string | — | Single receiver IP |
3636+| `airplay_port` | int | `5000` | Single receiver port |
3737+| `airplay_receivers` | array of tables | — | Multi-room. Each entry: `host`, optional `port` |
3838+3939+```toml
4040+[[airplay_receivers]]
4141+host = "192.168.1.50"
4242+4343+[[airplay_receivers]]
4444+host = "192.168.1.51"
4545+port = 5000
4646+```
4747+4848+## Squeezelite sink
4949+5050+| Key | Type | Default | Description |
5151+|---------------------------|------|---------|--------------------------------------|
5252+| `squeezelite_port` | int | `3483` | Slim Protocol TCP port |
5353+| `squeezelite_http_port` | int | `9999` | HTTP PCM broadcast port |
5454+5555+## Chromecast sink
5656+5757+| Key | Type | Default | Description |
5858+|--------------------------|---------|---------|-------------------------------------|
5959+| `chromecast_host` | string | — | Target Chromecast IP |
6060+| `chromecast_port` | int | `8009` | Cast control port |
6161+| `chromecast_http_port` | int | `7881` | WAV HTTP stream port |
6262+6363+## UPnP
6464+6565+| Key | Type | Default | Description |
6666+|--------------------------|---------|-------------|--------------------------------------------|
6767+| `upnp_renderer_url` | string | — | AVTransport controlURL of the target renderer |
6868+| `upnp_http_port` | int | `7879` | WAV broadcast HTTP port (sink mode) |
6969+| `upnp_server_enabled` | bool | `false` | Start the ContentDirectory media server |
7070+| `upnp_server_port` | int | `7878` | Media server HTTP port |
7171+| `upnp_renderer_enabled` | bool | `false` | Start the MediaRenderer endpoint |
7272+| `upnp_renderer_port` | int | `7880` | MediaRenderer HTTP port |
7373+| `upnp_friendly_name` | string | `"Rockbox"` | Display name shown to control points |
7474+7575+## Playback defaults
7676+7777+| Key | Type | Default | Description |
7878+|--------------------|---------|---------|------------------------------------------------|
7979+| `playlist_shuffle` | bool | `false` | |
8080+| `repeat_mode` | int | `1` | 0=Off 1=All 2=One 3=Shuffle 4=A-B |
8181+| `party_mode` | bool | `true` | |
8282+8383+## Tone, stereo & channels
8484+8585+| Key | Type | Default | Description |
8686+|--------------------|--------|---------|--------------------------------------------------|
8787+| `bass` | int | `0` | dB |
8888+| `treble` | int | `0` | dB |
8989+| `bass_cutoff` | int | `0` | Hz |
9090+| `treble_cutoff` | int | `0` | Hz |
9191+| `balance` | int | `0` | −100..+100 |
9292+| `stereo_width` | int | `100` | 0..255 % (when `channel_config = Custom`) |
9393+| `stereosw_mode` | int | `0` | |
9494+| `channel_config` | int | `0` | 0=Stereo 1=Mono 2=Custom 3=ML 4=MR 5=Karaoke 6=Swap |
9595+9696+## Surround
9797+9898+| Key | Type | Default | Description |
9999+|----------------------|------|---------|-----------------------------------|
100100+| `surround_enabled` | int | `0` | 0/5/8/10/15/30 ms (0=off) |
101101+| `surround_balance` | int | `0` | 0..99 % |
102102+| `surround_fx1` | int | `0` | HF cutoff, Hz |
103103+| `surround_fx2` | int | `0` | LF cutoff, Hz |
104104+| `surround_method2` | bool | `false` | Side-only processing |
105105+| `surround_mix` | int | `0` | 0..100 % |
106106+107107+## Crossfade
108108+109109+```toml
110110+crossfade = 5
111111+fade_on_stop = false
112112+fade_in_delay = 2
113113+fade_in_duration = 7
114114+fade_out_delay = 4
115115+fade_out_duration = 0
116116+fade_out_mixmode = 2
117117+```
118118+119119+## Equalizer
120120+121121+```toml
122122+eq_enabled = true
123123+eq_precut = 3 # dB headroom
124124+125125+[[eq_band_settings]]
126126+cutoff = 0 # Hz (per-band)
127127+q = 64 # × 10 fixed-point
128128+gain = 10 # × 10 dB fixed-point
129129+130130+# repeat for each of the 10 bands
131131+```
132132+133133+## ReplayGain
134134+135135+```toml
136136+[replaygain_settings]
137137+type = 0 # 0=Track 1=Album 2=Track shuffle 3=Off
138138+noclip = true
139139+preamp = 0 # × 10 dB fixed-point
140140+```
141141+142142+## Compressor
143143+144144+```toml
145145+[compressor_settings]
146146+threshold = -24 # dB
147147+makeup_gain = 0
148148+ratio = 4
149149+knee = 1
150150+release_time = 300 # ms
151151+attack_time = 5 # ms
152152+```
153153+154154+## Where it's parsed
155155+156156+The file is read by `rockbox_settings::load_settings()` in
157157+`crates/settings/src/lib.rs`. Unknown keys are silently ignored, so
158158+typos won't crash startup — but they also won't take effect.
+144
mintlify/reference/troubleshooting.mdx
···11+---
22+title: "Troubleshooting"
33+description: "Common errors, the symptoms they produce, and how to fix them."
44+icon: 'wrench'
55+---
66+77+## No audio when using built-in SDL on macOS
88+99+**Symptom:** `rockboxd` starts cleanly, the UI shows playback progressing,
1010+but no audio is heard.
1111+1212+**Cause:** the SDL audio subsystem wasn't initialised. On macOS the SDL
1313+event thread doesn't init audio (it's `#ifndef __APPLE__`).
1414+1515+**Fix:** this is handled in
1616+`firmware/target/hosted/sdl/system-sdl.c` — but only in recent builds.
1717+Update to the latest release. If you build from source, make sure
1818+`SDL_InitSubSystem(SDL_INIT_AUDIO)` is called from the `#else` branch of
1919+the platform guard.
2020+2121+## Snapcast: silence, then snapserver disconnects
2222+2323+**Symptom:** snapserver logs `Stream: 'default' eof` shortly after rockboxd starts.
2424+2525+**Cause:** snapserver was started **before** rockboxd, so it opened the
2626+FIFO first and saw EOF.
2727+2828+**Fix:** start rockboxd first, then snapserver. Rockbox holds a permanent
2929+write-side handle on the FIFO so snapserver never sees EOF mid-track.
3030+3131+For TCP mode, the order is reversed — start snapserver first.
3232+3333+## Squeezelite disconnects every 36 seconds
3434+3535+**Cause:** the `STMt` heartbeat is not being answered. squeezelite has a
3636+36-second watchdog.
3737+3838+**Fix:** every `STMt` heartbeat must be answered with `audg`. This is
3939+already the case in current builds; if you're hacking on
4040+`crates/slim/`, don't strip that response.
4141+4242+## Stale binary after editing C or Rust
4343+4444+**Symptom:** behaviour doesn't match the source code; logs reference
4545+old strings.
4646+4747+**Cause:** Zig only re-links when the static libraries are newer than
4848+the binary.
4949+5050+**Fix:**
5151+5252+```sh
5353+ls -la zig/zig-out/bin/rockboxd \
5454+ build-lib/libfirmware.a \
5555+ target/release/librockbox_cli.a
5656+```
5757+5858+If `rockboxd` is newer than every `.a`, force a rebuild:
5959+6060+```sh
6161+# After C changes
6262+cd build-lib && make lib && cd .. && cd zig && zig build
6363+6464+# After Rust changes
6565+cargo build --release -p rockbox-cli -p rockbox-server && cd zig && zig build
6666+```
6767+6868+## "library_directory is not set" after fresh install
6969+7070+**Cause:** `~/.config/rockbox.org/settings.toml` is missing or has no
7171+`music_dir` key.
7272+7373+**Fix:**
7474+7575+```toml
7676+music_dir = "/path/to/your/Music"
7777+```
7878+7979+## AirPlay receiver refuses to connect
8080+8181+**Symptom:** logs show `RTSP ANNOUNCE → 401` or `Receiver requires
8282+password`.
8383+8484+**Cause:** the receiver requires a PIN/password (most "AirPlay 2" gear).
8585+8686+**Status:** AirPlay 2 pairing/encryption isn't implemented. Use a
8787+shairport-sync receiver, an Apple TV, an Airport Express, or another
8888+AirPlay 1 device. See [AirPlay](/audio-output/airplay).
8989+9090+## Chromecast plays once then stops
9191+9292+**Symptom:** first track plays through, queue advances, second track
9393+gets stuck buffering.
9494+9595+**Cause:** the Chromecast cannot reach back to port 7881 on rockboxd's
9696+host. Common when rockboxd is in a VM/container.
9797+9898+**Fix:** forward port 7881 to the host, or run the container with
9999+`--network host`. See the [Chromecast](/audio-output/chromecast) page.
100100+101101+## mDNS discovery returns nothing
102102+103103+**Symptom:** the device picker is empty, even though receivers are on
104104+the LAN.
105105+106106+**Causes & fixes:**
107107+108108+- **Multicast doesn't cross Docker bridges.** Use `--network host`.
109109+- **Some Wi-Fi APs filter multicast.** Enable "multicast forwarding" or
110110+ "IGMP snooping" — vendor-specific naming.
111111+- **Avahi/mDNS not running.** On Linux, ensure `avahi-daemon` is
112112+ running for SSDP/Bonjour to work.
113113+114114+## "address already in use" on startup
115115+116116+**Cause:** a previous rockboxd process didn't shut down cleanly, or
117117+another service is on one of the API ports.
118118+119119+**Fix:**
120120+121121+```sh
122122+lsof -i :6061 -i :6062 -i :6063 -i :6600
123123+kill -9 <pid>
124124+```
125125+126126+…or change the bind ports via the env vars in
127127+[Reference › Ports](/reference/ports).
128128+129129+## Logs are too quiet
130130+131131+```sh
132132+RUST_LOG=debug rockboxd
133133+```
134134+135135+Or scoped:
136136+137137+```sh
138138+RUST_LOG=rockbox_airplay=debug,info rockboxd
139139+RUST_LOG=rockbox_slim=debug,info rockboxd
140140+```
141141+142142+Never use `eprintln!` / `println!` in the codebase — they bypass the
143143+filter and pollute stdout (which breaks FIFO mode). All Rust logging
144144+goes through `tracing`.
+83
mintlify/sdks/clojure.mdx
···11+---
22+title: "Clojure"
33+description: "Pipe-friendly Clojure wrapper over rockboxd's GraphQL API."
44+icon: 'lambda'
55+---
66+77+`deps.edn`:
88+99+```clojure
1010+{:deps {org.clojars.tsiry/rockbox-clj {:mvn/version "0.1.2-SNAPSHOT"}}}
1111+```
1212+1313+## Quick start
1414+1515+```clojure
1616+(require '[rockbox.core :as rb]
1717+ '[rockbox.playback :as pb]
1818+ '[rockbox.library :as lib])
1919+2020+(def client (rb/client))
2121+2222+(rb/connect client)
2323+2424+(when-let [t (pb/current-track client)]
2525+ (println "Now playing:" (:title t) "—" (:artist t)))
2626+2727+(let [{:keys [albums tracks]} (lib/search client "dark side")]
2828+ (println (count albums) "albums," (count tracks) "tracks"))
2929+3030+(-> client
3131+ (pb/play-album "album-id" {:shuffle true}))
3232+3333+(rb/on client :track-changed
3434+ (fn [t] (println "▶" (:title t) "by" (:artist t))))
3535+3636+(rb/disconnect client)
3737+```
3838+3939+## Configure
4040+4141+```clojure
4242+(def c (rb/client)) ;; localhost:6062
4343+(def c (rb/client {:host "192.168.1.42" :port 6062}))
4444+(def c (rb/client {:http-url "https://music.home/graphql"
4545+ :ws-url "wss://music.home/graphql"}))
4646+4747+;; Builder style — every with-* fn returns a new client value
4848+(def c (-> (rb/client)
4949+ (rb/with-host "music.home")
5050+ (rb/with-port 6062)
5151+ (rb/with-timeout 30000)
5252+ (rb/with-headers {:x-trace-id "req-123"})))
5353+```
5454+5555+## Conventions
5656+5757+- **Action functions return the client**, so chains compose with `->`:
5858+ `(-> client (pb/play-album "id") (pb/seek 30000))`
5959+- **Read functions return data** as plain Clojure maps with kebab-case keys.
6060+- **Enums are keywords**: `:playing`, `:paused`, `:stopped`.
6161+- **Events surface as callbacks _or_ `core.async` channels.**
6262+6363+## API surface
6464+6565+```clojure
6666+rockbox.core ;; client, connect, disconnect, on, query
6767+rockbox.playback ;; transport, play helpers
6868+rockbox.library ;; albums, artists, tracks, search, likes
6969+rockbox.playlist ;; live queue
7070+rockbox.saved-playlists ;; persistent playlists
7171+rockbox.smart-playlists ;; rule-based playlists
7272+rockbox.sound ;; volume
7373+rockbox.settings ;; EQ / ReplayGain / crossfade / …
7474+rockbox.system ;; version, status
7575+rockbox.browse ;; filesystem
7676+rockbox.devices ;; output devices
7777+rockbox.bluetooth ;; Linux only
7878+```
7979+8080+## More
8181+8282+Full reference and `core.async` event channels: see the
8383+[Clojure SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/clojure/README.md).
+82
mintlify/sdks/elixir.mdx
···11+---
22+title: "Elixir"
33+description: "Idiomatic Elixir SDK — pipe-friendly, builder-friendly, with messages-as-events."
44+icon: 'droplet'
55+---
66+77+```elixir
88+def deps do
99+ [{:rockbox_ex, "~> 0.1"}]
1010+end
1111+```
1212+1313+## Quick start
1414+1515+```elixir
1616+client = Rockbox.new()
1717+1818+# Optional: opens the WebSocket so subscribers receive events
1919+{:ok, _pid} = Rockbox.connect(client)
2020+2121+case Rockbox.Playback.current_track(client) do
2222+ {:ok, %Rockbox.Track{} = t} -> IO.puts("▶ #{t.title} — #{t.artist}")
2323+ {:ok, nil} -> IO.puts("Nothing is playing.")
2424+end
2525+2626+{:ok, results} = Rockbox.Library.search(client, "dark side")
2727+album = List.first(results.albums)
2828+:ok = Rockbox.Playback.play_album(client, album.id, shuffle: true)
2929+3030+# Events arrive as messages
3131+:ok = Rockbox.subscribe(:track_changed)
3232+3333+receive do
3434+ {:rockbox, :track_changed, track} ->
3535+ IO.puts("Now: #{track.title}")
3636+end
3737+3838+Rockbox.disconnect(client)
3939+```
4040+4141+## Configure
4242+4343+```elixir
4444+client = Rockbox.new() # localhost:6062
4545+client = Rockbox.new(host: "192.168.1.42", port: 6062)
4646+client = Rockbox.new(
4747+ http_url: "https://music.home/graphql",
4848+ ws_url: "wss://music.home/graphql"
4949+)
5050+```
5151+5252+## Highlights
5353+5454+- **Pipe-friendly** — every API function takes the client as its first arg.
5555+- **Builder-friendly** — smart-playlist rules and partial settings updates compose with `|>`.
5656+- **Tagged tuples or bangs** — `name/N → {:ok, value} | {:error, exception}`,
5757+ with a matching `name!/N` that raises.
5858+- **Real-time events as messages** — `Rockbox.subscribe(:track_changed)` and
5959+ receive `{:rockbox, :track_changed, %Rockbox.Track{}}`.
6060+- **Plugins** — implement `Rockbox.Plugin` and install with
6161+ `Rockbox.use_plugin/2`.
6262+6363+## API surface
6464+6565+```elixir
6666+Rockbox.Playback.* # transport, current/next track, play helpers
6767+Rockbox.Library.* # albums, artists, tracks, search, likes
6868+Rockbox.Queue.* # live queue
6969+Rockbox.SavedPlaylists.* # persistent playlists
7070+Rockbox.SmartPlaylists.* # rule-based playlists
7171+Rockbox.Sound.* # volume
7272+Rockbox.Settings.* # EQ / ReplayGain / crossfade / …
7373+Rockbox.System.* # version, status
7474+Rockbox.Browse.* # filesystem
7575+Rockbox.Devices.* # output devices
7676+Rockbox.Bluetooth.* # Linux only
7777+```
7878+7979+## More
8080+8181+Full reference and rule-builder DSL: see the
8282+[Elixir SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/elixir/README.md).
+83
mintlify/sdks/gleam.mdx
···11+---
22+title: "Gleam"
33+description: "Type-safe Gleam SDK with a tagged Result on every call."
44+icon: 'star'
55+---
66+77+```sh
88+gleam add rockbox
99+```
1010+1111+## Quick start
1212+1313+```gleam
1414+import gleam/io
1515+import gleam/list
1616+import gleam/option.{None, Some}
1717+import rockbox
1818+import rockbox/library
1919+import rockbox/playback
2020+2121+pub fn main() {
2222+ let client = rockbox.default_client()
2323+2424+ case playback.current_track(client) {
2525+ Ok(Some(track)) -> io.println("▶ " <> track.title <> " — " <> track.artist)
2626+ Ok(None) -> io.println("Nothing is playing.")
2727+ Error(_) -> io.println("Could not reach rockboxd.")
2828+ }
2929+3030+ let assert Ok(results) = library.search(client, "dark side")
3131+ case list.first(results.albums) {
3232+ Ok(album) -> {
3333+ let _ = playback.play_album(
3434+ client, album.id,
3535+ playback.play_options() |> playback.with_shuffle(True),
3636+ )
3737+ Nil
3838+ }
3939+ Error(_) -> Nil
4040+ }
4141+}
4242+```
4343+4444+## Configure
4545+4646+```gleam
4747+let client = rockbox.default_client() // localhost:6062
4848+let client = rockbox.at(host: "192.168.1.42", port: 6062)
4949+5050+let client =
5151+ rockbox.new()
5252+ |> rockbox.url("http://192.168.1.42:6062/graphql")
5353+ |> rockbox.connect
5454+```
5555+5656+## Highlights
5757+5858+- **Pipe-friendly** — every API function takes the client as its first arg.
5959+- **Tagged results** — every call returns
6060+ `Result(value, rockbox/error.Error)`, so `case` and `use` flows stay flat.
6161+- **Type-safe rules DSL** — compose smart-playlist rules with
6262+ `rockbox/smart_playlists/rules` instead of hand-written JSON.
6363+6464+## API surface
6565+6666+```gleam
6767+rockbox/playback
6868+rockbox/library
6969+rockbox/queue
7070+rockbox/saved_playlists
7171+rockbox/smart_playlists
7272+rockbox/sound
7373+rockbox/settings
7474+rockbox/system
7575+rockbox/browse
7676+rockbox/devices
7777+rockbox/bluetooth // Linux only
7878+```
7979+8080+## More
8181+8282+Full reference and rule DSL: see the
8383+[Gleam SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/gleam/README.md).
+150
mintlify/sdks/overview.mdx
···11+---
22+title: "Client SDKs"
33+description: "Six first-party SDKs. All wrap the GraphQL transport, surface real-time events, and ship a tiny plugin system."
44+icon: 'cubes'
55+---
66+77+Every SDK targets the GraphQL endpoint on **port 6062** and exposes the
88+same domain-namespaced API:
99+1010+```
1111+client.playback # transport, current/next track, play helpers
1212+client.library # albums, artists, tracks, search, likes, scan
1313+client.playlist # the active queue
1414+client.savedPlaylists
1515+client.smartPlaylists
1616+client.sound # volume
1717+client.settings # global EQ / replaygain / crossfade / shuffle
1818+client.system # version, runtime info
1919+client.browse # filesystem browser
2020+client.devices # output devices (Cast, AirPlay, Snapcast)
2121+client.bluetooth # Linux only
2222+```
2323+2424+## Pick a language
2525+2626+<CardGroup cols={3}>
2727+ <Card title="TypeScript" icon="js" href="/sdks/typescript">
2828+ `bun add @rockbox-zig/sdk`
2929+ </Card>
3030+ <Card title="Python" icon="python" href="/sdks/python">
3131+ `uv add rockbox-sdk` — async-first
3232+ </Card>
3333+ <Card title="Ruby" icon="gem" href="/sdks/ruby">
3434+ `gem install rockbox`
3535+ </Card>
3636+ <Card title="Elixir" icon="droplet" href="/sdks/elixir">
3737+ `{:rockbox_ex, "~> 0.1"}`
3838+ </Card>
3939+ <Card title="Clojure" icon="lambda" href="/sdks/clojure">
4040+ `org.clojars.tsiry/rockbox-clj`
4141+ </Card>
4242+ <Card title="Gleam" icon="star" href="/sdks/gleam">
4343+ `gleam add rockbox`
4444+ </Card>
4545+</CardGroup>
4646+4747+## Why not just use the GraphQL transport directly?
4848+4949+You can — `client.query()` on every SDK is an escape hatch, and the
5050+GraphiQL explorer at
5151+[http://localhost:6062/graphiql](http://localhost:6062/graphiql) lets you
5252+test queries without writing any client code. The SDKs add value when you
5353+want:
5454+5555+- **Typed responses** — Pydantic models in Python, `Struct`s in Ruby,
5656+ TypeScript types, Gleam tagged unions.
5757+- **Real-time events** — `track:changed` / `status:changed` /
5858+ `playlist:changed` over WebSocket with auto-reconnect and exponential
5959+ backoff.
6060+- **A plugin system** — Jellyfin-style install/uninstall lifecycle for
6161+ cross-cutting features (scrobbling, notifications, sleep timer).
6262+- **Smart-playlist rule builders** — type-safe rule DSLs (Gleam, Elixir,
6363+ Clojure).
6464+- **Idiomatic ergonomics** — pipe-friendly in functional languages,
6565+ builder DSLs in OOP languages.
6666+6767+## Common patterns
6868+6969+<CodeGroup>
7070+```ts TypeScript
7171+import { RockboxClient } from '@rockbox-zig/sdk';
7272+7373+const client = new RockboxClient();
7474+client.connect();
7575+7676+client.on('track:changed', (t) => console.log(`▶ ${t.title} — ${t.artist}`));
7777+7878+const { albums } = await client.library.search('dark side');
7979+await client.playback.playAlbum(albums[0].id, { shuffle: true });
8080+```
8181+8282+```python Python
8383+import asyncio
8484+from rockbox_sdk import RockboxClient
8585+8686+async def main():
8787+ async with RockboxClient(host="localhost") as client:
8888+ await client.connect()
8989+ results = await client.library.search("dark side")
9090+ await client.playback.play_album(results.albums[0].id, shuffle=True)
9191+9292+asyncio.run(main())
9393+```
9494+9595+```ruby Ruby
9696+require "rockbox"
9797+9898+client = Rockbox::Client.new
9999+client.on(:track_changed) { |t| puts "▶ #{t.title} — #{t.artist}" }
100100+client.connect
101101+102102+results = client.library.search("dark side")
103103+client.playback.play_album(results.albums.first.id, shuffle: true)
104104+```
105105+106106+```elixir Elixir
107107+client = Rockbox.new()
108108+{:ok, _pid} = Rockbox.connect(client)
109109+Rockbox.subscribe(:track_changed)
110110+111111+{:ok, results} = Rockbox.Library.search(client, "dark side")
112112+album = List.first(results.albums)
113113+:ok = Rockbox.Playback.play_album(client, album.id, shuffle: true)
114114+```
115115+116116+```clojure Clojure
117117+(require '[rockbox.core :as rb]
118118+ '[rockbox.playback :as pb]
119119+ '[rockbox.library :as lib])
120120+121121+(def client (rb/client))
122122+(rb/connect client)
123123+(rb/on client :track-changed
124124+ (fn [t] (println "▶" (:title t) "—" (:artist t))))
125125+126126+(let [{:keys [albums]} (lib/search client "dark side")]
127127+ (pb/play-album client (:id (first albums)) {:shuffle true}))
128128+```
129129+130130+```gleam Gleam
131131+import rockbox
132132+import rockbox/library
133133+import rockbox/playback
134134+135135+pub fn main() {
136136+ let client = rockbox.default_client()
137137+ let assert Ok(results) = library.search(client, "dark side")
138138+ case list.first(results.albums) {
139139+ Ok(album) -> {
140140+ let _ = playback.play_album(
141141+ client, album.id,
142142+ playback.play_options() |> playback.with_shuffle(True),
143143+ )
144144+ Nil
145145+ }
146146+ Error(_) -> Nil
147147+ }
148148+}
149149+```
150150+</CodeGroup>
+148
mintlify/sdks/python.mdx
···11+---
22+title: "Python"
33+description: "Async-first Python SDK on httpx + websockets, with Pydantic models."
44+icon: 'python'
55+---
66+77+```sh
88+uv add rockbox-sdk
99+# or
1010+pip install rockbox-sdk
1111+```
1212+1313+Requires **Python 3.10+**.
1414+1515+## Quick start
1616+1717+```python
1818+import asyncio
1919+from rockbox_sdk import RockboxClient, PlaybackStatus
2020+2121+async def main():
2222+ async with RockboxClient(host="localhost") as client:
2323+ track = await client.playback.current_track()
2424+ if track:
2525+ print(f"Now: {track.title} — {track.artist}")
2626+ if await client.playback.status() == PlaybackStatus.PAUSED:
2727+ await client.playback.resume()
2828+2929+asyncio.run(main())
3030+```
3131+3232+## Highlights
3333+3434+- **Async-first** — built on `httpx` + `websockets`. Use `await` everywhere.
3535+- **Domain-namespaced API** — `client.playback.*`, `client.library.*`, `client.sound.*`, …
3636+- **Typed responses** — every reply is a Pydantic model with snake_case fields.
3737+- **Real-time events** — `connect()` opens a WebSocket and forwards
3838+ `track:changed` / `status:changed` / `playlist:changed` to listeners.
3939+- **Builder API** — `RockboxClient.builder().host(...).port(...).build()`.
4040+- **Plugin system** — Jellyfin-style install/uninstall lifecycle.
4141+- **Python-friendly** — context manager, decorator listeners, dataclass inputs.
4242+4343+## Configure
4444+4545+```python
4646+client = RockboxClient(host="192.168.1.42", port=6062)
4747+4848+# Builder
4949+client = (
5050+ RockboxClient.builder()
5151+ .host("nas.local")
5252+ .port(6062)
5353+ .timeout(15)
5454+ .build()
5555+)
5656+5757+# Full URL override
5858+client = RockboxClient(
5959+ http_url="http://nas.local:6062/graphql",
6060+ ws_url="ws://nas.local:6062/graphql",
6161+)
6262+```
6363+6464+Always close: `await client.aclose()`, or use it as an async context
6565+manager (`async with RockboxClient() as client:`).
6666+6767+## Domains
6868+6969+| Namespace | What it does |
7070+|--------------------------|-----------------------------------------------|
7171+| `client.playback` | Transport, current/next, play helpers |
7272+| `client.library` | Albums, artists, tracks, search, likes, scan |
7373+| `client.playlist` | The active queue |
7474+| `client.saved_playlists` | Persistent playlists & folders |
7575+| `client.smart_playlists` | Rule-based playlists & stats |
7676+| `client.sound` | Volume |
7777+| `client.settings` | EQ / ReplayGain / crossfade / shuffle / … |
7878+| `client.system` | Version, runtime info |
7979+| `client.browse` | Filesystem & UPnP browser |
8080+| `client.devices` | Cast / source devices |
8181+| `client.bluetooth` | Bluetooth (Linux only) |
8282+8383+## Real-time events
8484+8585+```python
8686+from rockbox_sdk import RockboxClient, TRACK_CHANGED, STATUS_CHANGED
8787+8888+async with RockboxClient() as client:
8989+ await client.connect()
9090+9191+ @client.on(TRACK_CHANGED)
9292+ async def on_track(track):
9393+ print(f"▶ {track.title} — {track.artist}")
9494+9595+ @client.on(STATUS_CHANGED)
9696+ def on_status(raw_status):
9797+ print(f"◐ status = {raw_status}")
9898+9999+ await asyncio.Event().wait()
100100+```
101101+102102+Convenience wrappers: `client.on_track_changed(...)`,
103103+`client.on_status_changed(...)`, `client.on_playlist_changed(...)`.
104104+105105+## Plugins
106106+107107+```python
108108+class SleepTimer:
109109+ name = "sleep-timer"
110110+ version = "1.0.0"
111111+112112+ def __init__(self, minutes: int):
113113+ self.minutes = minutes
114114+ self._task = None
115115+116116+ def install(self, ctx):
117117+ async def fire():
118118+ await asyncio.sleep(self.minutes * 60)
119119+ await ctx.query("mutation { hardStop }")
120120+ self._task = asyncio.create_task(fire())
121121+122122+ def uninstall(self):
123123+ if self._task:
124124+ self._task.cancel()
125125+126126+await client.use(SleepTimer(30))
127127+```
128128+129129+## REPL-friendly
130130+131131+The SDK is async-first. The recommended REPL is **IPython** — `await`
132132+works at the top level, and you get tab-completion on Pydantic models.
133133+134134+```sh
135135+uv run ipython
136136+```
137137+138138+```python
139139+In [1]: from rockbox_sdk import RockboxClient
140140+In [2]: client = RockboxClient()
141141+In [3]: await client.playback.status()
142142+In [4]: track = await client.playback.current_track()
143143+```
144144+145145+## More
146146+147147+Full reference, type catalogue and plugin examples: see the
148148+[Python SDK README on GitHub ↗](https://github.com/tsirysndr/rockbox-zig/blob/master/sdk/python/README.md).