audiobits 0.0.0-stage → 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/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # audiobits
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - faf1c04: Preserve recipe parameter names and live modes in TypeScript authoring, and add
8
+ five procedural presets: tactileClick, gentleRejection, glassNotification,
9
+ whoosh and powerUp. Mark shared delay experimental and simplify the quick start
10
+ while retaining explicit production lifecycle guidance.
11
+ - 7db5e9f: Prepare the first three-sound core candidate: schema-1 oscillator/white-noise
12
+ recipes, seeded variation, play/live controls, bounded voices, buses and shared
13
+ delay, explicit caller-owned native interop, and gesture-driven lifecycle APIs.
14
+ Chromium is the initial browser gate. No registry release or deployment is implied.
15
+
16
+ ### Patch Changes
17
+
18
+ - faf1c04: Preserve managed linear ramps, live retargeting and release fades when the browser
19
+ lacks AudioParam.cancelAndHoldAtTime. Track bus gain, mute and delay gates explicitly.
20
+
21
+ ## 0.1.0-rc.0
22
+
23
+ ### Minor Changes
24
+
25
+ - Prepare the first three-sound core candidate: schema-1 oscillator/white-noise
26
+ recipes, seeded variation, play/live controls, bounded voices, buses and shared
27
+ delay, explicit caller-owned native interop, and gesture-driven lifecycle APIs.
28
+ Chromium is the initial browser gate. No registry release or deployment is implied.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 joacod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,4 +1,331 @@
1
- # Temporary Holding Version
1
+ # AudioBits
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
4
- If no other versions are published within 30 days, this package and version will be deleted.
3
+ AudioBits **0.1.0** is prepared for stable publication but has not yet been published to npm. It includes confirmation, impact, thruster, tactile click, gentle rejection,
4
+ glass notification, whoosh and power-up; schema-1 recipes; play/live controls; seeded variation; bounded
5
+ voices; bus gain, mute and routing; native output taps; and explicit lifecycle APIs.
6
+ Prerelease verification target: current Chromium. Other browsers and operating
7
+ systems are unverified and deliberately deferred. Maintainer listening acceptance for the
8
+ unchanged sound definitions is recorded separately. Nothing has been published,
9
+ and the npm identifier remains `audiobits`.
10
+
11
+ ## Try locally
12
+
13
+ Install the reviewed archive into your own private consumer with
14
+ `npm install /path/to/audiobits-0.1.0.tgz`. This is a local file install,
15
+ not an instruction to install an existing registry package. Use a browser
16
+ bundler and call `play()` directly from a gesture handler. Surface rejection
17
+ with `void play().catch(showError)` and retry from a fresh gesture.
18
+
19
+ ```ts
20
+ import { createAudio } from "audiobits";
21
+ import { confirmation } from "audiobits/recipes";
22
+
23
+ const audio = createAudio();
24
+ const sound = audio.sound(confirmation);
25
+ export async function play() {
26
+ await audio.start();
27
+ sound.play();
28
+ }
29
+ export function stop() {
30
+ audio.stopAll({ tails: "cut" });
31
+ }
32
+ export async function dispose() {
33
+ await audio.dispose();
34
+ }
35
+ ```
36
+
37
+ Bind `play()` to a button click (`void play().catch(showError)`).
38
+
39
+ ## Production lifecycle
40
+
41
+ For an SPA, invalidate pending activation when hiding or unmounting; suspend on
42
+ hide and dispose on navigation. Returning requires a fresh Play gesture.
43
+
44
+ ```ts
45
+ import { createAudio } from "audiobits";
46
+ import { confirmation } from "audiobits/recipes";
47
+ const audio = createAudio();
48
+ const sound = audio.sound(confirmation);
49
+ let request = 0;
50
+ export async function play() {
51
+ const token = ++request;
52
+ await audio.start();
53
+ if (token === request && !document.hidden) sound.play({ seed: 42 });
54
+ }
55
+ export function stop() {
56
+ request++;
57
+ audio.stopAll({ tails: "cut" });
58
+ }
59
+ function hide() {
60
+ if (document.hidden) {
61
+ stop();
62
+ void audio.suspend().catch(console.error);
63
+ }
64
+ }
65
+ document.addEventListener("visibilitychange", hide);
66
+ export async function dispose() {
67
+ stop();
68
+ document.removeEventListener("visibilitychange", hide);
69
+ await audio.dispose();
70
+ }
71
+ ```
72
+
73
+ `createAudio()` and `audio.sound()` create no context. A failed start reports
74
+ `AudioBitsError` with a retryable `start-failed` code. A blocked resume times out
75
+ after two wall-clock seconds; the context stays owned for gesture retry. Play
76
+ before activation fails with `not-ready`; no input is queued.
77
+
78
+ The archive exports `audiobits/schema.json` and `audiobits/capabilities.json`.
79
+ The latter identifies package version, shipped primitives, initial Chromium gate and verification browser matrix;
80
+ the schema is structural, while `validateRecipe` enforces additional semantic
81
+ limits. Agent guidance is included in [the AudioBits Skill](skill/SKILL.md).
82
+
83
+ ## Supported data
84
+
85
+ `defineSound(recipe)` accepts typed authored data, preserves recipe structure, literal parameter names and modes, validates and returns a deeply frozen `Recipe` snapshot.
86
+ Runtime validation remains authoritative for exact schema validity.
87
+ For external JSON, `validateRecipe(unknown)` returns `{ ok: true, recipe }` or
88
+ `{ ok: false, issues }`; every issue has `code`, `path`, and `message`.
89
+ Paths use JSON bracket notation, such as `$["layers"][0]["id"]`.
90
+ `AudioBitsError` carries `code` and `issues`; errors during validation use
91
+ `invalid-recipe`. The input must be plain JSON data without accessors or cycles.
92
+ Unknown fields and versions are rejected. Validation is browser-independent.
93
+
94
+ Schema version 1 supports:
95
+
96
+ - One-shot gates in `(0, 60]` seconds or sustained playback until Stop, with
97
+ 1–16 uniquely identified layers. Sustained recipes forbid `duration`.
98
+ - Sine, triangle, sawtooth, and square oscillators; numeric Hz or linear/
99
+ exponential frequency automation beginning at zero and ending by gate close
100
+ (at most 60 seconds of onset automation for sustained voices). White noise
101
+ uses bounded generated buffers; there is no audio-file fetch.
102
+ - Layer gain from -60 to 0 dB and fixed ADSR envelopes. Attack/decay are 0–10 s,
103
+ sustain is 0–1, release is 0.005–10 s. Effective attack is at least 0.002 s;
104
+ effective attack plus decay must fit within the gate. A zero decay ramps
105
+ directly to sustain rather than introducing an instantaneous peak.
106
+ - Lowpass/highpass/bandpass filters with frequency 20–20000 Hz and numeric
107
+ Q 0.1–20. At most eight filters and 128 frequency points across the recipe.
108
+ - Source/filter frequencies accept numeric values, mappings, variation, or
109
+ automation. Layer gain accepts numeric values, mappings, or variation;
110
+ gain automation and variable envelope times are not supported.
111
+ - Up to 16 named parameters with `min < max`, an in-range `default`, and `mode`
112
+ of `play` or `live`. Live parameters require `smoothing` in 0.005–1 seconds.
113
+ Names begin with an ASCII letter and contain letters, digits or underscores,
114
+ up to 64 characters. Declaration numbers are bounded to -60000–60000.
115
+
116
+ Depth is limited to 16, visited values to 10000, and diagnostics to 100.
117
+ Source/filter frequencies must be below the owning context's Nyquist frequency
118
+ at playback, including all mapping/variation extrema. Other effects,
119
+ expression strings, sequencing, and continuous random modulation are rejected. These are the capabilities shipped in 0.1.0.
120
+ The generated schema is available as `recipeSchema` or `audiobits/schema.json`.
121
+
122
+ ## Runtime ownership
123
+
124
+ `createAudio({ maxVoices, maxVoicesPerSound, masterGainDb })` defaults to
125
+ 32 active voices per engine, eight per sound, and -12 dB master gain. Limits
126
+ must be integers from 1–128; master gain must be -60–0 dB. `setMuted(boolean)`
127
+ uses a separate mute setting and a 5 ms gain ramp.
128
+
129
+ `sound.play({ at, gainDb, pan, parameters, seed, bus })` creates fresh sources synchronously. `at` is
130
+ absolute audio-context time, obtained by the host's own scheduling logic;
131
+ available after startup as `audio.native.context.currentTime`. Omit it for
132
+ immediate playback. Past timestamps are rejected. Playback gain is -60–0 dB
133
+ (default 0), pan is -1–1 (default 0). Future voices reserve capacity.
134
+
135
+ `voice.stop()` cancels before onset or releases from the current envelope value.
136
+ Repeated stops cannot extend lifetime. `voice.ended` resolves after owned nodes
137
+ are disconnected; `voice.state` is `active`, `stopping`, `retiring`, or `ended`.
138
+ Stopping voices keep their capacity reservation until cleanup. Oldest-voice
139
+ stealing uses a 5 ms output fade. There is at most one retiring voice beyond
140
+ engine/per-sound limits; another steal finalizes the previous retiree first.
141
+ `audio.counts` distinguishes reserved active/stopping voices from retirees.
142
+
143
+ Gate duration excludes release. Filters receive a bounded 50 ms tail allowance;
144
+ output fades to zero over the final 5 ms. Layer filters precede their envelopes.
145
+ `stopAll()` releases managed voices according to their recipe envelopes.
146
+ `stopAll({ tails: "cut" })` uses a 5 ms source/output fade. `sound.dispose()` immediately finalizes
147
+ that sound's voices and noise buffers. `audio.suspend()` invalidates pending starts and finalizes all voices before suspending.
148
+ Native suspension/interruption also finalizes voices when its state event arrives.
149
+ `audio.dispose()` invalidates pending startup, finalizes voices immediately,
150
+ disconnects master output, and closes the context once, even while suspended.
151
+ Disposal is terminal and idempotent; no audio-clock progress is needed.
152
+
153
+ `audio.state` reports `idle`, `starting`, `running`, `suspended`, `interrupted`,
154
+ `closed`, or `disposed`. `subscribe(listener)` returns an unsubscribe function.
155
+ Concurrent starts share one promise. A host controls visibility/navigation policy;
156
+ the engine does not replay sounds or automatically resume them.
157
+
158
+ Recipes contain no native nodes, callbacks, framework imports, or runtime
159
+ dependencies. Only `audiobits/recipes` imports curated sound data.
160
+ Default headroom checks do not guarantee safe peaks for arbitrary recipes,
161
+ filter resonance, gains, or concurrency.
162
+
163
+ ## Dynamic controls and replay
164
+
165
+ ```ts
166
+ import { createAudio } from "audiobits";
167
+ import { impact, thruster } from "audiobits/recipes";
168
+
169
+ const audio = createAudio();
170
+ const hit = audio.sound(impact);
171
+ const engine = audio.sound(thruster);
172
+
173
+ // In a user gesture, await activation before creating voices.
174
+ await audio.start();
175
+ hit.play({ parameters: { intensity: 0.8 }, seed: 42 });
176
+ const voice = engine.play({ parameters: { throttle: 0.2 }, seed: 42 });
177
+ voice.set({ throttle: 1 }); // Update this voice without retriggering.
178
+ console.log(voice.seed, voice.parameters);
179
+ voice.stop();
180
+ await voice.ended;
181
+ await audio.dispose();
182
+ ```
183
+
184
+ Omitted parameters use recipe defaults. Invalid names, non-finite values,
185
+ out-of-range controls, and invalid seeds fail before graph allocation or voice
186
+ stealing. Updates validate every supplied key before changing any parameter;
187
+ play-only controls cannot be updated. `voice.parameters` is a frozen snapshot of
188
+ requested values, not a readback of the current smoothed native parameters.
189
+ `voice.set()` after Stop, retirement, completion, or disposal throws
190
+ `ended-voice`; other control errors use `invalid-control`.
191
+
192
+ Mappings use `{ control, range: [low, high], scale }`, where scale is `linear`
193
+ or `exponential`. Exponential mapping endpoints must be positive. Mapping ranges
194
+ may descend; random ranges `{ random: [low, high] }` must be ordered. All extrema
195
+ must satisfy the target range. Automation points can reference only play-only
196
+ controls. Direct live mappings support source frequency, layer gain, and filter
197
+ frequency. Native targets ramp linearly over the parameter's smoothing time,
198
+ from their current value on every retarget. For gain, that ramp is in linear
199
+ amplitude after dB conversion; mapping scale describes the control-to-target
200
+ conversion, not the ramp curve. Envelopes remain fixed and independent of live
201
+ level controls, so release remains continuous.
202
+
203
+ Seeds are unsigned 32-bit integers, including zero. Without one, playback chooses
204
+ a seed and exposes it on `voice.seed`. xorshift32 uses shifts 13/17/5 and divides
205
+ its unsigned state by 2^32; seed zero maps internally to `0x6d2b79f5`. Resolution
206
+ traverses root effects, then layers in array order. Each layer resolves gain,
207
+ layer filters, source frequency, and a noise seed, in that order; automation
208
+ points follow time order. Each variation consumes one draw, and each noise layer
209
+ consumes one draw for its own xorshift32 stream. Object property insertion order
210
+ does not change this traversal. This algorithm reproduces choices and
211
+ noise for the same normalized recipe, controls, seed and sample rate; it does not
212
+ promise identical oscillator/filter samples across browsers or sample rates.
213
+
214
+ ## Noise ownership and bounds
215
+
216
+ Each noise layer owns one mono, one-second buffer at context sample rate. The
217
+ last 20 ms crossfades into the first 20 ms; looping resumes after that prefix,
218
+ so the wrap follows an ordinary adjacent sample pair. The loop period is about
219
+ 0.98 seconds. Both finite and sustained noise loop this bounded resource.
220
+ This treatment has signal evidence and maintainer listening acceptance;
221
+ individual listening observations were not recorded. There is no shared noise cache or rendered-output cache.
222
+
223
+ Noise supports integer sample rates from 8000 through 192000 Hz. Unsupported
224
+ rates fail with `noise-rate` before voice allocation. A layer retains at most
225
+ 768000 sample bytes (192000 at 48 kHz); generation temporarily uses one extra
226
+ buffer of that size. A recipe has at most 16 layers, and existing voice limits
227
+ bound concurrent buffers. The thruster owns ten nodes and one buffer per voice;
228
+ with its default eight-voice limit plus one retiree, sample storage is at most
229
+ 1728000 bytes at 48 kHz. The engine master owns two additional nodes for independent gain and mute. These are
230
+ owned sample-storage bounds, not measurements of all browser memory.
231
+
232
+ Control updates allocate no new audio nodes or buffers. Finishing a voice clears
233
+ its buffer-source references and disconnects owned nodes, including cancellation
234
+ before onset, stealing, sound disposal, and engine teardown. Browser-internal
235
+ reclamation timing remains outside the library's control.
236
+
237
+ ## Buses and routing
238
+
239
+ After `await audio.start()`, `audio.master` is the root bus. `audio.bus(name,
240
+ parent = audio.master)` creates a named bus or reuses the live bus with that
241
+ name; use `setParent()` to move an existing bus. Names contain 1–64 characters
242
+ and cannot be blank. At most 32 buses, including master, may be live. Routes
243
+ form a single-parent tree; invalid, disposed, foreign-engine, and cyclic
244
+ parents fail before changing the previous connection. Reassigning the same
245
+ parent does not create another route. `sound.play({ bus })` defaults to master
246
+ and rejects foreign/disposed buses before voice allocation or stealing.
247
+
248
+ `bus.setGainDb(value, rampSeconds = 0.005)` accepts -60–0 dB and 0–10 seconds.
249
+ It holds the current native value before ramping. `bus.setMuted(boolean)` uses
250
+ an independent 5 ms stage, preserving volume automation. `audio.setMuted()`
251
+ controls master mute, including a setting made before startup.
252
+
253
+ ```ts
254
+ import { createAudio } from "audiobits";
255
+ import { confirmation } from "audiobits/recipes";
256
+ const audio = createAudio();
257
+ const sound = audio.sound(confirmation);
258
+ // In a gesture handler, after activating this engine:
259
+ await audio.start();
260
+ const effects = audio.bus("effects");
261
+ effects.setGainDb(-6, 0.1);
262
+ const voice = sound.play({ bus: effects });
263
+ voice.stop(); // Release this voice.
264
+ audio.stopAll({ tails: "cut" }); // Fade managed voices over 5 ms.
265
+ effects.dispose(); // Finalize routed voices and descendant buses.
266
+ await audio.dispose();
267
+ ```
268
+
269
+ Each bus owns two gain nodes. Suspension and disposal finalize voices
270
+ without waiting for audio-clock progress.
271
+
272
+ Non-master bus disposal is idempotent and stops routed voices recursively.
273
+ A later lookup of that name creates a fresh bus. `master.dispose()` fails;
274
+ dispose the engine to release master. Engine disposal removes all owned
275
+ output and closes its context, even while suspended or starting.
276
+
277
+ ## Native analyser and caller ownership
278
+
279
+ `audio.native` is available after startup and exposes the owned `context`,
280
+ master `output`, and `connect(node)` tap helper. The helper rejects foreign
281
+ contexts, duplicate taps, the output itself, and the context destination.
282
+ It returns an idempotent detach function. Master already connects to the
283
+ speaker destination. Leave an analyser's output unconnected to avoid a second
284
+ audible path. The vanilla example reads peak levels with a host animation
285
+ frame, cancels that frame, detaches the tap, and disconnects its analyser
286
+ before engine teardown.
287
+
288
+ ```ts
289
+ import { createAudio } from "audiobits";
290
+ const audio = createAudio();
291
+ // Execute in a gesture handler.
292
+ await audio.start();
293
+ const native = audio.native;
294
+ const analyser = native.context.createAnalyser();
295
+ const detach = native.connect(analyser);
296
+ const samples = new Float32Array(analyser.fftSize);
297
+ analyser.getFloatTimeDomainData(samples);
298
+ // Host teardown:
299
+ detach();
300
+ analyser.disconnect();
301
+ await audio.dispose();
302
+ ```
303
+
304
+ Caller-created nodes, sources, connections, and animation listeners are
305
+ caller-owned. Stop unmanaged sources explicitly: `stopAll()` cannot stop them.
306
+ Direct native graph operations remain the caller's responsibility and cannot
307
+ be serialized as recipes. Closing the engine context invalidates these native
308
+ nodes; retained native handles do not transfer ownership back to the engine.
309
+
310
+ The site and vanilla demo invalidate pending Play actions, cut managed tails,
311
+ and suspend on hide. Returning does not resume; a fresh gesture starts audio.
312
+ Late activation after suspension is cancelled and cannot replay an old request.
313
+ The engine also clears owned voices/effects on native interruption; automatic
314
+ native recovery is suspended until a fresh `start()` request. Physical OS
315
+ interruption still requires manual evidence. Step 04 passed maintainer manual
316
+ verification on 2026-10-06. Browser/device details and individual observations
317
+ were not supplied; automated checks remain separate from that acceptance.
318
+
319
+ Buses provide gain, mute, and routing. Shared effects are deferred until
320
+ multiple real sound requirements justify an API.
321
+
322
+ ## Curated sounds
323
+
324
+ The eight exports live in `audiobits/recipes`: `confirmation`, `impact`, `thruster`,
325
+ `tactileClick`, `gentleRejection`, `glassNotification`, `whoosh`, and `powerUp`.
326
+ Intensity controls impact, click and power-up; brightness controls glass; size
327
+ controls whoosh; throttle is live on thruster. Other sounds have no parameters.
328
+ New sound definitions use only schema-1 sources, filters and envelopes. Their
329
+ signal and resource properties are tested; the current eight curated sounds have
330
+ passed the maintainer listening gate in Chrome. Output devices and detailed
331
+ listening coverage were not specified.
@@ -0,0 +1,40 @@
1
+ {
2
+ "packageVersion": "0.1.0",
3
+ "schemaVersion": 1,
4
+ "status": "stable",
5
+ "recipe": {
6
+ "kinds": ["one-shot", "sustained"],
7
+ "sources": ["oscillator", "noise"],
8
+ "waveforms": ["sine", "triangle", "sawtooth", "square"],
9
+ "noiseColors": ["white"],
10
+ "filters": ["lowpass", "highpass", "bandpass"],
11
+ "parameterModes": ["play", "live"],
12
+ "mappingScales": ["linear", "exponential"],
13
+ "automationCurves": ["linear", "exponential"],
14
+ "values": ["number", "mapping", "seeded-variation", "frequency-automation"]
15
+ },
16
+ "curatedRecipes": [
17
+ "confirmation",
18
+ "impact",
19
+ "thruster",
20
+ "tactileClick",
21
+ "gentleRejection",
22
+ "glassNotification",
23
+ "whoosh",
24
+ "powerUp"
25
+ ],
26
+ "runtime": [
27
+ "gesture-start",
28
+ "bounded-voices",
29
+ "live-controls",
30
+ "seeded-replay",
31
+ "buses",
32
+ "native-output-tap",
33
+ "suspend",
34
+ "dispose"
35
+ ],
36
+ "experimentalRuntime": [],
37
+ "browserGate": "Chromium",
38
+ "browserMatrix": ["Chromium"],
39
+ "schemaNote": "Structural schema only; validateRecipe also enforces semantic and resource limits. Playback checks context sample rate."
40
+ }
@@ -0,0 +1,74 @@
1
+ //#region src/recipe/generated.d.ts
2
+ type Mapping = {
3
+ readonly control: string;
4
+ readonly range: readonly [number, number];
5
+ readonly scale: "linear" | "exponential";
6
+ };
7
+ type Variation = {
8
+ readonly random: readonly [number, number];
9
+ };
10
+ type PointValue = number | Mapping | Variation;
11
+ type Value = PointValue | {
12
+ readonly points: ReadonlyArray<readonly [number, PointValue]>;
13
+ readonly curve: "linear" | "exponential";
14
+ };
15
+ type Frequency = Value;
16
+ type Parameter = {
17
+ readonly min: number;
18
+ readonly max: number;
19
+ readonly default: number;
20
+ readonly mode: "play";
21
+ } | {
22
+ readonly min: number;
23
+ readonly max: number;
24
+ readonly default: number;
25
+ readonly mode: "live";
26
+ readonly smoothing: number;
27
+ };
28
+ type Parameters = Readonly<Record<string, Parameter>>;
29
+ type Envelope = {
30
+ readonly attack: number;
31
+ readonly decay: number;
32
+ readonly sustain: number;
33
+ readonly release: number;
34
+ };
35
+ type Filter = {
36
+ readonly type: "filter";
37
+ readonly filter: "lowpass" | "highpass" | "bandpass";
38
+ readonly frequency: Frequency;
39
+ readonly q: number;
40
+ };
41
+ type Source = {
42
+ readonly type: "oscillator";
43
+ readonly waveform: "sine" | "triangle" | "sawtooth" | "square";
44
+ readonly frequency: Frequency;
45
+ } | {
46
+ readonly type: "noise";
47
+ readonly color: "white";
48
+ };
49
+ type Layer = {
50
+ readonly id: string;
51
+ readonly source: Source;
52
+ readonly gainDb: PointValue;
53
+ readonly envelope: Envelope;
54
+ readonly effects?: ReadonlyArray<Filter>;
55
+ };
56
+ type OneShotRecipe = {
57
+ readonly schemaVersion: 1;
58
+ readonly kind: "one-shot";
59
+ readonly duration: number;
60
+ readonly parameters?: Parameters;
61
+ readonly layers: ReadonlyArray<Layer>;
62
+ readonly effects?: ReadonlyArray<Filter>;
63
+ };
64
+ type SustainedRecipe = {
65
+ readonly schemaVersion: 1;
66
+ readonly kind: "sustained";
67
+ readonly parameters?: Parameters;
68
+ readonly layers: ReadonlyArray<Layer>;
69
+ readonly effects?: ReadonlyArray<Filter>;
70
+ };
71
+ type Recipe = OneShotRecipe | SustainedRecipe;
72
+ declare const recipeSchema: Readonly<Record<string, unknown>>;
73
+ //#endregion
74
+ export { Mapping as a, Parameters as c, Source as d, SustainedRecipe as f, recipeSchema as h, Layer as i, PointValue as l, Variation as m, Filter as n, OneShotRecipe as o, Value as p, Frequency as r, Parameter as s, Envelope as t, Recipe as u };
@@ -0,0 +1,96 @@
1
+ import { a as Mapping, c as Parameters, d as Source, h as recipeSchema, i as Layer, l as PointValue, m as Variation, n as Filter, p as Value, r as Frequency, s as Parameter, t as Envelope, u as Recipe } from "./generated-BQ3sfms_.js";
2
+ //#region src/recipe/validate.d.ts
3
+ interface RecipeIssue {
4
+ readonly code: string;
5
+ readonly path: string;
6
+ readonly message: string;
7
+ }
8
+ type ValidationResult = {
9
+ readonly ok: true;
10
+ readonly recipe: Recipe;
11
+ } | {
12
+ readonly ok: false;
13
+ readonly issues: readonly RecipeIssue[];
14
+ };
15
+ export declare function validateRecipe(input: unknown): ValidationResult;
16
+ export declare class AudioBitsError extends Error {
17
+ readonly code: string;
18
+ readonly issues: readonly RecipeIssue[];
19
+ constructor(code: string, message: string, issues?: readonly RecipeIssue[]);
20
+ }
21
+ export declare function defineSound<const R extends Recipe>(input: R): R;
22
+ //#endregion
23
+ //#region src/runtime/bus.d.ts
24
+ interface Bus {
25
+ readonly name: string;
26
+ readonly parent: Bus | null;
27
+ setParent(parent: Bus): void;
28
+ setGainDb(value: number, rampSeconds?: number): void;
29
+ setMuted(value: boolean): void;
30
+ dispose(): void;
31
+ }
32
+ //#endregion
33
+ //#region src/compiler/values.d.ts
34
+ type Controls = Readonly<Record<string, number>>;
35
+ //#endregion
36
+ //#region src/runtime/engine.d.ts
37
+ type AudioState = "idle" | "starting" | "running" | "suspended" | "interrupted" | "closed" | "disposed";
38
+ interface AudioOptions {
39
+ readonly maxVoices?: number;
40
+ readonly maxVoicesPerSound?: number;
41
+ readonly masterGainDb?: number;
42
+ }
43
+ type RecipeParameters<R extends Recipe> = "parameters" extends keyof R ? NonNullable<R["parameters"]> : never;
44
+ type NamedControls<K extends PropertyKey> = [K] extends [never] ? Readonly<Record<string, never>> : string extends K ? Controls : Readonly<Partial<Record<K, number>>>;
45
+ type RecipeControls<R extends Recipe = Recipe> = [RecipeParameters<R>] extends [never] ? Readonly<Record<string, never>> : NamedControls<keyof RecipeParameters<R>>;
46
+ type LiveControls<R extends Recipe = Recipe> = [RecipeParameters<R>] extends [never] ? Readonly<Record<string, never>> : string extends keyof RecipeParameters<R> ? Controls : NamedControls<{ [K in keyof RecipeParameters<R>]: RecipeParameters<R>[K] extends {
47
+ readonly mode: "live";
48
+ } ? K : never; }[keyof RecipeParameters<R>]>;
49
+ interface PlayOptions<R extends Recipe = Recipe> {
50
+ readonly at?: number;
51
+ readonly gainDb?: number;
52
+ readonly pan?: number;
53
+ readonly parameters?: RecipeControls<R>;
54
+ readonly seed?: number;
55
+ readonly bus?: Bus;
56
+ }
57
+ interface Voice<R extends Recipe = Recipe> {
58
+ readonly ended: Promise<void>;
59
+ readonly seed: number;
60
+ readonly parameters: RecipeControls<R>;
61
+ set(parameters: LiveControls<R>): void;
62
+ readonly state: "active" | "stopping" | "retiring" | "ended";
63
+ stop(): void;
64
+ }
65
+ interface Sound<R extends Recipe = Recipe> {
66
+ readonly recipe: R;
67
+ play(options?: PlayOptions<R>): Voice<R>;
68
+ dispose(): void;
69
+ }
70
+ interface AudioEngine {
71
+ readonly state: AudioState;
72
+ readonly counts: {
73
+ readonly active: number;
74
+ readonly retiring: number;
75
+ };
76
+ start(): Promise<void>;
77
+ suspend(): Promise<void>;
78
+ sound<const R extends Recipe>(input: R): Sound<R>;
79
+ sound(input: unknown): Sound;
80
+ readonly master: Bus;
81
+ bus(name: string, parent?: Bus): Bus;
82
+ readonly native: {
83
+ readonly context: AudioContext;
84
+ readonly output: AudioNode;
85
+ connect(node: AudioNode): () => void;
86
+ };
87
+ stopAll(options?: {
88
+ readonly tails?: "allow" | "cut";
89
+ }): void;
90
+ setMuted(muted: boolean): void;
91
+ subscribe(listener: (state: AudioState) => void): () => void;
92
+ dispose(): Promise<void>;
93
+ }
94
+ export declare function createAudio(options?: AudioOptions): AudioEngine;
95
+ //#endregion
96
+ export { type AudioEngine, type AudioOptions, type AudioState, type Bus, type Envelope, type Filter, type Frequency, type Layer, type LiveControls, type Mapping, type Parameter, type Parameters, type PlayOptions, type PointValue, type Recipe, type RecipeControls, type RecipeIssue, type Sound, type Source, type ValidationResult, type Value, type Variation, type Voice, recipeSchema };