> ## 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.

# Gleam

> Rockbox DSP, metadata, and playback in Gleam (Erlang target) via an erl_nif shim over the C ABI.

```sh theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
gleam add rockbox_ffi
```

Requires **Gleam ≥ 1.0** on the **Erlang target** and **OTP 27+** (JSON is
decoded with the built-in `json` module plus `gleam/dynamic/decode` — no
`gleam_json` dependency). The NIF links the Rust static archive
(`target/release/librockbox_ffi.a`); build it with `make` (which runs
`cargo build --release -p rockbox-ffi` first if needed) before `gleam test`.

```sh theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
make            # -> priv/rockbox_ffi_nif.so
gleam test
```

## Quick start

```gleam theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
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) ---
// Mutating `player` functions return the `Player`, so setup pipes:
let p =
  player.with_config(player.Config(..player.default_config(), volume: 0.8))
  |> player.set_queue([        // local paths, http(s):// URLs, or live-radio / streaming URLs
    "a.flac",
    "https://example.com/b.mp3",
    "http://stream.example.com/radio",
  ])
  |> player.set_eq_preset(player.BassBoost)   // 21 built-in EqPreset values
  |> player.set_bass(7)                       // tone controls, dB
  |> player.set_treble(4)
  |> player.set_crossfeed(player.Meier, 0, 0, 0, 0)
  |> player.set_shuffle(True)
  |> player.set_repeat(player.All)            // RepeatMode: Off / One / All
  |> player.play

player.status(p)         // Status(state: "playing", index: Some(0), ...)
player.dsp_settings(p)   // DspSettings(equalizer:, tone:, crossfeed:, ...)
```

## API

| Module             | Contents                                                                     |
| ------------------ | ---------------------------------------------------------------------------- |
| `rockbox/metadata` | `read(path) -> Result(Metadata, _)`, `probe(name) -> Option(String)`         |
| `rockbox/decoder`  | decode a file to interleaved-stereo S16LE PCM one chunk at a time            |
| `rockbox/dsp`      | EQ / tone / surround / compressor / ReplayGain, `process(d, pcm)`            |
| `rockbox/player`   | queue + transport + crossfade + ReplayGain + full DSP chain + shuffle/repeat |

### `rockbox/player`

Every mutating function returns the `Player`, so calls chain with `|>`. The
setters below apply the full Rockbox DSP chain live, mid-playback.

| Function                                                              | Purpose                                                                          |
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `new()` / `with_config(Config)`                                       | Create a player (device default, or explicit `Config`)                           |
| `set_queue(paths)` / `enqueue(path)`                                  | Set / append sources — local paths, `http(s)://`, or live-radio / streaming URLs |
| `insert(paths, InsertPosition)` / `queue()`                           | Insert at a position; read the current queue                                     |
| `play` / `pause` / `toggle` / `stop`                                  | Transport                                                                        |
| `next` / `previous` / `skip_to(i)` / `seek_ms(ms)`                    | Navigation within the queue / track                                              |
| `set_volume(f)` / `volume()` / `sample_rate()`                        | Output volume and device sample rate                                             |
| `set_balance(bal)` / `balance()`                                      | Stereo balance, `-100` (full left) … `+100` (full right); `0` = centre           |
| `set_shuffle(Bool)` / `is_shuffle_enabled()`                          | Shuffle playback                                                                 |
| `set_repeat(RepeatMode)` / `repeat()`                                 | Repeat mode — `Off` / `One` / `All`                                              |
| `set_crossfade(...)` / `set_replaygain(...)`                          | Crossfade behaviour; ReplayGain (player mode ints)                               |
| `set_eq_enabled(Bool)` / `is_eq_enabled()`                            | Toggle the graphic equalizer                                                     |
| `set_eq_preset(EqPreset)`                                             | Apply one of 21 built-in presets (`Flat`, `BassBoost`, …)                        |
| `set_eq_band(band, cutoff_hz, q, gain_db)` / `set_eq_precut(db)`      | Per-band EQ; EQ pre-cut headroom                                                 |
| `set_tone(...)` / `set_bass(db)` / `set_treble(db)`                   | Bass/treble tone controls                                                        |
| `set_bass_cutoff(hz)` / `set_treble_cutoff(hz)`                       | Tone cutoff frequencies                                                          |
| `set_crossfeed(CrossfeedMode, ...)`                                   | Headphone crossfeed — `CrossfeedOff` / `Meier` / `CrossfeedCustom`               |
| `set_surround(...)`                                                   | Surround effect                                                                  |
| `set_channel_mode(ChannelMode)` / `set_stereo_width(pct)`             | Channel mixing (`Stereo`, …); stereo width                                       |
| `set_bass_enhancement(strength, precut)` / `set_fatigue_reduction(s)` | Bass enhancement; listening-fatigue reduction                                    |
| `set_compressor(...)` / `set_dither(Bool)` / `set_pitch(ratio)`       | Compressor; dithering; pitch                                                     |
| `dsp_settings()`                                                      | Read back the whole chain as a typed `DspSettings` record                        |
| `status()`                                                            | A `Status` snapshot                                                              |
| `resume` / `save_resume` / `clear_resume` / `load_resume(path)`       | Queue + exact-position persistence                                               |
| `import_m3u` / `load_m3u` / `export_m3u` / `m3u_read` / `m3u_write`   | `.m3u` / `.m3u8` playlist I/O                                                    |

