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

# Clojure

> Rockbox DSP, metadata, and playback on the JVM via the Java Foreign Function & Memory API — no JNI.

Clojure bindings with **no JNI and no native glue to compile**: they call the
Java **Foreign Function & Memory API** (JEP 454, stable since **JDK 22**)
through interop to locate `librockbox_ffi` at runtime and bind every function
to a `MethodHandle` downcall.

```clojure theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
;; deps.edn
io.github.tsirysndr/rockbox-clj-ffi {:mvn/version "0.1.1"}
```

Requires a **JDK 22+** and the Clojure CLI. The published jar bundles a
prebuilt `librockbox_ffi` for every OS/arch and extracts the matching one at
load time — no Rust toolchain needed. `ROCKBOX_FFI_LIB` overrides; a repo
checkout falls back to `target/release`.

## Quick start

```clojure theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
(require '[rockbox.ffi.metadata :as metadata]
         '[rockbox.ffi.dsp :as dsp]
         '[rockbox.ffi.player :as player])

;; metadata -> map with keyword keys
(metadata/read "/music/song.flac")   ; => {:title "…" :codec "FLAC" …}
(metadata/probe "song.flac")         ; => "FLAC"

;; DSP (interleaved stereo int16 short-array)
(dsp/with-dsp [d 44100]
  (dsp/eq-enable d true)
  (dsp/set-eq-band d 0 100 0.7 3.0)
  (dsp/process d samples))           ; => short-array

;; Player (queue + transport + DSP)
(player/with-player [p {:volume 0.8}]
  ;; Mutating fns return the handle, so setup threads cleanly with `->`.
  (-> p
      ;; queue entries: local paths, http(s):// URLs, or live-radio / stream URLs
      (player/set-queue ["/music/a.flac"
                         "https://example.com/b.mp3"
                         "http://stream.example.com/live"])
      (player/set-eq-enabled true)
      (player/set-eq-preset :bass-boost)   ; one of 21 built-in presets
      (player/set-shuffle true)
      (player/set-repeat :all)
      (player/play))
  (:state (player/status p)))
```

## Player API

Every mutating player fn takes the handle `p` first and **returns `p`**, so a
full session — queue, DSP chain, shuffle/repeat, transport — threads with `->`.
Getters/queries (`volume`, `status`, `queue`, `dsp-settings`, `shuffle-enabled?`,
`repeat`, `eq-enabled?`, …) return their values as usual.

| Namespace                | Fns                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rockbox.ffi.metadata`   | `read`, `probe`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `rockbox.ffi.dsp`        | `with-dsp`, `eq-enable`, `set-eq-band`, `set-replaygain`, `process`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `rockbox.ffi.player`     | **lifecycle** `new-player`, `with-player`, `free` · **queue** `set-queue`, `enqueue`, `insert`, `queue` · **transport** `play`, `pause`, `toggle`, `stop`, `next`, `previous`, `skip-to`, `seek-ms` · **settings** `set-volume`, `volume`, `set-balance`, `balance`, `sample-rate`, `set-crossfade`, `set-replaygain` · **shuffle/repeat** `set-shuffle`, `shuffle-enabled?`, `set-repeat`, `repeat` · **status** `status` · **resume** `resume`, `save-resume`, `clear-resume`, `load-resume` · **playlists** `import-m3u`, `load-m3u`, `export-m3u`, `m3u-read`, `m3u-write`, `is-url?` |
| `rockbox.ffi.decoder`    | `open`, `with-decoder`, `free`, `metadata`, `next-chunk`, `seek-ms`, `elapsed-ms`, `finished`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `rockbox.ffi.player` DSP | `set-eq-enabled`, `eq-enabled?`, `set-eq-preset`, `set-eq-band`, `set-eq-precut`, `set-tone`, `set-bass`, `set-treble`, `set-bass-cutoff`, `set-treble-cutoff`, `set-crossfeed`, `set-surround`, `set-channel-mode`, `set-stereo-width`, `set-bass-enhancement`, `set-fatigue-reduction`, `set-compressor`, `set-dither`, `set-pitch`, `dsp-settings`                                                                                                                                                                                                                                     |

### Sources

`set-queue`, `enqueue`, and `insert` accept **local file paths**, finite
**`http(s)://` URLs**, and **live-radio / streaming URLs** in the same list —
mix them freely. `dsp-settings` returns a keywordized snapshot of the whole
chain.

### Stereo balance

`set-balance` / `balance` pan the output between the channels: the value ranges
from **-100 (full left)** to **+100 (full right)**, with **0 = centre**.

