rockbox_ffi (Gleam)

Package Version Hex Docs Gleam Erlang/OTP NIF License

Gleam bindings for the Rockbox DSP, metadata, and playback engine (Erlang target), via an erl_nif shim over the librockbox_ffi C ABI.

📖 Sound settings reference — the equalizer, tone, crossfeed, compressor and other DSP controls mirror Rockbox’s own. See the official Rockbox manual — Sound Settings.

Setup

gleam add rockbox_ffi

Requires OTP 27+ (JSON is decoded with the built-in json module plus gleam/dynamic/decode — no gleam_json dependency).

How the native code is delivered

Gleam has no compile step at gleam add time, so the package ships prebuilt NIF binaries — one .so per platform under priv/, named rockbox_ffi_nif-<target>.so. The Erlang loader (src/rockbox_ffi_nif.erl) detects the host triple at load time and loads the matching one (falling back to an unsuffixed rockbox_ffi_nif.so from a local make build).

Prebuilt targets:

TargetTier
aarch64-apple-darwinsupported
x86_64-apple-darwinsupported
x86_64-linux-gnusupported
aarch64-linux-gnusupported
x86_64-unknown-freebsdbest-effort
x86_64-unknown-netbsdbest-effort

On any other platform (musl/Alpine, Windows, or a glibc older than the CI runner’s) load_nif will fail — build from source instead.

Building from source

A from-source build needs the full monorepo checkout (the Cargo workspace and include/ header are not in the Hex package):

cd bindings/gleam
make            # -> priv/rockbox_ffi_nif.so  (Gleam copies priv/ into its build)
gleam test

Usage

import rockbox/metadata
import rockbox/dsp
import rockbox/player
import gleam/option.{None, Some}

// --- metadata ---
let assert Ok(meta) = metadata.read("song.flac")
meta.artist        // "…"
meta.duration_ms   // 122324
metadata.probe("track.opus")   // Some("Opus")

// --- DSP (interleaved stereo int16 BitArray) ---
let d = dsp.new(44_100)
dsp.eq_enable(d, True)
dsp.set_eq_band(d, 0, 60, 0.7, 3.0)
dsp.set_replaygain(d, 0, True, 0.0)                 // 0 = track (DSP-native)
dsp.set_replaygain_gains(d, Some(-6.02), None, None, None)
let out = dsp.process(d, pcm)                        // BitArray in/out

// --- playback (needs an output device) ---
let p = player.with_config(player.Config(..player.default_config(), volume: 0.8))
player.set_replaygain(p, 1, 0.0, True)              // 1 = track (player)
player.set_queue(p, ["a.flac", "b.mp3"])
player.play(p)
player.status(p)   // Status(state: "playing", index: Some(0), ...)

Dsp and Player are opaque NIF resources, freed by the BEAM garbage collector — no explicit close.

Two ReplayGain encodings

The DSP and player use different mode integers (a quirk of the C ABI):

Shared NIF

c_src/rockbox_ffi_nif.c and src/rockbox_ffi_nif.erl are shared verbatim with the Elixir binding (bindings/elixir/).

Releasing (maintainers)

Releasing is two steps: CI builds the binaries, then you publish from your machine (gleam publish needs an interactive Hex login that can’t run in CI).

  1. Bump version in gleam.toml (Hex versions are immutable).

  2. Run the bindings-gleam-release.yml workflow — from the Actions tab (workflow_dispatch) or by pushing a gleam-v<version> tag. It builds the NIF .so on one native runner per target and uploads them to a GitHub release whose tag (gleam-v<version>) is auto-created from gleam.toml (override with the tag input).

  3. Publish locally once CI finishes:

    gleam hex authenticate            # or: export HEXPM_API_KEY=...
    bindings/scripts/publish-gleam.sh # downloads the .so files into priv/, then gleam publish
    

    The script downloads every arch’s .so from the gleam-v<version> release into priv/ so gleam publish bundles the whole multi-arch fatball. Pass --tag / --repo to override, or --dry-run to preview.

Nothing prebuilt is committed to git (.gitignore keeps priv/*.so out); the binaries are produced fresh by CI for each release.

Search Document