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

# Swift

> Rockbox DSP, metadata, and playback in Swift — statically bundled or dlopen'd, macOS + iOS.

The Swift package ships the engine two ways, from one shared implementation
(`RockboxFFICore` — the public API plus a `dlsym` loader). Pick the product that
fits how you want to distribute:

| Product             | Native code       | Runtime lookup | Use when                                      |
| ------------------- | ----------------- | -------------- | --------------------------------------------- |
| `RockboxFFI`        | statically linked | none           | you want a single self-contained binary       |
| `RockboxFFIDynamic` | `dlopen`ed        | file lookup    | you'd rather ship the library beside your app |

Both drive the same typed `@convention(c)` closures; the loader resolves the
`rb_*` symbols from the process image (static) or from a `dlopen`ed library
(dynamic — the same approach as the Ruby and Python bindings), decided at
runtime.

Build the native artifacts once from the repo root — this produces **both**
`librockbox_ffi.a` (bundled by `RockboxFFI`) and `librockbox_ffi.dylib` (loaded
by `RockboxFFIDynamic`, which honours `ROCKBOX_FFI_LIB`, else walks up to
`target/release`):

```sh theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
cargo build --release -p rockbox-ffi
```

```sh theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
swift run rockbox-ffi-smoke                 # static product, no runtime lookup
swift run rockbox-ffi-play /path/to/audio   # dynamic product, plays via output device
```

## Distribution

The `bindings-release` workflow publishes reproducible, prebuilt Swift artifacts
to each GitHub Release:

<CardGroup cols={2}>
  <Card title="RockboxFFI.xcframework.zip" icon="apple">
    Static library + header for **macOS** (universal), **iOS** (arm64 device),
    and the **iOS simulator** (arm64 + x86\_64). Drop it into Xcode or reference
    it as a SwiftPM `binaryTarget`.
  </Card>

  <Card title="rockbox-ffi-swift-macos.tar.gz" icon="box">
    The SwiftPM package with the universal macOS archive vendored under `Libs/`,
    so `swift build` works turnkey off the monorepo (macOS only; iOS consumers
    use the xcframework).
  </Card>
</CardGroup>

<Note>
  The static `RockboxFFI` product force-references each `rb_*` entry point with a
  `-u <symbol>` linker flag (it reaches them via `dlsym`, so `-dead_strip` would
  otherwise drop them). If you consume the xcframework through this loader on
  iOS, keep those flags. iOS apps must also link `AudioToolbox` / `AVFoundation`
  and set up an `AVAudioSession` before using `Player`.
</Note>

## Quick start

```swift theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
import RockboxFFI

// metadata
let meta = try Metadata.read("/music/song.flac")   // [String: Any]
Metadata.probe("song.flac")                         // "FLAC"

// DSP (interleaved stereo int16)
let dsp = try Dsp(sampleRate: 44_100)
defer { dsp.close() }
dsp.eqEnable(true)
dsp.setEqBand(0, cutoffHz: 100, q: 0.7, gainDb: 3.0)
let out = try dsp.process(samples)

// Player (queue + transport) — mutating setters are @discardableResult and
// return Self, so they chain fluently.
var cfg = Player.Config(); cfg.volume = 0.8
let player = try Player(config: cfg)
try player
    .setQueue(["/music/a.flac", "https://example.com/b.mp3"])  // files, http(s), live streams
    .setEqEnabled(true)
    .setEqPreset(.bassBoost)
    .setShuffle(true)
    .setRepeat(.all)
    .play()
```

`setQueue` / `enqueue` / `insert` accept **local file paths**, finite
`http(s)://` URLs, and **live-radio / streaming URLs** interchangeably.

## Player DSP chain

The `Player` exposes the full Rockbox DSP pipeline. Every setter is
`@discardableResult` and returns `Self`, so they compose in one fluent chain
(the statement-style calls still compile unchanged). Read the live state back
with `dspSettings()` (a `[String: Any]` snapshot).

```swift theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
try player
    .setEqEnabled(true)
    .setEqPreset(.rock)                       // 21-case EqPreset (flat … vocalBoost)
    .setEqBand(0, cutoffHz: 100, q: 0.7, gainDb: 3.0)
    .setEqPrecut(2.0)
    .setBass(4).setTreble(2)                  // or setTone(bassDb:trebleDb:bassCutoffHz:trebleCutoffHz:)
    .setCrossfeed(.meier, directGain: 0, crossGain: 0, hfGain: 0, hfCutoff: 0)
    .setChannelMode(.stereo).setStereoWidth(120)
    .setBassEnhancement(strength: 50, precut: 0)
    .setFatigueReduction(30)

let dsp = try player.dspSettings()            // [String: Any] snapshot
```

### Player API