```clojure theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
(-> p (player/set-balance -30))   ; shift 30 % toward the left channel
(player/balance p)                ; => -30
```

### Enums

Every enum arg accepts either the keyword or the raw int (`rockbox.ffi.enums`):

| Map               | Keywords                                                                                                                                                                                                                                               |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `eq-preset`       | `:flat` `:acoustic` `:bass-boost` `:bass-reducer` `:classical` `:dance` `:deep` `:electronic` `:hip-hop` `:jazz` `:latin` `:loudness` `:lounge` `:piano` `:pop` `:rnb` `:rock` `:small-speakers` `:treble-boost` `:treble-reducer` `:vocal-boost` (21) |
| `crossfeed-mode`  | `:off` `:meier` `:custom`                                                                                                                                                                                                                              |
| `channel-mode`    | `:stereo` `:mono` `:custom` `:mono-left` `:mono-right` `:karaoke` `:swap`                                                                                                                                                                              |
| `repeat-mode`     | `:off` `:one` `:all`                                                                                                                                                                                                                                   |
| `crossfade-mode`  | `:off` `:auto-skip` `:manual-skip` `:shuffle` `:shuffle-or-manual` `:always`                                                                                                                                                                           |
| `replaygain-mode` | `:off` `:track` `:album` (player) — see the ReplayGain warning below                                                                                                                                                                                   |

<Tip>
  The DSP chain mirrors Rockbox's own sound settings. For what each control does
  (EQ bands, crossfeed, tone/bass/treble, compressor, dithering, pitch, …) see
  the official
  [Rockbox manual — Sound Settings](https://download.rockbox.org/daily/manual/rockbox-ipodvideo/rockbox-buildch6.html).
</Tip>

## Decoder API

`rockbox.ffi.decoder` streams an audio file to **interleaved-stereo S16LE PCM**
one chunk at a time through the Rockbox codec engine — useful for feeding your
own output sink, resampler, or analysis pipeline.

* `open` takes a path and returns a handle; `free` (or `with-decoder`) releases
  it when you're done.
* `metadata` returns the tags + stream properties as a keyword-keyed map (same
  shape as `metadata/read`).
* `next-chunk` yields `[samples sample-rate]` — `samples` is an interleaved
  stereo `short-array` — or **nil at end of track**.
* `seek-ms` requests a seek; `elapsed-ms` is the codec's last reported position.
* `finished` returns `[done code]`: `code` is `0` on a clean end and **negative
  on a codec error** (valid only once `done` is true).

```clojure theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
(require '[rockbox.ffi.decoder :as decoder])

(decoder/with-decoder [d "/music/song.flac"]
  (:title (decoder/metadata d))              ; => "…"
  (loop [total 0]
    (if-let [[samples rate] (decoder/next-chunk d)]
      (do
        ;; samples: interleaved stereo short-array at `rate` Hz — write it out
        (recur (+ total (alength samples))))
      (let [[done code] (decoder/finished d)]
        (when (neg? code) (throw (ex-info "codec error" {:code code})))
        total))))                            ; => total samples decoded
```

<Warning>
  Codec state is **process-wide — only one decoder decodes at a time**. Opening
  a second decoder blocks until the first is freed, so wrap each in
  `with-decoder` (or `free` it) before starting the next.
</Warning>

## Notes

* Rich values (metadata, player status, `dsp-settings`) come back as maps with
  **keyword keys**, parsed with `clojure.data.json`.
* Native memory is freed automatically — `with-dsp` / `with-player` free their
  handle, and every `char*` / `int16*` the ABI returns is freed inside the
  binding.
* Enum arguments accept either a keyword or the raw int (see
  `src/rockbox/ffi/enums.clj`).

<Warning>
  **Two ReplayGain encodings** — `dsp-replaygain-mode` (`:track` 0, `:album` 1,
  `:shuffle` 2, `:off` 3) for `rockbox.ffi.dsp`, `replaygain-mode` (`:off` 0,
  `:track` 1, `:album` 2) for `rockbox.ffi.player`. See
  [the overview](/bindings/overview#the-one-thing-to-know-two-replaygain-encodings).
</Warning>

<Note>
  The FFM downcalls need `--enable-native-access=ALL-UNNAMED`, wired into the
  project's `deps.edn` aliases. This binding lives under `rockbox.ffi.*` and
  coexists cleanly with the separate `rockbox-clj` gRPC SDK (which owns the bare
  `rockbox.*` namespaces).
</Note>