`DspSettings` is a typed record whose sections include `equalizer`, `tone`,
`surround`, `channel_mode`, `stereo_width`, `compressor`, `dither`, `pitch`,
`crossfeed`, `bass_enhancement`, and `fatigue_reduction`.

<Tip>
  These knobs map 1:1 to Rockbox's own Sound Settings. See the official
  [Rockbox manual](https://download.rockbox.org/daily/manual/rockbox-ipodvideo/rockbox-buildch6.html)
  for what each control does and its value ranges.
</Tip>

* `Dsp` and `Player` are opaque NIF resources, freed by the BEAM garbage
  collector — no explicit close.
* PCM crosses the boundary as a `BitArray`.

<Warning>
  The DSP and player use **different mode integers**: `dsp.set_replaygain` →
  `0` track, `1` album, `2` shuffle, `3` off; `player.set_replaygain` → `0`
  off, `1` track, `2` album. See
  [the overview](/bindings/overview#the-one-thing-to-know-two-replaygain-encodings).
</Warning>

### `rockbox/decoder`

Decode an audio file to interleaved-stereo **S16LE** PCM (a `BitArray`), one
chunk at a time, straight through the Rockbox codec engine — no output device
needed.

| Function                                          | Purpose                                                                        |
| ------------------------------------------------- | ------------------------------------------------------------------------------ |
| `open(path) -> Decoder`                           | Open a decoder for a local audio file                                          |
| `metadata(decoder) -> Metadata`                   | Tags + stream properties (same shape as `metadata.read`)                       |
| `next_chunk(decoder) -> Option(#(BitArray, Int))` | Next PCM buffer + sample rate; `None` at end of track                          |
| `seek_ms(decoder, ms)` / `elapsed_ms(decoder)`    | Request a seek; read the codec's last-reported position                        |
| `finished(decoder) -> #(Bool, Int)`               | `#(done, code)` — `code` `0` = clean end, negative = codec error (when `done`) |

**Decode a file to PCM**

```gleam theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
import rockbox/decoder
import gleam/option.{None, Some}

let d = decoder.open("song.flac")
decoder.metadata(d).duration_ms      // 122324

// Pull chunks until the track ends.
fn drain(d) {
  case decoder.next_chunk(d) {
    Some(#(pcm, rate)) -> {
      // `pcm` is interleaved-stereo little-endian int16 at `rate` Hz.
      drain(d)
    }
    None -> Nil
  }
}
drain(d)

let #(done, code) = decoder.finished(d)   // #(True, 0) on a clean end
```

* `Decoder` is an opaque NIF resource, freed by the BEAM garbage collector —
  no explicit close.

<Warning>
  Codec state is **process-wide — only one `Decoder` decodes at a time.**
  Opening a second decoder blocks until the first has been garbage-collected.
</Warning>

<Note>
  The Gleam and Elixir bindings share the exact same
  `rockbox_ffi_nif.{c,erl}` NIF, vendored into each project.
</Note>