| Group          | Methods                                                                                                                                                                    |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Queue          | `setQueue(_:)`, `enqueue(_:)`, `insert(_:position:index:)`, `queue()`                                                                                                      |
| Transport      | `play()`, `pause()`, `toggle()`, `stop()`, `next()`, `previous()`, `skip(to:)`, `seek(ms:)`                                                                                |
| Settings       | `setVolume(_:)`, `volume`, `setBalance(_:)`, `balance`, `sampleRate`, `setCrossfade(_:…)`, `setReplaygain(_:preampDb:preventClipping:)`                                    |
| Shuffle/repeat | `setShuffle(_:)`, `isShuffleEnabled()`, `setRepeat(_:)`, `` `repeat`() ``                                                                                                  |
| EQ             | `setEqEnabled(_:)`, `isEqEnabled()`, `setEqPreset(_:)`, `setEqBand(_:cutoffHz:q:gainDb:)`, `setEqPrecut(_:)`                                                               |
| Tone           | `setTone(bassDb:trebleDb:bassCutoffHz:trebleCutoffHz:)`, `setBass(_:)`, `setTreble(_:)`, `setBassCutoff(_:)`, `setTrebleCutoff(_:)`                                        |
| Spatial        | `setCrossfeed(_:directGain:crossGain:hfGain:hfCutoff:)`, `setSurround(delayMs:balance:cutoffLowHz:cutoffHighHz:)`, `setChannelMode(_:)`, `setStereoWidth(_:)`              |
| Enhancement    | `setBassEnhancement(strength:precut:)`, `setFatigueReduction(_:)`, `setCompressor(thresholdDb:makeupGain:ratio:knee:attackMs:releaseMs:)`, `setDither(_:)`, `setPitch(_:)` |
| Status/DSP     | `status()`, `dspSettings()`                                                                                                                                                |
| Resume / m3u   | `resume()`, `saveResume()`, `clearResume()`, `importM3u(_:position:index:)`, `loadM3u(_:)`, `exportM3u(_:)`                                                                |

`setBalance(_:)` / `balance` take a stereo balance of **-100 (full left) to
+100 (full right)**, with **0 = centre**.

Relevant enums: `RepeatMode` (`off`/`one`/`all`), `EqPreset` (21 cases,
`flat` … `vocalBoost`), `CrossfeedMode` (`off`/`meier`/`custom`), `ChannelMode`
(`stereo`/`mono`/`custom`/`monoLeft`/`monoRight`/`karaoke`/`swap`),
`CrossfadeMode`, and `MixMode`.

<Tip>
  The DSP controls mirror Rockbox's own Sound Settings. For what each parameter
  does, see the [official Rockbox manual](https://download.rockbox.org/daily/manual/rockbox-ipodvideo/rockbox-buildch6.html).
</Tip>

## Decoder

`Decoder` is a streaming decoder: it decodes one local audio file to
interleaved-stereo **S16LE PCM**, one chunk at a time, through the Rockbox codec
engine. Open it with a path; the native handle is freed on `close()` or
`deinit`.

| Member        | Returns                  | Notes                                                    |
| ------------- | ------------------------ | -------------------------------------------------------- |
| `init(path:)` | —                        | opens the file for decoding; throws if the codec can't   |
| `metadata()`  | `[String: Any]`          | tags + stream properties (same shape as `Metadata.read`) |
| `nextChunk()` | `(samples, sampleRate)?` | interleaved-stereo int16; `nil` at end of track          |
| `seek(ms:)`   | —                        | request a seek to `ms` milliseconds                      |
| `elapsedMs`   | `UInt64`                 | position last reported by the codec, in ms               |
| `finished()`  | `(done, code)`           | `code` valid when `done`: `0` = clean end, `< 0` = error |
| `close()`     | —                        | frees the handle; safe to call more than once            |

```swift theme={"theme":{"light":"catppuccin-latte","dark":"min-dark"}}
import RockboxFFI

let decoder = try Decoder(path: "/music/song.flac")
defer { decoder.close() }

let tags = try decoder.metadata()            // [String: Any]

while let (samples, sampleRate) = decoder.nextChunk() {
    // samples: [Int16] interleaved L,R,L,R…  — feed to your output / encoder
    _ = (samples, sampleRate)
}

let (done, code) = decoder.finished()        // code 0 = clean end, negative = codec error
```

<Warning>
  The Rockbox codec state is **process-wide — only one `Decoder` decodes at a
  time**. Constructing a second `Decoder` blocks until the first is freed
  (via `close()` or `deinit`).
</Warning>

## Notes

* Rich values (metadata, player status) come back as `[String: Any]`, parsed
  from the ABI's JSON with `JSONSerialization`.
* Native memory is freed inside the binding — `close()` / `deinit` for handles;
  every `char*` / `int16*` return is freed after copying.

<Warning>
  **Two ReplayGain encodings** — use `DspReplayGainMode` (track 0, album 1,
  shuffle 2, off 3) for `Dsp`, and `ReplayGainMode` (off 0, track 1, album 2)
  for `Player`. See [the overview](/bindings/overview#the-one-thing-to-know-two-replaygain-encodings).
</Warning>

<Note>
  Keep `Sources/RockboxFFICore/Loader.swift` — and the `ffiSymbols` list in
  `Package.swift` — in sync with `include/rockbox_ffi.h`. The loader declares
  each function signature by hand, and the static product force-links each
  symbol by name.
</Note>
