rockbox-ffi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,243 @@
1
+ # rockbox-ffi (TypeScript)
2
+
3
+ [![npm](https://img.shields.io/npm/v/rockbox-ffi?logo=npm&logoColor=white)](https://www.npmjs.com/package/rockbox-ffi)
4
+ ![TypeScript](https://img.shields.io/badge/TypeScript-5.7-3178C6?logo=typescript&logoColor=white)
5
+ ![Bun](https://img.shields.io/badge/Bun-ready-000000?logo=bun&logoColor=white)
6
+ ![Deno](https://img.shields.io/badge/Deno-ready-000000?logo=deno&logoColor=white)
7
+ ![Node.js](https://img.shields.io/badge/Node.js-ready-5FA04E?logo=nodedotjs&logoColor=white)
8
+ ![License](https://img.shields.io/badge/license-GPL--2.0--or--later-blue)
9
+
10
+ TypeScript bindings for the [Rockbox](https://www.rockbox.org) audio engine —
11
+ **metadata** parsing (40+ formats), the **DSP** pipeline (EQ, tone, surround,
12
+ compressor, ReplayGain, resampler), and a queue-based **player** with
13
+ crossfade. One typed API, three runtimes: **Bun**, **Deno**, and **Node.js**.
14
+
15
+ ```ts
16
+ import { metadata, Dsp } from "rockbox-ffi/bun";
17
+
18
+ const meta = metadata.read("song.flac");
19
+ console.log(`${meta.artist} — ${meta.title} (${meta.duration_ms} ms)`);
20
+ ```
21
+
22
+ ## Install
23
+
24
+ ```sh
25
+ npm install rockbox-ffi # Node.js (also pulls in koffi)
26
+ bun add rockbox-ffi # Bun
27
+ deno add npm:rockbox-ffi # Deno (or import npm:rockbox-ffi/deno directly)
28
+ ```
29
+
30
+ Then import the entry point for your runtime — all three expose the identical
31
+ API:
32
+
33
+ | Runtime | Import | Notes |
34
+ | ------- | -------------------------------------- | ------------------------------------------------ |
35
+ | Bun | `import … from "rockbox-ffi/bun"` | uses built-in `bun:ffi` |
36
+ | Deno | `import … from "npm:rockbox-ffi/deno"` | run with `--allow-ffi --allow-read --allow-env` |
37
+ | Node.js | `import … from "rockbox-ffi/node"` | uses [`koffi`](https://koffi.dev) (a dependency) |
38
+
39
+ > Prefer a single import? `import { load } from "rockbox-ffi"` returns a
40
+ > promise that resolves the correct backend at runtime (see
41
+ > [Runtime auto-detection](#runtime-auto-detection)).
42
+
43
+ ### The native library
44
+
45
+ These bindings call into the Rockbox engine through a native shared library
46
+ (`librockbox_ffi.dylib` / `.so`). Point the package at it with the
47
+ `ROCKBOX_FFI_LIB` environment variable:
48
+
49
+ ```sh
50
+ export ROCKBOX_FFI_LIB=/path/to/librockbox_ffi.dylib
51
+ ```
52
+
53
+ If unset, the loader searches upward from the package for a
54
+ `target/release/librockbox_ffi.{dylib,so}` (handy when working inside the
55
+ Rockbox repo). See [Building the native library](#building-the-native-library).
56
+
57
+ ## Quick start
58
+
59
+ ```ts
60
+ import {
61
+ metadata,
62
+ Dsp,
63
+ Player,
64
+ sineStereo,
65
+ DspReplayGainMode,
66
+ ReplayGainMode,
67
+ CrossfadeMode,
68
+ } from "rockbox-ffi/bun"; // or /node, or npm:rockbox-ffi/deno
69
+
70
+ // --- metadata --------------------------------------------------------
71
+ const meta = metadata.read("song.flac");
72
+ console.log(meta.artist, "—", meta.title, `${meta.duration_ms} ms`);
73
+ console.log(metadata.probe("track.opus")); // "Opus" (extension guess, no I/O)
74
+
75
+ // --- DSP: process interleaved stereo Int16 ---------------------------
76
+ const dsp = new Dsp(44_100);
77
+ dsp.eqEnable(true);
78
+ dsp.setEqBand(0, /*cutoffHz*/ 60, /*q*/ 0.7, /*gainDb*/ 3.0);
79
+ dsp.setReplaygain(DspReplayGainMode.TRACK, /*noclip*/ true, /*preampDb*/ 0.0);
80
+ dsp.setReplaygainGains(/*trackGainDb*/ -6.02); // −6 dB ≈ half amplitude
81
+
82
+ const input = sineStereo(1_000, 1.0, 44_100); // 1 s of a 1 kHz test tone
83
+ const output: Int16Array = dsp.process(input);
84
+ dsp.close();
85
+
86
+ // --- playback (needs an audio output device) -------------------------
87
+ const player = new Player({ volume: 0.8, crossfadeMode: CrossfadeMode.ALWAYS });
88
+ player.setReplaygain(ReplayGainMode.TRACK, 0.0, true);
89
+ player.setQueue(["a.flac", "b.mp3", "c.opus"]);
90
+ player.play();
91
+ console.log(player.status()); // { state: "playing", index: 0, ... }
92
+ ```
93
+
94
+ ## API
95
+
96
+ Everything is fully typed — the tables below are a map; your editor has the
97
+ details.
98
+
99
+ ### `metadata`
100
+
101
+ | Function | Returns | Description | |
102
+ | -------------------------- | ---------- | ------------------------------------------------------- | -------------------------------------------------------- |
103
+ | `metadata.read(path)` | `Metadata` | Parse tags, duration, ReplayGain, album-art/cue offsets | |
104
+ | `metadata.probe(filename)` | `string \ | null` | Codec label from the extension, without opening the file |
105
+
106
+ ### `Dsp`
107
+
108
+ Interleaved-S16LE-stereo processor. Construct with a sample rate, feed it
109
+ `Int16Array`s, and `close()` (or `using`) when done.
110
+
111
+ ```ts
112
+ const dsp = new Dsp(sampleRate: number);
113
+ ```
114
+
115
+ | Method | Description |
116
+ | ---------------------------------------------------------------- | ------------------------------------------------------------ |
117
+ | `process(samples: Int16Array): Int16Array` | Run stereo S16 frames through the pipeline |
118
+ | `setInputFrequency(hz)` | Change input rate (engages the resampler) |
119
+ | `eqEnable(on)` / `setEqBand(band, cutoffHz, q, gainDb)` | 10-band EQ (band 0 low-shelf, 9 high-shelf) |
120
+ | `setEqPrecut(db)` | Negative pre-gain to avoid EQ clipping |
121
+ | `setTone(bassDb, trebleDb)` / `setToneCutoffs(bHz, tHz)` | Bass/treble shelves |
122
+ | `setSurround(delayMs, balance, fx1, fx2)` | Haas surround (`delayMs > 0` enables) |
123
+ | `setChannelConfig(mode)` / `setStereoWidth(pct)` | Channel mode / custom width |
124
+ | `setCompressor(threshold, makeup, ratio, knee, rel, atk)` | Dynamic-range compressor (`threshold 0` = off) |
125
+ | `setReplaygain(mode, noclip, preampDb)` | ReplayGain mode (see [encodings](#two-replaygain-encodings)) |
126
+ | `setReplaygainGains(trackDb?, albumDb?, trackPeak?, albumPeak?)` | Per-track gains in dB (omit = absent) |
127
+ | `setReplaygainGainsRaw(tg, ag, tp, ap: bigint)` | Native Q7.24 gains (the `raw_*` metadata fields) |
128
+ | `flush()` / `close()` | Drop buffered samples / free the handle |
129
+
130
+ ### `Player`
131
+
132
+ Queue-based player backed by a live audio device and a background thread.
133
+
134
+ ```ts
135
+ const player = new Player(config?: PlayerConfig);
136
+ ```
137
+
138
+ | Method | Description |
139
+ | -------------------------------------------------------------- | ---------------------------------------- |
140
+ | `setQueue(paths)` / `enqueue(path)` | Replace / append to the queue |
141
+ | `play()` `pause()` `toggle()` `stop()` | Transport |
142
+ | `next()` `previous()` `skipTo(index)` `seekMs(ms)` | Navigation |
143
+ | `setVolume(v)` / `volume()` | Volume, `0.0`–`1.0` |
144
+ | `sampleRate()` | Output rate everything resamples to |
145
+ | `setCrossfade(mode, foDelay?, foDur?, fiDelay?, fiDur?, mix?)` | Configure crossfade |
146
+ | `setReplaygain(mode, preampDb, preventClipping)` | ReplayGain (player encoding) |
147
+ | `status(): PlayerStatus` | Snapshot: state, index, position, queue… |
148
+ | `close()` | Stop playback and free the handle |
149
+
150
+ `PlayerConfig` (all optional): `sampleRate` (`0` = device default),
151
+ `bufferSeconds`, `volume`, `replaygainMode`, `replaygainPreampDb`,
152
+ `replaygainPreventClipping`, `crossfadeMode`, `fadeOutDelayMs`,
153
+ `fadeOutDurationMs`, `fadeInDelayMs`, `fadeInDurationMs`, `mixMode`.
154
+
155
+ ### Enums & helpers
156
+
157
+ `DspReplayGainMode`, `ReplayGainMode`, `CrossfadeMode`, `MixMode`,
158
+ `ChannelConfig`, and `sineStereo(freqHz, seconds, rate, amplitude?)` for
159
+ generating test tones. `abiVersion()` returns the native ABI version.
160
+
161
+ ### Types
162
+
163
+ `Metadata`, `PlayerStatus`, `ReplayGain`, `AlbumArt`, `Cuesheet`,
164
+ `PlayerConfig` are exported for annotations. JSON field names are snake_case
165
+ (e.g. `duration_ms`, `sample_rate`, `replaygain.raw_track_gain`).
166
+
167
+ ## Resource management
168
+
169
+ `Dsp` and `Player` hold native handles. Free them with `close()`, or let
170
+ `using` do it automatically (both implement `Symbol.dispose`):
171
+
172
+ ```ts
173
+ using dsp = new Dsp(44_100);
174
+ // dsp.close() runs at end of scope
175
+ ```
176
+
177
+ Everything else that crosses the FFI boundary (JSON strings, sample buffers)
178
+ is freed inside the wrappers — you never call a `*_free` yourself.
179
+
180
+ > **Note:** the `Dsp` wraps a process-wide singleton, so keep only one alive
181
+ > at a time and use it from a single thread.
182
+
183
+ ## Two ReplayGain encodings
184
+
185
+ The DSP and player take *different* mode integers (a quirk of the underlying
186
+ C ABI) — use the named enums and you won't have to remember which:
187
+
188
+ - `Dsp.setReplaygain` → `DspReplayGainMode` (`TRACK=0, ALBUM=1, SHUFFLE=2, OFF=3`)
189
+ - `Player.setReplaygain` → `ReplayGainMode` (`OFF=0, TRACK=1, ALBUM=2`)
190
+
191
+ ## Runtime auto-detection
192
+
193
+ If you don't want to hard-code the backend, import `load` from the package
194
+ root; it dynamically imports the right module (so the other runtimes' FFI code
195
+ is never parsed):
196
+
197
+ ```ts
198
+ import { load } from "rockbox-ffi";
199
+
200
+ const { metadata, Dsp, Player } = await load();
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Developing
206
+
207
+ The rest is only relevant when hacking on the bindings themselves inside the
208
+ [Rockbox repo](https://github.com/tsirysndr/rockboxd).
209
+
210
+ ### Building the native library
211
+
212
+ ```sh
213
+ cargo build --release -p rockbox-ffi
214
+ # target/release/librockbox_ffi.dylib (macOS)
215
+ # target/release/librockbox_ffi.so (Linux)
216
+ ```
217
+
218
+ The loader finds this automatically when the package sits inside the repo;
219
+ elsewhere, set `ROCKBOX_FFI_LIB`.
220
+
221
+ ### Running the smoke tests
222
+
223
+ ```sh
224
+ bun run examples/smoke.bun.ts
225
+ deno run --allow-ffi --allow-read --allow-env examples/smoke.deno.ts
226
+ npm install && npx tsx examples/smoke.node.ts
227
+ bunx tsc --noEmit # type check
228
+ ```
229
+
230
+ ### Building & publishing
231
+
232
+ The package is authored in TypeScript and shipped as bundled ESM + `.d.ts`
233
+ under `dist/`:
234
+
235
+ ```sh
236
+ bun install
237
+ bun run build # Bun.build + tsc -> dist/*.js + dist/*.d.ts
238
+ npm publish # prepublishOnly runs the build automatically
239
+ ```
240
+
241
+ `build` bundles the four entry points (`index` / `bun` / `deno` / `node`),
242
+ keeping `bun:ffi` and `koffi` external, so consumers get the right backend via
243
+ the package `exports` map (`.`, `./bun`, `./deno`, `./node`).
package/dist/api.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import type { Raw } from "./ffi.ts";
2
+ import type { Metadata, PlayerConfig, PlayerStatus } from "./types.ts";
3
+ export declare function makeApi(raw: Raw): {
4
+ abiVersion: () => number;
5
+ metadata: {
6
+ /** Parse the metadata of the audio file at `path`. */
7
+ read(path: string): Metadata;
8
+ /** Guess the codec label from a filename's extension (null if unknown). */
9
+ probe(filename: string): string | null;
10
+ };
11
+ Dsp: {
12
+ new (sampleRate: number): {
13
+ "__#private@#h": unknown;
14
+ close(): void;
15
+ setInputFrequency(hz: number): void;
16
+ flush(): void;
17
+ eqEnable(enable: boolean): void;
18
+ setEqBand(band: number, cutoffHz: number, q: number, gainDb: number): void;
19
+ setEqPrecut(db: number): void;
20
+ setTone(bassDb: number, trebleDb: number): void;
21
+ setToneCutoffs(bassHz: number, trebleHz: number): void;
22
+ setSurround(delayMs: number, balance: number, fx1: number, fx2: number): void;
23
+ setChannelConfig(mode: number): void;
24
+ setStereoWidth(percent: number): void;
25
+ setCompressor(threshold: number, makeupGain: number, ratio: number, knee: number, releaseTime: number, attackTime: number): void;
26
+ /** mode: DspReplayGainMode (TRACK=0, ALBUM=1, SHUFFLE=2, OFF=3). */
27
+ setReplaygain(mode: number, noclip: boolean, preampDb: number): void;
28
+ /** Per-track gains in plain dB, peaks as linear amplitude. undefined => absent. */
29
+ setReplaygainGains(trackGainDb?: number | null, albumGainDb?: number | null, trackPeak?: number | null, albumPeak?: number | null): void;
30
+ /** Native Q7.24 factors (the `raw_*` metadata fields); use BigInt. */
31
+ setReplaygainGainsRaw(trackGain: bigint, albumGain: bigint, trackPeak: bigint, albumPeak: bigint): void;
32
+ /** Run interleaved stereo S16 samples through the pipeline. */
33
+ process(samples: Int16Array): Int16Array;
34
+ [Symbol.dispose](): void;
35
+ };
36
+ };
37
+ Player: {
38
+ new (config?: PlayerConfig): {
39
+ "__#private@#h": unknown;
40
+ close(): void;
41
+ setQueue(paths: string[]): void;
42
+ enqueue(path: string): void;
43
+ play(): void;
44
+ pause(): void;
45
+ toggle(): void;
46
+ stop(): void;
47
+ next(): void;
48
+ previous(): void;
49
+ skipTo(index: number): void;
50
+ seekMs(ms: number | bigint): void;
51
+ setVolume(vol: number): void;
52
+ volume(): number;
53
+ sampleRate(): number;
54
+ setCrossfade(mode: number, foDelayMs?: number, foDurMs?: number, fiDelayMs?: number, fiDurMs?: number, mixMode?: 0): void;
55
+ /** mode: ReplayGainMode (OFF=0, TRACK=1, ALBUM=2). */
56
+ setReplaygain(mode: number, preampDb: number, preventClipping: boolean): void;
57
+ status(): PlayerStatus;
58
+ [Symbol.dispose](): void;
59
+ };
60
+ };
61
+ };
62
+ /** Generate `seconds` of a sine as interleaved stereo Int16. */
63
+ export declare function sineStereo(freqHz: number, seconds: number, rate: number, amplitude?: number): Int16Array;
64
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAKvE,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG;sBAGP,MAAM;;QAG3B,sDAAsD;mBAC3C,MAAM,GAAG,QAAQ;QAK5B,2EAA2E;wBAC3D,MAAM,GAAG,MAAM,GAAG,IAAI;;;yBAQd,MAAM;6BAD1B,OAAO;qBAKF,IAAI;kCAUS,MAAM,GAAG,IAAI;qBAG1B,IAAI;6BAGI,OAAO,GAAG,IAAI;4BAGf,MAAM,YAAY,MAAM,KAAK,MAAM,UAAU,MAAM,GAAG,IAAI;4BAG1D,MAAM,GAAG,IAAI;4BAGb,MAAM,YAAY,MAAM,GAAG,IAAI;mCAGxB,MAAM,YAAY,MAAM,GAAG,IAAI;iCAGjC,MAAM,WAAW,MAAM,OAAO,MAAM,OAAO,MAAM,GAAG,IAAI;mCAGtD,MAAM,GAAG,IAAI;oCAGZ,MAAM,GAAG,IAAI;qCAIxB,MAAM,cAAc,MAAM,SAAS,MAAM,QAAQ,MAAM,eACrD,MAAM,cAAc,MAAM,GACtC,IAAI;YAGP,oEAAoE;gCAChD,MAAM,UAAU,OAAO,YAAY,MAAM,GAAG,IAAI;YAGpE,mFAAmF;6CAEnE,MAAM,GAAG,IAAI,gBAAgB,MAAM,GAAG,IAAI,cAC5C,MAAM,GAAG,IAAI,cAAc,MAAM,GAAG,IAAI,GACnD,IAAI;YAKP,sEAAsE;6CAEzD,MAAM,aAAa,MAAM,aAAa,MAAM,aAAa,MAAM,GACzE,IAAI;YAGP,+DAA+D;6BAC9C,UAAU,GAAG,UAAU;gCA5DpB,IAAI;;;;sBA2EH,YAAY;6BAD7B,OAAO;qBAwBF,IAAI;4BAUG,MAAM,EAAE,GAAG,IAAI;0BAGjB,MAAM,GAAG,IAAI;oBAGnB,IAAI;qBAGH,IAAI;sBAGH,IAAI;oBAGN,IAAI;oBAGJ,IAAI;wBAGA,IAAI;0BAGF,MAAM,GAAG,IAAI;uBAGhB,MAAM,GAAG,MAAM,GAAG,IAAI;2BAGlB,MAAM,GAAG,IAAI;sBAGlB,MAAM;0BAGF,MAAM;+BAIZ,MAAM,4FAEX,IAAI;YAGP,sDAAsD;gCAClC,MAAM,YAAY,MAAM,mBAAmB,OAAO,GAAG,IAAI;sBAGnE,YAAY;gCArDF,IAAI;;;EA6D3B;AAED,gEAAgE;AAChE,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,SAAQ,GAC/D,UAAU,CAUZ"}
package/dist/bun.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { sineStereo } from "./api.ts";
2
+ export declare const abiVersion: () => number, metadata: {
3
+ read(path: string): import("./types.ts").Metadata;
4
+ probe(filename: string): string | null;
5
+ }, Dsp: {
6
+ new (sampleRate: number): {
7
+ "__#private@#h": unknown;
8
+ close(): void;
9
+ setInputFrequency(hz: number): void;
10
+ flush(): void;
11
+ eqEnable(enable: boolean): void;
12
+ setEqBand(band: number, cutoffHz: number, q: number, gainDb: number): void;
13
+ setEqPrecut(db: number): void;
14
+ setTone(bassDb: number, trebleDb: number): void;
15
+ setToneCutoffs(bassHz: number, trebleHz: number): void;
16
+ setSurround(delayMs: number, balance: number, fx1: number, fx2: number): void;
17
+ setChannelConfig(mode: number): void;
18
+ setStereoWidth(percent: number): void;
19
+ setCompressor(threshold: number, makeupGain: number, ratio: number, knee: number, releaseTime: number, attackTime: number): void;
20
+ setReplaygain(mode: number, noclip: boolean, preampDb: number): void;
21
+ setReplaygainGains(trackGainDb?: number | null, albumGainDb?: number | null, trackPeak?: number | null, albumPeak?: number | null): void;
22
+ setReplaygainGainsRaw(trackGain: bigint, albumGain: bigint, trackPeak: bigint, albumPeak: bigint): void;
23
+ process(samples: Int16Array): Int16Array;
24
+ [Symbol.dispose](): void;
25
+ };
26
+ }, Player: {
27
+ new (config?: import("./types.ts").PlayerConfig): {
28
+ "__#private@#h": unknown;
29
+ close(): void;
30
+ setQueue(paths: string[]): void;
31
+ enqueue(path: string): void;
32
+ play(): void;
33
+ pause(): void;
34
+ toggle(): void;
35
+ stop(): void;
36
+ next(): void;
37
+ previous(): void;
38
+ skipTo(index: number): void;
39
+ seekMs(ms: number | bigint): void;
40
+ setVolume(vol: number): void;
41
+ volume(): number;
42
+ sampleRate(): number;
43
+ setCrossfade(mode: number, foDelayMs?: number, foDurMs?: number, fiDelayMs?: number, fiDurMs?: number, mixMode?: 0): void;
44
+ setReplaygain(mode: number, preampDb: number, preventClipping: boolean): void;
45
+ status(): import("./types.ts").PlayerStatus;
46
+ [Symbol.dispose](): void;
47
+ };
48
+ };
49
+ export { sineStereo };
50
+ export * from "./enums.ts";
51
+ export type { Metadata, PlayerStatus, PlayerConfig } from "./types.ts";
52
+ //# sourceMappingURL=bun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAeA,OAAO,EAAW,UAAU,EAAE,MAAM,UAAU,CAAC;AA4E/C,eAAO,MAAQ,UAAU,gBAAE,QAAQ;;;GAAE,GAAG;;;;;;;;;;;;;;;;;;;;;GAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;CAAQ,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
package/dist/bun.js ADDED
@@ -0,0 +1,96 @@
1
+ import {
2
+ SPEC,
3
+ resolveLibPath
4
+ } from "./chunk-ewj9g332.js";
5
+ import {
6
+ ChannelConfig,
7
+ CrossfadeMode,
8
+ DspReplayGainMode,
9
+ MixMode,
10
+ ReplayGainMode,
11
+ makeApi,
12
+ sineStereo
13
+ } from "./chunk-raaa0pkf.js";
14
+
15
+ // src/bun.ts
16
+ import {
17
+ CString,
18
+ dlopen,
19
+ FFIType,
20
+ ptr,
21
+ toArrayBuffer
22
+ } from "bun:ffi";
23
+ import { existsSync } from "node:fs";
24
+ import { dirname, join } from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+ var TOK = {
27
+ void: FFIType.void,
28
+ ptr: FFIType.ptr,
29
+ u32: FFIType.u32,
30
+ i32: FFIType.i32,
31
+ u64: FFIType.u64,
32
+ usize: FFIType.u64,
33
+ f32: FFIType.f32,
34
+ bool: FFIType.bool,
35
+ strbuf: FFIType.ptr,
36
+ i16in: FFIType.ptr,
37
+ outsize: FFIType.ptr
38
+ };
39
+ function makeRaw() {
40
+ const here = dirname(fileURLToPath(import.meta.url));
41
+ const libPath = resolveLibPath(here, (k) => Bun.env[k], existsSync, join, dirname);
42
+ const symbols = {};
43
+ for (const [name, spec] of Object.entries(SPEC)) {
44
+ symbols[name] = { args: spec.args.map((t) => TOK[t]), returns: TOK[spec.ret] };
45
+ }
46
+ const lib = dlopen(libPath, symbols);
47
+ const sym = lib.symbols;
48
+ const enc = new TextEncoder;
49
+ return {
50
+ sym,
51
+ cstr(s) {
52
+ const buf = enc.encode(s + "\x00");
53
+ return ptr(buf);
54
+ },
55
+ i16in(a) {
56
+ return ptr(a);
57
+ },
58
+ sizeOut() {
59
+ const arr = new BigUint64Array(1);
60
+ return { arg: ptr(arr), value: () => Number(arr[0]) };
61
+ },
62
+ takeString(p) {
63
+ if (p === null || p === 0)
64
+ return null;
65
+ const str = new CString(p).toString();
66
+ sym.rb_string_free(p);
67
+ return str;
68
+ },
69
+ takeI16(p, len) {
70
+ const ab = toArrayBuffer(p, 0, len * 2);
71
+ const copy = new Int16Array(len);
72
+ copy.set(new Int16Array(ab));
73
+ sym.rb_buffer_free(p, len);
74
+ return copy;
75
+ },
76
+ isNull(p) {
77
+ return p === null || p === 0 || p === undefined;
78
+ }
79
+ };
80
+ }
81
+ var api = makeApi(makeRaw());
82
+ var { abiVersion, metadata, Dsp, Player } = api;
83
+ export {
84
+ sineStereo,
85
+ metadata,
86
+ abiVersion,
87
+ ReplayGainMode,
88
+ Player,
89
+ MixMode,
90
+ DspReplayGainMode,
91
+ Dsp,
92
+ CrossfadeMode,
93
+ ChannelConfig
94
+ };
95
+
96
+ //# debugId=9A1F3395E268D15D64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/bun.ts"],
4
+ "sourcesContent": [
5
+ "// Bun backend + full API. Import from here under Bun:\n// import { Dsp, Player, metadata } from \"rockbox-ffi/bun\";\n\nimport {\n CString,\n dlopen,\n FFIType,\n type Pointer,\n ptr,\n toArrayBuffer,\n} from \"bun:ffi\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { makeApi, sineStereo } from \"./api.ts\";\nimport { resolveLibPath, SPEC, type Raw, type Tok } from \"./ffi.ts\";\n\nconst TOK: Record<Tok, FFIType> = {\n void: FFIType.void,\n ptr: FFIType.ptr,\n u32: FFIType.u32,\n i32: FFIType.i32,\n u64: FFIType.u64,\n usize: FFIType.u64,\n f32: FFIType.f32,\n bool: FFIType.bool,\n strbuf: FFIType.ptr,\n i16in: FFIType.ptr,\n outsize: FFIType.ptr,\n};\n\nfunction makeRaw(): Raw {\n const here = dirname(fileURLToPath(import.meta.url));\n const libPath = resolveLibPath(\n here,\n (k) => Bun.env[k],\n existsSync,\n join,\n dirname,\n );\n\n const symbols: Record<string, { args: FFIType[]; returns: FFIType }> = {};\n for (const [name, spec] of Object.entries(SPEC)) {\n symbols[name] = { args: spec.args.map((t) => TOK[t]), returns: TOK[spec.ret] };\n }\n const lib = dlopen(libPath, symbols);\n const sym = lib.symbols as Record<string, (...a: any[]) => any>;\n\n // Keep marshalled input buffers alive across the (synchronous) call by\n // holding a reference on the caller's stack — Bun's `ptr()` does not copy.\n const enc = new TextEncoder();\n\n return {\n sym,\n cstr(s: string) {\n const buf = enc.encode(s + \"\\0\");\n // Attach to a WeakRef-free holder: return the pointer but stash the\n // buffer on the returned Number via closure is impossible, so we rely\n // on synchronous FFI — the buffer is created fresh here and the call\n // happens immediately after in the same tick.\n return ptr(buf);\n },\n i16in(a: Int16Array) {\n return ptr(a);\n },\n sizeOut() {\n const arr = new BigUint64Array(1);\n return { arg: ptr(arr), value: () => Number(arr[0]) };\n },\n takeString(p: unknown) {\n if (p === null || p === 0) return null;\n // Bun pointers are numbers at runtime; the typed API wants `Pointer`.\n const str = new CString(p as Pointer).toString();\n sym.rb_string_free(p);\n return str;\n },\n takeI16(p: unknown, len: number) {\n const ab = toArrayBuffer(p as Pointer, 0, len * 2);\n const copy = new Int16Array(len);\n copy.set(new Int16Array(ab));\n sym.rb_buffer_free(p, len);\n return copy;\n },\n isNull(p: unknown) {\n return p === null || p === 0 || p === undefined;\n },\n };\n}\n\nconst api = makeApi(makeRaw());\nexport const { abiVersion, metadata, Dsp, Player } = api;\nexport { sineStereo };\nexport * from \"./enums.ts\";\nexport type { Metadata, PlayerStatus, PlayerConfig } from \"./types.ts\";\n"
6
+ ],
7
+ "mappings": ";;;;;;;;;;;;;;;AAGA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA;AACA;AACA;AAKA,IAAM,MAA4B;AAAA,EAChC,MAAM,QAAQ;AAAA,EACd,KAAK,QAAQ;AAAA,EACb,KAAK,QAAQ;AAAA,EACb,KAAK,QAAQ;AAAA,EACb,KAAK,QAAQ;AAAA,EACb,OAAO,QAAQ;AAAA,EACf,KAAK,QAAQ;AAAA,EACb,MAAM,QAAQ;AAAA,EACd,QAAQ,QAAQ;AAAA,EAChB,OAAO,QAAQ;AAAA,EACf,SAAS,QAAQ;AACnB;AAEA,SAAS,OAAO,GAAQ;AAAA,EACtB,MAAM,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AAAA,EACnD,MAAM,UAAU,eACd,MACA,CAAC,MAAM,IAAI,IAAI,IACf,YACA,MACA,OACF;AAAA,EAEA,MAAM,UAAiE,CAAC;AAAA,EACxE,YAAY,MAAM,SAAS,OAAO,QAAQ,IAAI,GAAG;AAAA,IAC/C,QAAQ,QAAQ,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,SAAS,IAAI,KAAK,KAAK;AAAA,EAC/E;AAAA,EACA,MAAM,MAAM,OAAO,SAAS,OAAO;AAAA,EACnC,MAAM,MAAM,IAAI;AAAA,EAIhB,MAAM,MAAM,IAAI;AAAA,EAEhB,OAAO;AAAA,IACL;AAAA,IACA,IAAI,CAAC,GAAW;AAAA,MACd,MAAM,MAAM,IAAI,OAAO,IAAI,MAAI;AAAA,MAK/B,OAAO,IAAI,GAAG;AAAA;AAAA,IAEhB,KAAK,CAAC,GAAe;AAAA,MACnB,OAAO,IAAI,CAAC;AAAA;AAAA,IAEd,OAAO,GAAG;AAAA,MACR,MAAM,MAAM,IAAI,eAAe,CAAC;AAAA,MAChC,OAAO,EAAE,KAAK,IAAI,GAAG,GAAG,OAAO,MAAM,OAAO,IAAI,EAAE,EAAE;AAAA;AAAA,IAEtD,UAAU,CAAC,GAAY;AAAA,MACrB,IAAI,MAAM,QAAQ,MAAM;AAAA,QAAG,OAAO;AAAA,MAElC,MAAM,MAAM,IAAI,QAAQ,CAAY,EAAE,SAAS;AAAA,MAC/C,IAAI,eAAe,CAAC;AAAA,MACpB,OAAO;AAAA;AAAA,IAET,OAAO,CAAC,GAAY,KAAa;AAAA,MAC/B,MAAM,KAAK,cAAc,GAAc,GAAG,MAAM,CAAC;AAAA,MACjD,MAAM,OAAO,IAAI,WAAW,GAAG;AAAA,MAC/B,KAAK,IAAI,IAAI,WAAW,EAAE,CAAC;AAAA,MAC3B,IAAI,eAAe,GAAG,GAAG;AAAA,MACzB,OAAO;AAAA;AAAA,IAET,MAAM,CAAC,GAAY;AAAA,MACjB,OAAO,MAAM,QAAQ,MAAM,KAAK,MAAM;AAAA;AAAA,EAE1C;AAAA;AAGF,IAAM,MAAM,QAAQ,QAAQ,CAAC;AACtB,MAAQ,YAAY,UAAU,KAAK,WAAW;",
8
+ "debugId": "9A1F3395E268D15D64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,99 @@
1
+ // src/ffi.ts
2
+ var SPEC = {
3
+ rb_ffi_abi_version: { args: [], ret: "u32" },
4
+ rb_string_free: { args: ["ptr"], ret: "void" },
5
+ rb_buffer_free: { args: ["ptr", "usize"], ret: "void" },
6
+ rb_dsp_new: { args: ["u32"], ret: "ptr" },
7
+ rb_dsp_free: { args: ["ptr"], ret: "void" },
8
+ rb_dsp_set_input_frequency: { args: ["ptr", "u32"], ret: "void" },
9
+ rb_dsp_flush: { args: ["ptr"], ret: "void" },
10
+ rb_dsp_eq_enable: { args: ["ptr", "bool"], ret: "void" },
11
+ rb_dsp_set_tone: { args: ["ptr", "i32", "i32"], ret: "void" },
12
+ rb_dsp_set_tone_cutoffs: { args: ["ptr", "i32", "i32"], ret: "void" },
13
+ rb_dsp_set_surround: { args: ["ptr", "i32", "i32", "i32", "i32"], ret: "void" },
14
+ rb_dsp_set_channel_config: { args: ["ptr", "i32"], ret: "void" },
15
+ rb_dsp_set_stereo_width: { args: ["ptr", "i32"], ret: "void" },
16
+ rb_dsp_set_compressor: {
17
+ args: ["ptr", "i32", "i32", "i32", "i32", "i32", "i32"],
18
+ ret: "void"
19
+ },
20
+ rb_dsp_set_replaygain: { args: ["ptr", "i32", "bool", "f32"], ret: "void" },
21
+ rb_dsp_set_replaygain_gains: {
22
+ args: ["ptr", "f32", "f32", "f32", "f32"],
23
+ ret: "void"
24
+ },
25
+ rb_dsp_set_replaygain_gains_raw: {
26
+ args: ["ptr", "u64", "u64", "u64", "u64"],
27
+ ret: "void"
28
+ },
29
+ rb_dsp_set_eq_band: { args: ["ptr", "usize", "i32", "f32", "f32"], ret: "void" },
30
+ rb_dsp_set_eq_precut: { args: ["ptr", "f32"], ret: "void" },
31
+ rb_dsp_process: { args: ["ptr", "i16in", "usize", "outsize"], ret: "ptr" },
32
+ rb_meta_read_json: { args: ["strbuf"], ret: "ptr" },
33
+ rb_meta_probe: { args: ["strbuf"], ret: "ptr" },
34
+ rb_player_new: { args: [], ret: "ptr" },
35
+ rb_player_new_with_config: {
36
+ args: [
37
+ "u32",
38
+ "f32",
39
+ "f32",
40
+ "i32",
41
+ "f32",
42
+ "bool",
43
+ "i32",
44
+ "u32",
45
+ "u32",
46
+ "u32",
47
+ "u32",
48
+ "i32"
49
+ ],
50
+ ret: "ptr"
51
+ },
52
+ rb_player_free: { args: ["ptr"], ret: "void" },
53
+ rb_player_set_queue_json: { args: ["ptr", "strbuf"], ret: "void" },
54
+ rb_player_enqueue: { args: ["ptr", "strbuf"], ret: "void" },
55
+ rb_player_play: { args: ["ptr"], ret: "void" },
56
+ rb_player_pause: { args: ["ptr"], ret: "void" },
57
+ rb_player_toggle: { args: ["ptr"], ret: "void" },
58
+ rb_player_stop: { args: ["ptr"], ret: "void" },
59
+ rb_player_next: { args: ["ptr"], ret: "void" },
60
+ rb_player_previous: { args: ["ptr"], ret: "void" },
61
+ rb_player_skip_to: { args: ["ptr", "usize"], ret: "void" },
62
+ rb_player_seek_ms: { args: ["ptr", "u64"], ret: "void" },
63
+ rb_player_set_volume: { args: ["ptr", "f32"], ret: "void" },
64
+ rb_player_set_crossfade: {
65
+ args: ["ptr", "i32", "u32", "u32", "u32", "u32", "i32"],
66
+ ret: "void"
67
+ },
68
+ rb_player_set_replaygain: { args: ["ptr", "i32", "f32", "bool"], ret: "void" },
69
+ rb_player_volume: { args: ["ptr"], ret: "f32" },
70
+ rb_player_sample_rate: { args: ["ptr"], ret: "u32" },
71
+ rb_player_status_json: { args: ["ptr"], ret: "ptr" }
72
+ };
73
+ var LIB_NAMES = ["librockbox_ffi.dylib", "librockbox_ffi.so", "rockbox_ffi.dll"];
74
+ function resolveLibPath(startDir, env, exists, join, dirname) {
75
+ const override = env("ROCKBOX_FFI_LIB");
76
+ if (override)
77
+ return override;
78
+ let dir = startDir;
79
+ const tried = [];
80
+ for (let i = 0;i < 40; i++) {
81
+ const rel = join(dir, "target", "release");
82
+ for (const name of LIB_NAMES) {
83
+ const p = join(rel, name);
84
+ tried.push(p);
85
+ if (exists(p))
86
+ return p;
87
+ }
88
+ const parent = dirname(dir);
89
+ if (parent === dir)
90
+ break;
91
+ dir = parent;
92
+ }
93
+ throw new Error("could not locate librockbox_ffi. Set ROCKBOX_FFI_LIB or run " + "`cargo build --release -p rockbox-ffi`. Tried:\n " + tried.join(`
94
+ `));
95
+ }
96
+
97
+ export { SPEC, resolveLibPath };
98
+
99
+ //# debugId=EBB87DA589FF378164756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ffi.ts"],
4
+ "sourcesContent": [
5
+ "// Runtime-agnostic FFI description: one symbol table with abstract type\n// tokens that each backend (bun.ts / deno.ts) maps to its own FFI types.\n\n/** Abstract argument/return type tokens used by the symbol spec. */\nexport type Tok =\n | \"void\"\n | \"ptr\" // opaque handle / returned pointer\n | \"u32\"\n | \"i32\"\n | \"u64\"\n | \"usize\"\n | \"f32\"\n | \"bool\"\n | \"strbuf\" // input: a JS string marshalled as `const char *`\n | \"i16in\" // input: an Int16Array marshalled as `const int16_t *`\n | \"outsize\"; // input: a `size_t *` out-parameter\n\nexport interface SymSpec {\n args: Tok[];\n ret: Tok;\n}\n\n// Mirrors include/rockbox_ffi.h. Keep in sync with the C ABI.\nexport const SPEC: Record<string, SymSpec> = {\n rb_ffi_abi_version: { args: [], ret: \"u32\" },\n rb_string_free: { args: [\"ptr\"], ret: \"void\" },\n rb_buffer_free: { args: [\"ptr\", \"usize\"], ret: \"void\" },\n\n rb_dsp_new: { args: [\"u32\"], ret: \"ptr\" },\n rb_dsp_free: { args: [\"ptr\"], ret: \"void\" },\n rb_dsp_set_input_frequency: { args: [\"ptr\", \"u32\"], ret: \"void\" },\n rb_dsp_flush: { args: [\"ptr\"], ret: \"void\" },\n rb_dsp_eq_enable: { args: [\"ptr\", \"bool\"], ret: \"void\" },\n rb_dsp_set_tone: { args: [\"ptr\", \"i32\", \"i32\"], ret: \"void\" },\n rb_dsp_set_tone_cutoffs: { args: [\"ptr\", \"i32\", \"i32\"], ret: \"void\" },\n rb_dsp_set_surround: { args: [\"ptr\", \"i32\", \"i32\", \"i32\", \"i32\"], ret: \"void\" },\n rb_dsp_set_channel_config: { args: [\"ptr\", \"i32\"], ret: \"void\" },\n rb_dsp_set_stereo_width: { args: [\"ptr\", \"i32\"], ret: \"void\" },\n rb_dsp_set_compressor: {\n args: [\"ptr\", \"i32\", \"i32\", \"i32\", \"i32\", \"i32\", \"i32\"],\n ret: \"void\",\n },\n rb_dsp_set_replaygain: { args: [\"ptr\", \"i32\", \"bool\", \"f32\"], ret: \"void\" },\n rb_dsp_set_replaygain_gains: {\n args: [\"ptr\", \"f32\", \"f32\", \"f32\", \"f32\"],\n ret: \"void\",\n },\n rb_dsp_set_replaygain_gains_raw: {\n args: [\"ptr\", \"u64\", \"u64\", \"u64\", \"u64\"],\n ret: \"void\",\n },\n rb_dsp_set_eq_band: { args: [\"ptr\", \"usize\", \"i32\", \"f32\", \"f32\"], ret: \"void\" },\n rb_dsp_set_eq_precut: { args: [\"ptr\", \"f32\"], ret: \"void\" },\n rb_dsp_process: { args: [\"ptr\", \"i16in\", \"usize\", \"outsize\"], ret: \"ptr\" },\n\n rb_meta_read_json: { args: [\"strbuf\"], ret: \"ptr\" },\n rb_meta_probe: { args: [\"strbuf\"], ret: \"ptr\" },\n\n rb_player_new: { args: [], ret: \"ptr\" },\n rb_player_new_with_config: {\n args: [\n \"u32\", \"f32\", \"f32\", \"i32\", \"f32\", \"bool\", \"i32\", \"u32\", \"u32\", \"u32\",\n \"u32\", \"i32\",\n ],\n ret: \"ptr\",\n },\n rb_player_free: { args: [\"ptr\"], ret: \"void\" },\n rb_player_set_queue_json: { args: [\"ptr\", \"strbuf\"], ret: \"void\" },\n rb_player_enqueue: { args: [\"ptr\", \"strbuf\"], ret: \"void\" },\n rb_player_play: { args: [\"ptr\"], ret: \"void\" },\n rb_player_pause: { args: [\"ptr\"], ret: \"void\" },\n rb_player_toggle: { args: [\"ptr\"], ret: \"void\" },\n rb_player_stop: { args: [\"ptr\"], ret: \"void\" },\n rb_player_next: { args: [\"ptr\"], ret: \"void\" },\n rb_player_previous: { args: [\"ptr\"], ret: \"void\" },\n rb_player_skip_to: { args: [\"ptr\", \"usize\"], ret: \"void\" },\n rb_player_seek_ms: { args: [\"ptr\", \"u64\"], ret: \"void\" },\n rb_player_set_volume: { args: [\"ptr\", \"f32\"], ret: \"void\" },\n rb_player_set_crossfade: {\n args: [\"ptr\", \"i32\", \"u32\", \"u32\", \"u32\", \"u32\", \"i32\"],\n ret: \"void\",\n },\n rb_player_set_replaygain: { args: [\"ptr\", \"i32\", \"f32\", \"bool\"], ret: \"void\" },\n rb_player_volume: { args: [\"ptr\"], ret: \"f32\" },\n rb_player_sample_rate: { args: [\"ptr\"], ret: \"u32\" },\n rb_player_status_json: { args: [\"ptr\"], ret: \"ptr\" },\n};\n\n/**\n * The low-level backend a runtime loader must provide. High-level classes in\n * api.ts are written once against this interface.\n */\nexport interface Raw {\n /** The dlopen'd symbol table; call e.g. `sym.rb_dsp_new(44100)`. */\n sym: Record<string, (...a: any[]) => any>;\n /** Marshal a JS string as a `const char *` argument. */\n cstr(s: string): unknown;\n /** Marshal an Int16Array as a `const int16_t *` argument. */\n i16in(a: Int16Array): unknown;\n /** Allocate a `size_t` out-parameter; `value()` reads it after the call. */\n sizeOut(): { arg: unknown; value(): number };\n /** Read a returned `char *` into a string and free it (null => null). */\n takeString(p: unknown): string | null;\n /** Read a returned `int16_t *` of `len` samples into a copy and free it. */\n takeI16(p: unknown, len: number): Int16Array;\n /** True if a returned handle/pointer is NULL. */\n isNull(p: unknown): boolean;\n}\n\nconst LIB_NAMES = [\"librockbox_ffi.dylib\", \"librockbox_ffi.so\", \"rockbox_ffi.dll\"];\n\n/**\n * Locate the shared library: `ROCKBOX_FFI_LIB` env var first, then by walking\n * up from `startDir` to a `target/release` directory. `exists` and the path\n * ops are injected so this works under both Bun and Deno.\n */\nexport function resolveLibPath(\n startDir: string,\n env: (k: string) => string | undefined,\n exists: (p: string) => boolean,\n join: (...p: string[]) => string,\n dirname: (p: string) => string,\n): string {\n const override = env(\"ROCKBOX_FFI_LIB\");\n if (override) return override;\n\n let dir = startDir;\n const tried: string[] = [];\n for (let i = 0; i < 40; i++) {\n const rel = join(dir, \"target\", \"release\");\n for (const name of LIB_NAMES) {\n const p = join(rel, name);\n tried.push(p);\n if (exists(p)) return p;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new Error(\n \"could not locate librockbox_ffi. Set ROCKBOX_FFI_LIB or run \" +\n \"`cargo build --release -p rockbox-ffi`. Tried:\\n \" + tried.join(\"\\n \"),\n );\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "EBB87DA589FF378164756E2164756E21",
9
+ "names": []
10
+ }