> ## Documentation Index
> Fetch the complete documentation index at: https://rockboxzig.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Python

> Rockbox DSP, metadata, and playback in Python via cffi over the librockbox_ffi C ABI.

```sh theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
pip install rockbox-ffi
# or
uv add rockbox-ffi
```

Requires **Python 3.9+**. The wheel bundles a prebuilt `librockbox_ffi` for
your platform — no Rust toolchain needed. From a repo checkout, the loader
walks up to `target/release/`; override with `ROCKBOX_FFI_LIB`.

## Quick start

```python theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
import rockbox_ffi as rb
from rockbox_ffi import Dsp, Player, metadata
from rockbox_ffi.enums import (
    DspReplayGainMode, ReplayGainMode, CrossfadeMode,
    EqPreset, RepeatMode, CrossfeedMode,
)

# --- metadata ---------------------------------------------------------
meta = metadata.read("song.flac")
print(meta["artist"], "—", meta["title"], meta["duration_ms"], "ms")
print(metadata.probe("track.opus"))          # -> "Opus"  (no I/O, extension guess)

# --- DSP: process interleaved stereo int16 ----------------------------
with Dsp(44_100) as dsp:
    dsp.eq_enable(True)
    dsp.set_eq_band(0, cutoff_hz=60, q=0.7, gain_db=3.0)
    dsp.set_replaygain(DspReplayGainMode.TRACK, noclip=True, preamp_db=0.0)
    dsp.set_replaygain_gains(track_gain_db=-6.02)   # −6 dB ≈ half amplitude
    processed = dsp.process(samples)                # array('h')

# --- playback (needs an output device) --------------------------------
# Setters return None — call them plainly, one per line (no chaining).
with Player(volume=0.8) as player:
    player.set_replaygain(ReplayGainMode.TRACK, preamp_db=0.0, prevent_clipping=True)
    player.set_crossfade(CrossfadeMode.ALWAYS)

    # --- live DSP chain (mirrored back by player.dsp_settings()) ------
    player.set_eq_enabled(True)
    player.set_eq_preset(EqPreset.BASS_BOOST)        # 21 presets: Flat, Rock, Jazz, …
    player.set_bass(4)                                # tone: bass/treble in dB
    player.set_treble(2)
    player.set_crossfeed(CrossfeedMode.MEIER, 0, -60, -80, 700)
    player.set_bass_enhancement(strength=30, precut=-30)   # PBE
    print(player.dsp_settings())                     # -> dict of the whole chain

    # --- shuffle & repeat --------------------------------------------
    player.set_shuffle(True)
    player.set_repeat(RepeatMode.ALL)

    # --- sources: local paths, http(s):// files, live-radio URLs ------
    player.set_queue([
        "a.flac",
        "https://example.com/b.mp3",
        "https://stream.example.com/radio",          # live stream
    ])
    player.play()
    print(player.status())     # {'state': 'playing', 'index': 0, ...}
```

## Decode a file to PCM

`Decoder` streams an audio file to interleaved-stereo S16LE PCM one chunk at a
time through the Rockbox codec engine — the same decoders that power playback,
without an output device.

```python theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
from rockbox_ffi import Decoder

with Decoder("song.flac") as dec:
    print(dec.metadata()["title"])               # same shape as metadata.read
    for samples, rate in dec.chunks():            # array('h'), Hz
        ...                                        # feed samples somewhere
    print(dec.finished())                          # (True, 0) — clean end
    # dec.seek_ms(30_000); dec.elapsed_ms()       # seek / query position
```

`chunks()` (or `next_chunk()`) yields `(samples, sample_rate)` and stops at
`None` on end of track; `finished()` returns `(done, code)` where `code` is `0`
for a clean end and negative for a codec error.

<Warning>
  **One decoder at a time.** The codec state is process-wide (global) — only one
  `Decoder` can decode at once. Constructing a second one *blocks* until the
  first is closed. Always use `with Decoder(...)` (or call `close()`) so the
  native resource is freed; a GC finalizer is the backstop.
</Warning>

## API

| Module                 | Contents                                                                                                                                                                                                    |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rockbox_ffi.metadata` | `read(path) -> dict`, `probe(filename) -> str \| None`                                                                                                                                                      |
| `rockbox_ffi.Dsp`      | EQ / tone / surround / compressor / ReplayGain, `process(samples)`                                                                                                                                          |
| `rockbox_ffi.Decoder`  | streaming file → S16LE PCM via the codec engine: `metadata()`, `next_chunk()` / `chunks()`, `seek_ms(ms)`, `elapsed_ms()`, `finished() -> (done, code)` (one decoder at a time — codec state is global)     |
| `rockbox_ffi.Player`   | queue + transport + crossfade + ReplayGain + shuffle/repeat + full live DSP chain (10-band EQ, tone, crossfeed, surround, PBE/AFR, compressor, dither, pitch), `dsp_settings() -> dict`, `status() -> dict` |
| `rockbox_ffi.enums`    | `DspReplayGainMode`, `ReplayGainMode`, `CrossfadeMode`, `MixMode`, `RepeatMode`, `EqPreset`, `ChannelMode`, `CrossfeedMode`, `InsertPosition`, …                                                            |

* **Handles** are context managers — `with Dsp(...) as dsp:` frees the native
  resource on exit. A GC finalizer is the backstop.
* **Rich values** (metadata, status) come back as plain `dict`s.
* **Sample buffers** are `array('h')` of interleaved-stereo signed 16-bit
  samples.

<Note>
  **Player DSP chain.** Every setter applies live and is reflected back by
  `player.dsp_settings()`. The full surface:
  **EQ** — `set_eq_enabled` / `is_eq_enabled`, `set_eq_preset(EqPreset)`
  (21 presets: `FLAT`, `ROCK`, `JAZZ`, `BASS_BOOST`, …), `set_eq_band`,
  `set_eq_precut`. **Tone** — `set_tone`, `set_bass`, `set_treble`,
  `set_bass_cutoff`, `set_treble_cutoff`. **Spatial** —
  `set_crossfeed(CrossfeedMode)`, `set_surround`, `set_channel_mode(ChannelMode)`,
  `set_stereo_width`, `set_balance(balance)` / `balance()` (stereo balance,
  `-100` full left … `+100` full right, `0` = centre). **Enhancers** — `set_bass_enhancement` (PBE),
  `set_fatigue_reduction` (AFR), `set_compressor`, `set_dither`, `set_pitch`.
  **Shuffle / repeat** — `set_shuffle` / `is_shuffle_enabled`,
  `set_repeat(RepeatMode)` / `repeat()`.

  For what each parameter does (and sensible ranges), see the official Rockbox
  manual: <a href="https://download.rockbox.org/daily/manual/rockbox-ipodvideo/rockbox-buildch6.html">Sound Settings</a>.
</Note>

<Warning>
  `Dsp.set_replaygain` and `Player.set_replaygain` take **different mode
  integers**. Always pass the named enums — `DspReplayGainMode` for the DSP,
  `ReplayGainMode` for the player. See [the overview](/bindings/overview#the-one-thing-to-know-two-replaygain-encodings).
</Warning>

## Interactive console

```sh theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
uv pip install -e '.[dev]'      # installs IPython
uv run python console.py
```

Drops into IPython with `rb`, `metadata`, `Dsp`, `Player`, the enums, and a
`FIXTURE` sample track preloaded:

```python theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
metadata.read(str(FIXTURE))["title"]        # 'Speak'
p = Player(volume=0.6)
p.set_queue([str(FIXTURE)]); p.play()
p.status()["state"]                          # 'playing'
```
