rockbox-ffi (Python) #
Python bindings for the Rockbox DSP, metadata, codecs, and
playback engine, via cffi (ABI mode) over the prebuilt
librockbox_ffi shared library.
📖 Sound settings reference — the equalizer, tone, crossfeed, compressor and other DSP controls mirror Rockbox's own. See the official Rockbox manual — Sound Settings.
Setup #
Build the shared library once (from the repo root):
cargo build --release -p rockbox-ffi
Then install the Python package with uv:
cd bindings/python
uv venv
uv pip install -e .
uv run python examples/smoke.py
The library is located automatically by walking up to
target/release/librockbox_ffi.{dylib,so}. Override with the
ROCKBOX_FFI_LIB environment variable.
Interactive console #
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 (falls back to the plain REPL without
IPython):
metadata.read(str(FIXTURE))["title"] # 'Speak'
p = Player(volume=0.6)
p.set_queue([str(FIXTURE)]); p.play()
p.status()["state"] # 'playing'
Usage #
import rockbox_ffi as rb
from rockbox_ffi import Dsp, Player, metadata
from rockbox_ffi.enums import DspReplayGainMode, ReplayGainMode, CrossfadeMode
# --- metadata ---------------------------------------------------------
meta = metadata.read("song.flac")
print(meta["artist"], "—", meta["title"], meta["duration_ms"], "ms")
print(metadata.probe("track.opus")) # -> "Opus"
# --- DSP (interleaved stereo int16) -----------------------------------
with Dsp(44100) 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) # halves amplitude
processed = dsp.process(samples) # array('h')
# --- codecs (decode a file to PCM, one chunk at a time) ---------------
from rockbox_ffi import Decoder
with Decoder("song.flac") as dec:
print(dec.metadata()["title"]) # tags from the open file
for samples, sample_rate in dec.chunks(): # array('h'), Hz
... # interleaved-stereo int16 PCM
print(dec.finished()) # (True, 0) (0 = clean end)
# --- playback (needs an output device) --------------------------------
with Player(volume=0.8) as player:
player.set_replaygain(ReplayGainMode.TRACK, preamp_db=0.0, prevent_clipping=True)
player.set_crossfade(CrossfadeMode.ALWAYS)
# Queue entries may be local files, http(s):// URLs to remote media,
# or live-radio streams, or HLS / MPEG-DASH manifests (.m3u8/.mpd) — mix and match freely.
player.set_queue(["a.flac", "https://example.com/b.mp3", "http://radio.example/stream", "https://cdn.example.com/live/main.m3u8"])
player.play()
print(player.status()) # {'state': 'playing', 'index': 0, ...}
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 |
codec engine: metadata(), chunks() / next_chunk(), seek_ms(), finished() |
rockbox_ffi.Player |
queue + transport + crossfade + ReplayGain, status() -> dict |
rockbox_ffi.enums |
DspReplayGainMode, ReplayGainMode, CrossfadeMode, MixMode, … |
Two ReplayGain encodings #
The DSP and player use different mode integers (a quirk of the C ABI):
Dsp.set_replaygain→DspReplayGainMode(TRACK=0, ALBUM=1, SHUFFLE=2, OFF=3)Player.set_replaygain→ReplayGainMode(OFF=0, TRACK=1, ALBUM=2)
Use the named enums and you won't have to remember which is which.
Memory #
All heap allocations crossing the FFI boundary (JSON strings, sample
buffers) are freed inside the wrappers — you never call a *_free yourself.