sonarium 0.6.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +271 -0
  3. package/dist/index.d.ts +1000 -0
  4. package/dist/index.js +2716 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/sonarium.iife.js +236 -0
  7. package/dist/sonarium.iife.js.map +1 -0
  8. package/package.json +63 -0
  9. package/src/core/engine.ts +468 -0
  10. package/src/core/listener.ts +61 -0
  11. package/src/core/matter-voice.ts +240 -0
  12. package/src/core/profile.ts +313 -0
  13. package/src/core/room.ts +135 -0
  14. package/src/core/scanner.ts +176 -0
  15. package/src/core/voices.ts +222 -0
  16. package/src/index.ts +74 -0
  17. package/src/interact/activate.ts +56 -0
  18. package/src/interact/drag.ts +52 -0
  19. package/src/interact/keyboard.ts +48 -0
  20. package/src/interact/midi.ts +40 -0
  21. package/src/interact/motion.ts +61 -0
  22. package/src/interact/pointer.ts +65 -0
  23. package/src/interact/scroll.ts +43 -0
  24. package/src/math/chroma.ts +111 -0
  25. package/src/math/mapping.ts +100 -0
  26. package/src/math/matter.ts +201 -0
  27. package/src/math/modular.ts +99 -0
  28. package/src/math/pulse.ts +64 -0
  29. package/src/math/scales.ts +90 -0
  30. package/src/math/util.ts +16 -0
  31. package/src/spatial/backend.ts +166 -0
  32. package/src/spatial/bus.ts +114 -0
  33. package/src/spatial/decoder.ts +63 -0
  34. package/src/spatial/encoder.ts +54 -0
  35. package/src/spatial/field.ts +75 -0
  36. package/src/spatial/perceptual.ts +43 -0
  37. package/src/spatial/room-foa.ts +81 -0
  38. package/src/spatial/rotation.ts +73 -0
  39. package/src/spatial/sh.ts +38 -0
  40. package/src/spatial/sphere.ts +36 -0
  41. package/src/themes/index.ts +60 -0
  42. package/src/types.ts +157 -0
  43. package/src/ui/gate.ts +65 -0
@@ -0,0 +1,1000 @@
1
+ import * as Tone from 'tone';
2
+
3
+ /** MAPPING.md §0 — the five scales reachable from a hostname hash. */
4
+ declare const SCALES: Record<string, readonly number[]>;
5
+ interface SiteKey {
6
+ root: number;
7
+ scaleName: string;
8
+ scale: readonly number[];
9
+ label: string;
10
+ }
11
+ /** Deterministic per-domain musical identity (T8 audio branding). Same host → same key, always. */
12
+ declare function siteKey(hostname: string): SiteKey;
13
+ /** Parse "D dorian" / "Eb pentMinor" → SiteKey. Returns null when unrecognized. */
14
+ declare function parseKey(spec: string): SiteKey | null;
15
+ /**
16
+ * The Musical Quantizer (Invariant #2): degree ∈ [0,1] → MIDI note inside the key,
17
+ * laid across OCTAVES octaves from BASE_MIDI + root. stepOffset shifts by whole scale steps
18
+ * (sibling melodies S4, heading registers S6, kiki nudge G10).
19
+ */
20
+ declare function degreeToMidi(degree: number, key: SiteKey, stepOffset?: number): number;
21
+ declare const midiToFreq: (m: number) => number;
22
+ /**
23
+ * Walk N scale steps up/down from a midi note, staying on the key's pitch classes
24
+ * (the ribbon controller's quantizer — MODULAR.md §3; Invariant #2 holds under drag).
25
+ */
26
+ declare function stepInScale(midi: number, key: SiteKey, steps: number): number;
27
+ declare function midiToNoteName(m: number): string;
28
+
29
+ interface Rgb {
30
+ r: number;
31
+ g: number;
32
+ b: number;
33
+ a: number;
34
+ }
35
+ interface Hsl {
36
+ h: number;
37
+ s: number;
38
+ l: number;
39
+ }
40
+ interface Chroma {
41
+ /** 0 cool … 1 warm (desaturated colors regress to 0.5) */
42
+ warmth: number;
43
+ saturation: number;
44
+ luminance: number;
45
+ }
46
+ /** Computed styles emit rgb()/rgba(). Returns null for anything else (treat as no color). */
47
+ declare function parseCssColor(css: string): Rgb | null;
48
+ declare function rgbToHsl({ r, g, b }: Rgb): Hsl;
49
+ /** CHROMA.md §1 — cosine distance of hue from 30° (orange); greys regress to neutral 0.5. */
50
+ declare function warmthFromHue(h: number, s: number): number;
51
+ declare function chromaOf(rgb: Rgb | null): Chroma;
52
+ /** CH1 — dark UI sounds dark. */
53
+ declare const brightnessFromLuminance: (l: number) => number;
54
+ /** CH2 — bright lifts, gently. */
55
+ declare const velocityFromLuminance: (l: number) => number;
56
+ /** CH3 — warm = energetic onset (thesis T5). Multiplier on the woven attack. */
57
+ declare const attackScaleFromWarmth: (w: number) => number;
58
+ /** CH4 — warm = full-bodied: bonus on the sub oscillator level (subs only, not shimmer). */
59
+ declare const subBonusFromWarmth: (w: number) => number;
60
+ /** CH5 — vivid color = vivid spectrum: richness passed to genPartials (rolloff reduction). */
61
+ declare const richnessFromSaturation: (s: number) => number;
62
+ type ModeName = 'lydian' | 'mixolydian' | 'dorian' | 'pentMinor' | null;
63
+ /**
64
+ * CH6 — the palette chooses the mode; null = neutral, keep the hostname-hashed scale
65
+ * (identity unchanged). Root pitch-class always stays hostname-hashed (S9).
66
+ */
67
+ declare function modeFromPalette(pal: Chroma): ModeName;
68
+ /** Visual weight of the page: bg dominates, text tints. */
69
+ declare function pagePalette(bg: Chroma, text: Chroma): Chroma;
70
+ /** CH7 — warm rooms hum warmer. Multiplier on the ambience cutoff. */
71
+ declare const roomToneScaleFromWarmth: (w: number) => number;
72
+ /** CH8 — warm pages run slightly faster (consumed by PULSE.md P2). */
73
+ declare const tempoScaleFromWarmth: (w: number) => number;
74
+
75
+ type chroma_Chroma = Chroma;
76
+ type chroma_Hsl = Hsl;
77
+ type chroma_ModeName = ModeName;
78
+ type chroma_Rgb = Rgb;
79
+ declare const chroma_attackScaleFromWarmth: typeof attackScaleFromWarmth;
80
+ declare const chroma_brightnessFromLuminance: typeof brightnessFromLuminance;
81
+ declare const chroma_chromaOf: typeof chromaOf;
82
+ declare const chroma_modeFromPalette: typeof modeFromPalette;
83
+ declare const chroma_pagePalette: typeof pagePalette;
84
+ declare const chroma_parseCssColor: typeof parseCssColor;
85
+ declare const chroma_rgbToHsl: typeof rgbToHsl;
86
+ declare const chroma_richnessFromSaturation: typeof richnessFromSaturation;
87
+ declare const chroma_roomToneScaleFromWarmth: typeof roomToneScaleFromWarmth;
88
+ declare const chroma_subBonusFromWarmth: typeof subBonusFromWarmth;
89
+ declare const chroma_tempoScaleFromWarmth: typeof tempoScaleFromWarmth;
90
+ declare const chroma_velocityFromLuminance: typeof velocityFromLuminance;
91
+ declare const chroma_warmthFromHue: typeof warmthFromHue;
92
+ declare namespace chroma {
93
+ export { type chroma_Chroma as Chroma, type chroma_Hsl as Hsl, type chroma_ModeName as ModeName, type chroma_Rgb as Rgb, chroma_attackScaleFromWarmth as attackScaleFromWarmth, chroma_brightnessFromLuminance as brightnessFromLuminance, chroma_chromaOf as chromaOf, chroma_modeFromPalette as modeFromPalette, chroma_pagePalette as pagePalette, chroma_parseCssColor as parseCssColor, chroma_rgbToHsl as rgbToHsl, chroma_richnessFromSaturation as richnessFromSaturation, chroma_roomToneScaleFromWarmth as roomToneScaleFromWarmth, chroma_subBonusFromWarmth as subBonusFromWarmth, chroma_tempoScaleFromWarmth as tempoScaleFromWarmth, chroma_velocityFromLuminance as velocityFromLuminance, chroma_warmthFromHue as warmthFromHue };
94
+ }
95
+
96
+ type Wave = 'sine' | 'triangle' | 'sawtooth' | 'square';
97
+ type Role = 'toggle' | 'button' | 'link' | 'input' | 'heading' | 'media' | 'item' | 'container' | 'text';
98
+ type SynthKind = 'matter' | 'synth' | 'fm' | 'pluck' | 'membrane' | 'noise';
99
+ type Articulation = 'hit' | 'preview' | 'tick' | 'strum' | 'whisper' | 'toggle-on' | 'toggle-off' | 'motif' | 'echo' | 'phrase' | 'ribbon';
100
+ interface Rect {
101
+ x: number;
102
+ y: number;
103
+ w: number;
104
+ h: number;
105
+ }
106
+ /** Spherical source properties — the 聲球 (SPATIAL.md §2, §4). */
107
+ interface SphereProps {
108
+ /** Radians, AmbiX convention: +azimuth = left. */
109
+ azimuth: number;
110
+ /** Radians, + = up. */
111
+ elevation: number;
112
+ /** Apparent angular size σ ∈ [0,1] — point source … wraps around the listener. */
113
+ extent: number;
114
+ /** δ ∈ [0,1]: 1 = focused beam at the listener, 0 = omni radiator. */
115
+ directivity: number;
116
+ }
117
+ /** The spat5.oper surface (SPATIAL.md §5) — all ∈ [0,1], perceptually monotonic. */
118
+ interface PerceptualFactors {
119
+ presence: number;
120
+ roomPresence: number;
121
+ envelopment: number;
122
+ warmth: number;
123
+ brilliance: number;
124
+ }
125
+ /** The Matter weave, resolved per element (MATTER.md) — consumed by MatterVoice + backends. */
126
+ interface MatterVoiceParams {
127
+ matter: {
128
+ edge: number;
129
+ mass: number;
130
+ texture: number;
131
+ air: number;
132
+ };
133
+ /** 24 additive partial amplitudes, energy-normalized (the continuous spectrum). */
134
+ partials: number[];
135
+ transient: {
136
+ lengthS: number;
137
+ hpHz: number;
138
+ level: number;
139
+ };
140
+ breath: {
141
+ level: number;
142
+ bpRatio: number;
143
+ };
144
+ subShimmer: {
145
+ interval: number;
146
+ level: number;
147
+ };
148
+ glideS: number;
149
+ jitterCents: number;
150
+ envelope: {
151
+ attackS: number;
152
+ decayS: number;
153
+ sustain: number;
154
+ releaseScale: number;
155
+ };
156
+ filter: {
157
+ q: number;
158
+ biteAmount: number;
159
+ biteDecayS: number;
160
+ };
161
+ reverb: {
162
+ sendScale: number;
163
+ sendCutoffHz: number;
164
+ bloom: number;
165
+ extentBonus: number;
166
+ };
167
+ /** The modular patch (MODULAR.md): CSS plugs the cables. */
168
+ patch: {
169
+ fold: {
170
+ drive: number;
171
+ mix: number;
172
+ };
173
+ fm: {
174
+ index: number;
175
+ };
176
+ unison: {
177
+ detuneCents: number;
178
+ mix: number;
179
+ };
180
+ lfo: {
181
+ rateHz: number;
182
+ shape: 'sine' | 'square';
183
+ vibratoCents: number;
184
+ tremolo: number;
185
+ filterDepth: number;
186
+ };
187
+ portamentoS: number;
188
+ };
189
+ }
190
+ /** The contract between page reading (L1) and the audio substrate (L0). See ARCHITECTURE.md §2. */
191
+ interface SonicProfile {
192
+ role: Role;
193
+ rect: Rect;
194
+ pan: {
195
+ x: number;
196
+ y: number;
197
+ z: number;
198
+ };
199
+ sphere: SphereProps;
200
+ midi: number;
201
+ freqHz: number;
202
+ degree: number;
203
+ wave: Wave;
204
+ attack: number;
205
+ release: number;
206
+ durationS: number;
207
+ filterHz: number;
208
+ filterQ: number;
209
+ velocityScale: number;
210
+ reverbSend: number;
211
+ synthKind: SynthKind;
212
+ octaveShift: number;
213
+ /** The full Matter weave (MATTER.md) — always computed; the 'matter' voice consumes all of
214
+ * it, other synth kinds consume the reverb/filter threads. */
215
+ voice: MatterVoiceParams;
216
+ /** The Chroma weave (CHROMA.md): the element's color as mood. */
217
+ chroma: {
218
+ warmth: number;
219
+ saturation: number;
220
+ luminance: number;
221
+ };
222
+ /** Human-readable provenance of every parameter — describe() truth (PLAN.md Invariant #6). */
223
+ reasons: Record<string, string>;
224
+ }
225
+ interface VoiceRecipe {
226
+ synthKind: SynthKind;
227
+ /** When set, geometry does not override the waveform (e.g. heading bells stay bells). */
228
+ pinWave?: Wave;
229
+ octaveShift: number;
230
+ baseVelocity: number;
231
+ releaseScale: number;
232
+ }
233
+ interface Theme {
234
+ name: string;
235
+ roles: Partial<Record<Role, Partial<VoiceRecipe>>>;
236
+ defaults: VoiceRecipe;
237
+ }
238
+ interface SonariumOptions {
239
+ /** Root element to sonify. Default: document.body */
240
+ root?: Element;
241
+ /** Sound palette. Default 'aurora'. */
242
+ theme?: 'aurora' | 'mono' | 'paper' | Theme;
243
+ /** 'auto' = deterministic per-hostname key (MAPPING.md §0), or e.g. 'D dorian'. */
244
+ key?: string;
245
+ /** Listener mode: cursor as ears, or fixed center. Default 'pointer'. */
246
+ listener?: 'pointer' | 'center';
247
+ /** Ambience level 0..1 (room tone + sparkles). Default 0.12; 0 disables. */
248
+ ambient?: number;
249
+ /** Enable device tilt/shake drivers on mobile. Default true. */
250
+ motion?: boolean;
251
+ /** Autoplay-unlock & mute UI. 'chip' = floating control, 'none' = bring your own. */
252
+ gate?: 'chip' | 'none';
253
+ /** Master volume in dB. Default -10. */
254
+ volume?: number;
255
+ /** Max concurrent voices (pool size). Default 18, hard cap 24. */
256
+ maxVoices?: number;
257
+ /** 'hrtf' (default) or 'equalpower' for low-end devices. */
258
+ panning?: 'hrtf' | 'equalpower';
259
+ /**
260
+ * Spatial engine (SPATIAL.md): 'ambisonic' (default) encodes everything into one rotatable
261
+ * FOA field, Spat-style; 'panner' is the v0.1 per-voice HRTF path (also the auto-fallback).
262
+ */
263
+ spatial?: 'ambisonic' | 'panner';
264
+ /** Spat-style perceptual factors (presence, roomPresence, envelopment, warmth, brilliance). */
265
+ perceptual?: Partial<PerceptualFactors>;
266
+ /** Reverb: 'auto' sizes the room from viewport width, or a fixed decay in seconds. */
267
+ reverb?: 'auto' | number;
268
+ /** Respect prefers-reduced-motion by softening output. Default true. */
269
+ respectReducedMotion?: boolean;
270
+ /** Mirror every trigger to the first Web MIDI output (the page as a MIDI controller). Default false. */
271
+ midi?: boolean;
272
+ }
273
+ type SonariumEvent = 'start' | 'trigger' | 'mute' | 'dispose';
274
+ interface TriggerDetail {
275
+ el: Element;
276
+ profile: SonicProfile;
277
+ velocity: number;
278
+ articulation: Articulation;
279
+ }
280
+
281
+ declare const DEFAULT_FACTORS: PerceptualFactors;
282
+
283
+ /**
284
+ * L0 Acoustic Substrate — VoicePool (ARCHITECTURE.md §4).
285
+ * Profiles are data; voices are rented lanes configured at trigger time. Spatial placement is
286
+ * delegated to the active SpatialBackend (ambisonic field or per-voice panners — SPATIAL.md §6).
287
+ * Imports Tone only; never reads the DOM.
288
+ */
289
+
290
+ interface VoiceBuses {
291
+ dryIn: Tone.InputNode;
292
+ wetIn: Tone.InputNode;
293
+ }
294
+ declare class VoicePool {
295
+ private backend;
296
+ private maxVoices;
297
+ private lanes;
298
+ private sleepTimer;
299
+ constructor(backend: SpatialBackend, maxVoices: number);
300
+ private createSynth;
301
+ private createLane;
302
+ private acquire;
303
+ /** Configure a lane from the profile, then sound it. `when` lets strums schedule ahead. */
304
+ trigger(profile: SonicProfile, velocity: number, when?: number): void;
305
+ get activeCount(): number;
306
+ /** MODULAR.md §3 — a sustained ribbon voice. Returns null when no matter lane can gate. */
307
+ sustain(profile: SonicProfile, velocity: number): {
308
+ setFreq(hz: number, glideS: number): void;
309
+ release(): void;
310
+ } | null;
311
+ dispose(): void;
312
+ }
313
+
314
+ /**
315
+ * L0 Acoustic Substrate — the Room: master chain (spatial input → warmth/brilliance shelves →
316
+ * volume → limiter), viewport-sized reverb (S7/S8), ambience (I13), visibility fading (I14),
317
+ * mute. In 'panner' mode it also owns the classic dry/wet routing; in 'ambisonic' mode the
318
+ * AmbisonicBackend consumes `reverb` and `spatialIn` directly (SPATIAL.md §6). Imports Tone only.
319
+ */
320
+
321
+ interface RoomOptions {
322
+ volumeDb: number;
323
+ reverb: 'auto' | number;
324
+ ambient: number;
325
+ mode: 'panner' | 'ambisonic';
326
+ }
327
+ declare class Room {
328
+ private opts;
329
+ /** Classic v0.1 buses — meaningful in 'panner' mode (dryIn = spatialIn, wetIn = reverb). */
330
+ readonly buses: VoiceBuses;
331
+ /** Everything audible enters here (gets the perceptual EQ + limiter). */
332
+ readonly spatialIn: Tone.Gain;
333
+ readonly reverb: Tone.Reverb;
334
+ private master;
335
+ private limiter;
336
+ private lowShelf;
337
+ private highShelf;
338
+ private wetGain;
339
+ private noise;
340
+ private noiseFilter;
341
+ private noiseGain;
342
+ private rushGain;
343
+ private resizeTimer;
344
+ private mutedNow;
345
+ private toneScale;
346
+ constructor(opts: RoomOptions, vw: number, factors: PerceptualFactors);
347
+ setFactors(f: PerceptualFactors): void;
348
+ private roomParams;
349
+ /** Debounced: Tone.Reverb regenerates its impulse response when decay changes. */
350
+ resize(vw: number): void;
351
+ /** I13 — room tone (sparkles became the phrase engine, PULSE.md §3). toneScale = CH7 warmth. */
352
+ startAmbience(vw: number, level: number, toneScale?: number): void;
353
+ /** MATTER.md §2.2 — moving through the page moves air. Swells fast, decays in ~450 ms. */
354
+ rush(level: number): void;
355
+ /** I14 — never sound in a background tab. */
356
+ setHidden(hidden: boolean): void;
357
+ setMuted(muted: boolean): void;
358
+ dispose(): void;
359
+ }
360
+
361
+ /**
362
+ * Pure field rotation — SPATIAL.md §3.1. The 3×3 matrix acts on the (X, Y, Z) channel triple
363
+ * (W is rotation-invariant). Row-major: m[r][c], applied as X' = m00·X + m01·Y + m02·Z, etc.
364
+ * Composition R = Rz(yaw) · Ry(pitch) · Rx(roll). Angles in radians.
365
+ */
366
+ type Mat3 = [
367
+ [
368
+ number,
369
+ number,
370
+ number
371
+ ],
372
+ [
373
+ number,
374
+ number,
375
+ number
376
+ ],
377
+ [
378
+ number,
379
+ number,
380
+ number
381
+ ]
382
+ ];
383
+ /** Acts on column vectors (X, Y, Z)ᵀ in the AmbiX frame (+x fwd, +y left, +z up). */
384
+ declare function rotationMatrix(yaw: number, pitch: number, roll?: number): Mat3;
385
+ declare function applyMat3(m: Mat3, v: [number, number, number]): [number, number, number];
386
+ /**
387
+ * Look semantics (SPATIAL.md §3.1): the field rotation is the INVERSE of the head's intrinsic
388
+ * yaw-then-pitch rotation. Looking right by α (head Rz(−α)) and up by β (head Ry(−β)) gives
389
+ * field R = (Rz(−α)·Ry(−β))⁻¹ = Ry(β)·Rz(α). The sign/composition decision lives here and
390
+ * only here, pinned by tests: look right → right-side sources arrive frontward; look up →
391
+ * overhead sources arrive frontward.
392
+ */
393
+ declare function lookMatrix(yawRight: number, pitchUp: number): Mat3;
394
+
395
+ /**
396
+ * SpatialBackend — the seam between the voice pool and the spatial engine (SPATIAL.md §6).
397
+ * 'ambisonic' encodes every lane into the shared FOA field; 'panner' is the v0.1 per-voice
398
+ * HRTF path, kept as option and automatic fallback. Imports Tone only.
399
+ */
400
+
401
+ interface LaneOutput {
402
+ /** The lane's filter connects here. */
403
+ input: Tone.InputNode;
404
+ /** Place the voice in space from its profile (called at trigger time). */
405
+ setPlacement(profile: SonicProfile, when?: number): void;
406
+ dispose(): void;
407
+ }
408
+ interface SpatialBackend {
409
+ readonly kind: 'ambisonic' | 'panner';
410
+ createOutput(): LaneOutput;
411
+ setFactors(factors: PerceptualFactors): void;
412
+ /** Rotate the field (ambisonic) — no-op on the panner backend. */
413
+ setRotation(m: Mat3): void;
414
+ onViewport(vw: number): void;
415
+ dispose(): void;
416
+ }
417
+
418
+ declare class FieldRig {
419
+ private backend;
420
+ private mode;
421
+ private target;
422
+ private tilt;
423
+ private look;
424
+ private raf;
425
+ private running;
426
+ constructor(backend: SpatialBackend, mode: 'pointer' | 'center');
427
+ start(): void;
428
+ /** I9 — cursor (viewport-normalized 0..1) becomes look direction: right edge = look right. */
429
+ pointTo(tx: number, ty: number): void;
430
+ /** I10 — device attitude: γ right-tilt = look right, β beyond ~40° = look up/down. */
431
+ tiltTo(gamma: number, beta: number): void;
432
+ /** Public look API (head tracking / WebXR later plugs in here). Radians, right/up positive. */
433
+ lookAt(yawRight: number, pitchUp: number): void;
434
+ get state(): {
435
+ yaw: number;
436
+ pitch: number;
437
+ };
438
+ dispose(): void;
439
+ }
440
+
441
+ declare class ListenerRig {
442
+ private mode;
443
+ private target;
444
+ private tilt;
445
+ private pos;
446
+ private raf;
447
+ private running;
448
+ constructor(mode: 'pointer' | 'center');
449
+ start(): void;
450
+ /** I9 — pointer position (viewport-normalized 0..1) targets the ears. */
451
+ pointTo(tx: number, ty: number): void;
452
+ /** I10 — device tilt offsets the ears (γ → x ±4 m, β → y ±2 m). */
453
+ tiltTo(gamma: number, beta: number): void;
454
+ dispose(): void;
455
+ }
456
+
457
+ interface ProfileEnv {
458
+ root: Element;
459
+ key: SiteKey;
460
+ theme: Theme;
461
+ vw: number;
462
+ vh: number;
463
+ }
464
+
465
+ /**
466
+ * L1 Page Reading — eligibility, registry, observers, visible set (ARCHITECTURE.md §5).
467
+ * Imports DOM only; produces data + callbacks, triggers nothing audible itself.
468
+ */
469
+
470
+ interface Entry {
471
+ profile: SonicProfile | null;
472
+ visible: boolean;
473
+ }
474
+ interface ScannerCallbacks {
475
+ /** Fired when an element scrolls/loads into view after the initial scan (I7 whisper). */
476
+ onAppear: (el: Element) => void;
477
+ }
478
+ declare class Scanner {
479
+ private env;
480
+ private cb;
481
+ readonly registry: Map<Element, Entry>;
482
+ private io;
483
+ private mo;
484
+ private capWarned;
485
+ private initialScanDone;
486
+ private mutationTimer;
487
+ constructor(env: ProfileEnv, cb: ScannerCallbacks);
488
+ scan(): void;
489
+ private register;
490
+ private unregister;
491
+ isOff(el: Element): boolean;
492
+ private queueMutations;
493
+ /** Profile with lazy compute + cache (cheap path: geometry invalidation only nulls it). */
494
+ profileFor(el: Element): SonicProfile | null;
495
+ /** Geometry changed globally (scroll/resize): rects are stale, voices params survive. */
496
+ invalidateRects(): void;
497
+ visibleElements(): Element[];
498
+ /** The element (or nearest registered ancestor) the scanner knows about. */
499
+ resolve(target: Element | null): Element | null;
500
+ updateEnv(vw: number, vh: number): void;
501
+ dispose(): void;
502
+ }
503
+
504
+ interface ResolvedOptions {
505
+ root: Element;
506
+ theme: Theme;
507
+ key: SiteKey;
508
+ listener: 'pointer' | 'center';
509
+ ambient: number;
510
+ motion: boolean;
511
+ gate: 'chip' | 'none';
512
+ volume: number;
513
+ maxVoices: number;
514
+ panning: 'HRTF' | 'equalpower';
515
+ spatial: 'ambisonic' | 'panner';
516
+ reverb: 'auto' | number;
517
+ velocityFactor: number;
518
+ midi: boolean;
519
+ }
520
+ type State = 'idle' | 'armed' | 'running' | 'disposed';
521
+ declare class Engine {
522
+ readonly opts: ResolvedOptions;
523
+ state: State;
524
+ muted: boolean;
525
+ scanner: Scanner;
526
+ pool: VoicePool | null;
527
+ room: Room | null;
528
+ rig: ListenerRig | FieldRig | null;
529
+ backend: SpatialBackend | null;
530
+ factors: PerceptualFactors;
531
+ /** The page's mood (CHROMA.md §3) and pulse (PULSE.md §1), fixed at create(). */
532
+ readonly palette: Chroma;
533
+ readonly tempo: number;
534
+ private phraseLoop;
535
+ private activity;
536
+ private gate;
537
+ private env;
538
+ private detachers;
539
+ private listeners;
540
+ private unlockHandler;
541
+ private appearBucket;
542
+ private bucketTimer;
543
+ constructor(userOpts?: SonariumOptions);
544
+ private arm;
545
+ private removeUnlockListeners;
546
+ private starting;
547
+ start(): Promise<void>;
548
+ /** PULSE.md §3 — the ambience reads the layout as a score; scroll moves the playhead. */
549
+ private playPhrase;
550
+ /**
551
+ * SPATIAL.md §6 — ambisonic field by default; if its construction throws on an exotic
552
+ * browser, fall back to the v0.1 per-voice panner world rather than staying silent.
553
+ */
554
+ private buildAudioGraph;
555
+ /**
556
+ * MODULAR.md §3 — the ribbon controller: press-and-drag turns an element into a sustained
557
+ * voice swept across scale degrees (quantized — the glide between steps is the glissando).
558
+ */
559
+ ribbon(el: Element): {
560
+ move(dxPx: number): void;
561
+ release(): void;
562
+ } | null;
563
+ /** MATTER.md §2.2 — scroll drivers report air movement; rides the room-tone noise. */
564
+ airRush(level: number): void;
565
+ /** Live spat5.oper surface: adjust presence/roomPresence/envelopment/warmth/brilliance. */
566
+ setPerceptual(partial: Partial<PerceptualFactors>): void;
567
+ toggleMute(): void;
568
+ dispose(): void;
569
+ /**
570
+ * The single entry point for anything that wants to sound an element (Invariant: L2 drivers
571
+ * never touch Tone). Resolves the element to its profile and rents a voice.
572
+ * `transpose` shifts in semitones relative to the quantized pitch — callers must pass
573
+ * consonant intervals only (e.g. keyboard.ts FILL_INTERVALS).
574
+ */
575
+ excite(el: Element, velocity: number, articulation: Articulation, when?: number, transpose?: number): void;
576
+ /** I3/I11 — strum a set of elements left→right (or right→left for a reverse flick). */
577
+ strum(els: Element[], velocity: number, articulation?: Articulation, reverse?: boolean): void;
578
+ private whisper;
579
+ /** I12 — the page introduces itself: its largest landmarks, in DOM order, in the site key. */
580
+ private playIntroMotif;
581
+ /** Invariant #6 — explain why an element sounds the way it does. Works before start(). */
582
+ describe(el: Element): SonicProfile | null;
583
+ geometryChanged(): void;
584
+ roomResized(): void;
585
+ on(event: SonariumEvent, fn: (detail?: unknown) => void): () => void;
586
+ private emit;
587
+ }
588
+
589
+ /**
590
+ * L3 Composition — themes are plain data (MAPPING.md §3). Adding a theme means adding an
591
+ * object here; no engine code may special-case a theme name.
592
+ */
593
+
594
+ declare const THEMES: Record<string, Theme>;
595
+
596
+ declare const ROOM_HALF_W = 8;
597
+ declare const ROOM_HALF_H = 4;
598
+ /** G1 — element center x → azimuth (positionX, meters). */
599
+ declare function panX(rect: Rect, vw: number): number;
600
+ /** G2 — element center y → elevation (positionY, meters; screen-top = up). */
601
+ declare function panY(rect: Rect, vh: number): number;
602
+ /** G3 — vertical position → brightness tilt multiplier on the filter cutoff. */
603
+ declare function brightnessTilt(rect: Rect, vh: number): number;
604
+ /** G4 — log-normalized size 0 (tiny) … 1 (viewport-filling). */
605
+ declare function sizeT(rect: Rect, vw: number, vh: number): number;
606
+ /**
607
+ * G4 — pitch degree, inverted: big elements speak low (T2). Compressed into [0.1, 0.85] so
608
+ * sibling/heading step offsets have melodic headroom instead of clamping at the edges.
609
+ */
610
+ declare const degreeFromSize: (t: number) => number;
611
+ /** G5 — big elements speak louder (T2). */
612
+ declare const velocityFromSize: (t: number) => number;
613
+ /** G6 — roundness r ∈ [0,1]: 0 = razor corner (kiki), 1 = pill/circle (bouba). */
614
+ declare function roundness(radiusPx: number, rect: Rect): number;
615
+ /** G7 — the Kiki/Bouba waveform ladder. */
616
+ declare function waveFromRoundness(r: number): Wave;
617
+ /** G8 — sharp = plosive onset, round = soft bloom. Seconds. */
618
+ declare const attackFromRoundness: (r: number) => number;
619
+ /** G9 — sharp edges ring with resonance. */
620
+ declare const qFromRoundness: (r: number) => number;
621
+ /** G10 — sharper ↔ slightly higher (T1), in whole scale steps. */
622
+ declare const pitchNudgeFromRoundness: (r: number) => number;
623
+ /** G11 — elongation → duration: long elements sweep, squares tick. Seconds. */
624
+ declare function durationFromElongation(rect: Rect): number;
625
+ /** G13 — box-shadow blur lifts the element into the room's reverb. */
626
+ declare const sendFromShadowBlur: (blurPx: number) => number;
627
+ /** S1 — DOM depth → distance behind the sound stage (listener at z=0 facing −z). */
628
+ declare const zFromDepth: (depth: number) => number;
629
+ /** S2 — deeper nesting = duller voice. Hz. */
630
+ declare const cutoffFromDepth: (depth: number) => number;
631
+ /** S3 — deeper nesting = quieter (distance loudness, Zahorik 2002). */
632
+ declare const velocityFromDepth: (depth: number) => number;
633
+ /** S5 — positive z-index pulls the element toward the listener. Meters toward z=0. */
634
+ declare const zBonusFromZIndex: (z: number) => number;
635
+ /** S4 — siblings climb the scale, wrapping each octave (do-re-mi…-do) so long lists stay melodic. */
636
+ declare const stepsFromSiblingIndex: (i: number, scaleLen?: number) => number;
637
+ /** S6 — headings: h1 lands lowest/grandest. Whole scale steps (negative = down). */
638
+ declare const stepsFromHeadingLevel: (level: number) => number;
639
+ /** S7/S8 — the viewport is the room. */
640
+ declare function reverbFromViewport(vw: number): {
641
+ decay: number;
642
+ wet: number;
643
+ };
644
+ /** I13 — room tone color follows room size. Hz. */
645
+ declare const ambienceCutoffFromViewport: (vw: number) => number;
646
+
647
+ declare const mapping_ROOM_HALF_H: typeof ROOM_HALF_H;
648
+ declare const mapping_ROOM_HALF_W: typeof ROOM_HALF_W;
649
+ declare const mapping_ambienceCutoffFromViewport: typeof ambienceCutoffFromViewport;
650
+ declare const mapping_attackFromRoundness: typeof attackFromRoundness;
651
+ declare const mapping_brightnessTilt: typeof brightnessTilt;
652
+ declare const mapping_cutoffFromDepth: typeof cutoffFromDepth;
653
+ declare const mapping_degreeFromSize: typeof degreeFromSize;
654
+ declare const mapping_durationFromElongation: typeof durationFromElongation;
655
+ declare const mapping_panX: typeof panX;
656
+ declare const mapping_panY: typeof panY;
657
+ declare const mapping_pitchNudgeFromRoundness: typeof pitchNudgeFromRoundness;
658
+ declare const mapping_qFromRoundness: typeof qFromRoundness;
659
+ declare const mapping_reverbFromViewport: typeof reverbFromViewport;
660
+ declare const mapping_roundness: typeof roundness;
661
+ declare const mapping_sendFromShadowBlur: typeof sendFromShadowBlur;
662
+ declare const mapping_sizeT: typeof sizeT;
663
+ declare const mapping_stepsFromHeadingLevel: typeof stepsFromHeadingLevel;
664
+ declare const mapping_stepsFromSiblingIndex: typeof stepsFromSiblingIndex;
665
+ declare const mapping_velocityFromDepth: typeof velocityFromDepth;
666
+ declare const mapping_velocityFromSize: typeof velocityFromSize;
667
+ declare const mapping_waveFromRoundness: typeof waveFromRoundness;
668
+ declare const mapping_zBonusFromZIndex: typeof zBonusFromZIndex;
669
+ declare const mapping_zFromDepth: typeof zFromDepth;
670
+ declare namespace mapping {
671
+ export { mapping_ROOM_HALF_H as ROOM_HALF_H, mapping_ROOM_HALF_W as ROOM_HALF_W, mapping_ambienceCutoffFromViewport as ambienceCutoffFromViewport, mapping_attackFromRoundness as attackFromRoundness, mapping_brightnessTilt as brightnessTilt, mapping_cutoffFromDepth as cutoffFromDepth, mapping_degreeFromSize as degreeFromSize, mapping_durationFromElongation as durationFromElongation, mapping_panX as panX, mapping_panY as panY, mapping_pitchNudgeFromRoundness as pitchNudgeFromRoundness, mapping_qFromRoundness as qFromRoundness, mapping_reverbFromViewport as reverbFromViewport, mapping_roundness as roundness, mapping_sendFromShadowBlur as sendFromShadowBlur, mapping_sizeT as sizeT, mapping_stepsFromHeadingLevel as stepsFromHeadingLevel, mapping_stepsFromSiblingIndex as stepsFromSiblingIndex, mapping_velocityFromDepth as velocityFromDepth, mapping_velocityFromSize as velocityFromSize, mapping_waveFromRoundness as waveFromRoundness, mapping_zBonusFromZIndex as zBonusFromZIndex, mapping_zFromDepth as zFromDepth };
672
+ }
673
+
674
+ interface Matter {
675
+ /** boundary abruptness, kiki↔bouba: 1 − roundness */
676
+ edge: number;
677
+ /** size/weight: log-area sizeT */
678
+ mass: number;
679
+ /** surface noisiness/airiness: shadows, translucency, dashed borders, media */
680
+ texture: number;
681
+ /** distance into the room: normalized DOM depth */
682
+ air: number;
683
+ }
684
+ interface MatterVisuals {
685
+ roundness: number;
686
+ sizeT: number;
687
+ depth: number;
688
+ shadowBlurPx: number;
689
+ opacity: number;
690
+ dashedBorder: boolean;
691
+ isMedia: boolean;
692
+ backdropBlurPx: number;
693
+ /** MODULAR.md M7 — typography enters the weave (bold text carries weight). Default 0. */
694
+ massBonus?: number;
695
+ }
696
+ declare function deriveMatter(v: MatterVisuals): Matter;
697
+ declare const PARTIAL_COUNT = 24;
698
+ /**
699
+ * Continuous spectrum: EDGE sets the rolloff (bright↔pure), elongation sets hollowness
700
+ * (long thin elements = pipes = odd harmonics), richness (CHROMA.md CH5: saturation)
701
+ * subtracts from the rolloff exponent — vivid color = vivid spectrum. Normalized to Σa² = 1
702
+ * so the whole continuum sits at equal loudness.
703
+ */
704
+ declare function genPartials(edge: number, elongation: number, richness?: number): Float32Array;
705
+ interface TransientSpec {
706
+ /** noise burst length, seconds */
707
+ lengthS: number;
708
+ /** high-pass corner of the burst, Hz */
709
+ hpHz: number;
710
+ /** burst level relative to the voice (×velocity) */
711
+ level: number;
712
+ }
713
+ /** EDGE → the /k/ of kiki: a filtered click at onset. edge 0 → none. */
714
+ declare function transient(edge: number): TransientSpec;
715
+ interface BreathSpec {
716
+ /** sustained airy layer level (0 = pure tone) */
717
+ level: number;
718
+ /** band-pass center as a ratio of the fundamental */
719
+ bpRatio: number;
720
+ }
721
+ /** TEXTURE → breath: translucent/soft-shadowed things are airy. */
722
+ declare function breath(texture: number): BreathSpec;
723
+ /** TEXTURE → detune jitter in cents (rough surfaces are pitch-unstable). */
724
+ declare const detuneJitterCents: (texture: number) => number;
725
+ interface SubShimmerSpec {
726
+ /** semitone offset of osc B: −12 chest sub for massive, +12 sparkle for tiny */
727
+ interval: number;
728
+ level: number;
729
+ }
730
+ /** MASS → the second oscillator: big = chest, tiny = sparkle. */
731
+ declare function subShimmer(mass: number): SubShimmerSpec;
732
+ /** ROUND → glide: the bouba swoop-in. Returns portamento seconds (0 for sharp). */
733
+ declare const glideS: (edge: number) => number;
734
+ interface EnvelopeSpec {
735
+ attackS: number;
736
+ decayS: number;
737
+ sustain: number;
738
+ releaseScale: number;
739
+ }
740
+ /** EDGE strikes, MASS adds inertia. */
741
+ declare function envelopeWeave(edge: number, mass: number): EnvelopeSpec;
742
+ interface FilterWeaveSpec {
743
+ q: number;
744
+ /** transient brightness bite: cutoff multiplier at onset, decaying to 1 */
745
+ biteAmount: number;
746
+ biteDecayS: number;
747
+ }
748
+ /** EDGE rings and bites. (Base cutoff itself comes from AIR via S2 + tilt, as before.) */
749
+ declare function filterWeave(edge: number): FilterWeaveSpec;
750
+ interface ReverbWeaveSpec {
751
+ /** multiplier on the profile's base send */
752
+ sendScale: number;
753
+ /** low-pass on the way into the reverb: dark sources bloom dark. Hz */
754
+ sendCutoffHz: number;
755
+ /** 0 = arrive at full send immediately (sharp); 1 = swell from 35% over the note (round) */
756
+ bloom: number;
757
+ /** spatial extent bonus from airy texture */
758
+ extentBonus: number;
759
+ }
760
+ declare function reverbWeave(edge: number, mass: number, texture: number): ReverbWeaveSpec;
761
+ /** I8 — scroll velocity (px/ms) → air-rush gain on the room tone, decaying over ~400 ms. */
762
+ declare const airRushGain: (pxPerMs: number) => number;
763
+
764
+ type matter_BreathSpec = BreathSpec;
765
+ type matter_EnvelopeSpec = EnvelopeSpec;
766
+ type matter_FilterWeaveSpec = FilterWeaveSpec;
767
+ type matter_Matter = Matter;
768
+ type matter_MatterVisuals = MatterVisuals;
769
+ declare const matter_PARTIAL_COUNT: typeof PARTIAL_COUNT;
770
+ type matter_ReverbWeaveSpec = ReverbWeaveSpec;
771
+ type matter_SubShimmerSpec = SubShimmerSpec;
772
+ type matter_TransientSpec = TransientSpec;
773
+ declare const matter_airRushGain: typeof airRushGain;
774
+ declare const matter_breath: typeof breath;
775
+ declare const matter_deriveMatter: typeof deriveMatter;
776
+ declare const matter_detuneJitterCents: typeof detuneJitterCents;
777
+ declare const matter_envelopeWeave: typeof envelopeWeave;
778
+ declare const matter_filterWeave: typeof filterWeave;
779
+ declare const matter_genPartials: typeof genPartials;
780
+ declare const matter_glideS: typeof glideS;
781
+ declare const matter_reverbWeave: typeof reverbWeave;
782
+ declare const matter_subShimmer: typeof subShimmer;
783
+ declare const matter_transient: typeof transient;
784
+ declare namespace matter {
785
+ export { type matter_BreathSpec as BreathSpec, type matter_EnvelopeSpec as EnvelopeSpec, type matter_FilterWeaveSpec as FilterWeaveSpec, type matter_Matter as Matter, type matter_MatterVisuals as MatterVisuals, matter_PARTIAL_COUNT as PARTIAL_COUNT, type matter_ReverbWeaveSpec as ReverbWeaveSpec, type matter_SubShimmerSpec as SubShimmerSpec, type matter_TransientSpec as TransientSpec, matter_airRushGain as airRushGain, matter_breath as breath, matter_deriveMatter as deriveMatter, matter_detuneJitterCents as detuneJitterCents, matter_envelopeWeave as envelopeWeave, matter_filterWeave as filterWeave, matter_genPartials as genPartials, matter_glideS as glideS, matter_reverbWeave as reverbWeave, matter_subShimmer as subShimmer, matter_transient as transient };
786
+ }
787
+
788
+ /** P1+P2 — layout density × page warmth → bpm, clamped to [56, 116]. */
789
+ declare function tempoFromPage(elementCount: number, warmthScale: number): number;
790
+ declare const secondsPerBeat: (bpm: number) => number;
791
+ /**
792
+ * P3 — offset (seconds from now) to the next grid boundary of `gridS` seconds that is at
793
+ * least `minAheadS` away. `phaseS` = seconds elapsed since the grid's epoch.
794
+ */
795
+ declare function nextGridOffset(phaseS: number, gridS: number, minAheadS: number): number;
796
+ /** Strums step in 32nd notes — the same gesture, now in the groove. Seconds. */
797
+ declare const strumStepS: (bpm: number) => number;
798
+ /** Echoes answer on 8ths, one octave up, quiet (PULSE.md §2). */
799
+ declare const ECHO_TRANSPOSE = 12;
800
+ declare const ECHO_VELOCITY_SCALE = 0.22;
801
+ declare const ECHO_MIN_AHEAD_S = 0.08;
802
+ declare const echoGridS: (bpm: number) => number;
803
+ declare const DUCK_RECOVERY_MS = 2000;
804
+ /** Activity count decays linearly: fully forgiven after count·2 s of silence. */
805
+ declare const decayCount: (count: number, dtMs: number) => number;
806
+ /** Velocity multiplier: the 6th rapid repeat sits at ~40%, never below. */
807
+ declare const duckFactor: (count: number) => number;
808
+ declare const PHRASE_PROBABILITY = 0.55;
809
+ declare const PHRASE_MIN_NOTES = 2;
810
+ declare const PHRASE_MAX_NOTES = 4;
811
+ /** Reading order: rows of ~80 px, then left→right. Sort key for (top, left). */
812
+ declare const readingOrderKey: (top: number, left: number) => number;
813
+ /**
814
+ * Window the score by scroll progress: with n elements and a phrase of len, the playhead
815
+ * starts at `progress·(n − len)`.
816
+ */
817
+ declare function phraseWindow(n: number, len: number, progress: number): number;
818
+
819
+ declare const pulse_DUCK_RECOVERY_MS: typeof DUCK_RECOVERY_MS;
820
+ declare const pulse_ECHO_MIN_AHEAD_S: typeof ECHO_MIN_AHEAD_S;
821
+ declare const pulse_ECHO_TRANSPOSE: typeof ECHO_TRANSPOSE;
822
+ declare const pulse_ECHO_VELOCITY_SCALE: typeof ECHO_VELOCITY_SCALE;
823
+ declare const pulse_PHRASE_MAX_NOTES: typeof PHRASE_MAX_NOTES;
824
+ declare const pulse_PHRASE_MIN_NOTES: typeof PHRASE_MIN_NOTES;
825
+ declare const pulse_PHRASE_PROBABILITY: typeof PHRASE_PROBABILITY;
826
+ declare const pulse_decayCount: typeof decayCount;
827
+ declare const pulse_duckFactor: typeof duckFactor;
828
+ declare const pulse_echoGridS: typeof echoGridS;
829
+ declare const pulse_nextGridOffset: typeof nextGridOffset;
830
+ declare const pulse_phraseWindow: typeof phraseWindow;
831
+ declare const pulse_readingOrderKey: typeof readingOrderKey;
832
+ declare const pulse_secondsPerBeat: typeof secondsPerBeat;
833
+ declare const pulse_strumStepS: typeof strumStepS;
834
+ declare const pulse_tempoFromPage: typeof tempoFromPage;
835
+ declare namespace pulse {
836
+ export { pulse_DUCK_RECOVERY_MS as DUCK_RECOVERY_MS, pulse_ECHO_MIN_AHEAD_S as ECHO_MIN_AHEAD_S, pulse_ECHO_TRANSPOSE as ECHO_TRANSPOSE, pulse_ECHO_VELOCITY_SCALE as ECHO_VELOCITY_SCALE, pulse_PHRASE_MAX_NOTES as PHRASE_MAX_NOTES, pulse_PHRASE_MIN_NOTES as PHRASE_MIN_NOTES, pulse_PHRASE_PROBABILITY as PHRASE_PROBABILITY, pulse_decayCount as decayCount, pulse_duckFactor as duckFactor, pulse_echoGridS as echoGridS, pulse_nextGridOffset as nextGridOffset, pulse_phraseWindow as phraseWindow, pulse_readingOrderKey as readingOrderKey, pulse_secondsPerBeat as secondsPerBeat, pulse_strumStepS as strumStepS, pulse_tempoFromPage as tempoFromPage };
837
+ }
838
+
839
+ interface ModularVisuals {
840
+ /** seconds; 0 = no animation */
841
+ animationS: number;
842
+ borderStyle: 'solid' | 'dashed' | 'dotted' | 'none';
843
+ borderWidthPx: number;
844
+ /** seconds; 0 = no transition */
845
+ transitionS: number;
846
+ }
847
+ interface PatchParams {
848
+ fold: {
849
+ drive: number;
850
+ mix: number;
851
+ };
852
+ fm: {
853
+ index: number;
854
+ };
855
+ unison: {
856
+ detuneCents: number;
857
+ mix: number;
858
+ };
859
+ lfo: {
860
+ rateHz: number;
861
+ shape: 'sine' | 'square';
862
+ vibratoCents: number;
863
+ tremolo: number;
864
+ filterDepth: number;
865
+ };
866
+ portamentoS: number;
867
+ }
868
+ /** M1 — heavy borders saturate: drive ∈ [0,1], mix follows. */
869
+ declare function foldFromBorder(borderWidthPx: number, edge: number): PatchParams['fold'];
870
+ /** M2 — rough sharp surfaces ring metallic (audio-rate FM index, × carrier Hz in-voice). */
871
+ declare const fmIndex: (texture: number, edge: number) => number;
872
+ /** M3 — big elements are thick, not just low. */
873
+ declare function unisonFromMass(mass: number): PatchParams['unison'];
874
+ /** M4/M5 — the LFO patch bay: animation owns it; dashed/dotted borders chop; else idle. */
875
+ declare function lfoFromCss(animationS: number, borderStyle: ModularVisuals['borderStyle']): PatchParams['lfo'];
876
+ /** M6 — elements that ease visually ease in pitch. Seconds added to the glide. */
877
+ declare const portamentoFromTransition: (transitionS: number) => number;
878
+ /** M7 — typography enters the weave: bold text carries weight (MASS bonus). */
879
+ declare const massBonusFromFontWeight: (weight: number) => number;
880
+ declare function patchFrom(matter: {
881
+ edge: number;
882
+ mass: number;
883
+ texture: number;
884
+ }, visuals: ModularVisuals): PatchParams;
885
+ /**
886
+ * The wavefolder transfer curve: y = sin(2.5·(π/2)·x). Near-linear for quiet signals,
887
+ * folding for hot ones — the amp envelope sweeps the spectrum through the fold every note.
888
+ */
889
+ declare function foldCurve(samples?: number): Float32Array;
890
+ /** Drag-glissando ribbon (MODULAR.md §3): horizontal travel → scale-step offset, ±1 octave. */
891
+ declare function ribbonSteps(dxPx: number, vw: number, scaleLen: number): number;
892
+
893
+ type modular_ModularVisuals = ModularVisuals;
894
+ type modular_PatchParams = PatchParams;
895
+ declare const modular_fmIndex: typeof fmIndex;
896
+ declare const modular_foldCurve: typeof foldCurve;
897
+ declare const modular_foldFromBorder: typeof foldFromBorder;
898
+ declare const modular_lfoFromCss: typeof lfoFromCss;
899
+ declare const modular_massBonusFromFontWeight: typeof massBonusFromFontWeight;
900
+ declare const modular_patchFrom: typeof patchFrom;
901
+ declare const modular_portamentoFromTransition: typeof portamentoFromTransition;
902
+ declare const modular_ribbonSteps: typeof ribbonSteps;
903
+ declare const modular_unisonFromMass: typeof unisonFromMass;
904
+ declare namespace modular {
905
+ export { type modular_ModularVisuals as ModularVisuals, type modular_PatchParams as PatchParams, modular_fmIndex as fmIndex, modular_foldCurve as foldCurve, modular_foldFromBorder as foldFromBorder, modular_lfoFromCss as lfoFromCss, modular_massBonusFromFontWeight as massBonusFromFontWeight, modular_patchFrom as patchFrom, modular_portamentoFromTransition as portamentoFromTransition, modular_ribbonSteps as ribbonSteps, modular_unisonFromMass as unisonFromMass };
906
+ }
907
+
908
+ /**
909
+ * Pure spherical-harmonic encoding — SPATIAL.md §1–2. AmbiX: ACN order [W, Y, Z, X],
910
+ * SN3D normalization, +x forward, +y left, +z up, +azimuth left. No DOM, no Tone.
911
+ */
912
+ declare const DEG: number;
913
+ interface FoaGains {
914
+ w: number;
915
+ y: number;
916
+ z: number;
917
+ x: number;
918
+ }
919
+ /**
920
+ * First-order encode of a plane wave from (azimuth, elevation), with extent σ blending the
921
+ * directional components into the omni channel (SPATIAL.md §2). Angles in radians.
922
+ */
923
+ declare function foaGains(azimuth: number, elevation: number, extent?: number): FoaGains;
924
+ /** Unit direction vector for (azimuth, elevation) in the AmbiX frame. */
925
+ declare function unitVector(azimuth: number, elevation: number): [number, number, number];
926
+
927
+ /**
928
+ * Pure FOA decoding — SPATIAL.md §3.2. Sampling decoder with max-rE weighting over a fixed
929
+ * virtual-speaker layout; each speaker is later rendered by one native HRTF panner.
930
+ */
931
+
932
+ interface Speaker {
933
+ /** Unit direction in the AmbiX frame. */
934
+ dir: [number, number, number];
935
+ label: string;
936
+ }
937
+ /** Cube vertices: full-sphere coverage including elevation, symmetric, M = 8. */
938
+ declare const CUBE_LAYOUT: Speaker[];
939
+ interface DecodeRow {
940
+ /** Per-ACN gains [w, y, z, x] for one speaker: s = w·W + y·Y + z·Z + x·X. */
941
+ w: number;
942
+ y: number;
943
+ z: number;
944
+ x: number;
945
+ }
946
+ /**
947
+ * Sampling decode matrix for SN3D input: sm = (1/M)·[g0·W + 3·g1·(X·xm + Y·ym + Z·zm)].
948
+ * The 3 re-normalizes first-order SN3D to N3D inside the projection.
949
+ */
950
+ declare function decodeMatrix(layout: Speaker[]): DecodeRow[];
951
+ /** Decode an encoded source to speaker signals (used by tests and the worklet path later). */
952
+ declare function decodeGains(rows: DecodeRow[], g: FoaGains): number[];
953
+
954
+ declare const AZ_MAX: number;
955
+ declare const EL_MAX: number;
956
+ /** SP1/SP2 — screen position → direction. Screen-right = −azimuth (sign tested). */
957
+ declare function sphereFromRect(rect: Rect, vw: number, vh: number): {
958
+ azimuth: number;
959
+ elevation: number;
960
+ };
961
+ /** SP4 — big elements wrap around the listener (T2 extended into space). */
962
+ declare function extentFromSize(sizeT: number): number;
963
+ /** SP5 — Kiki/Bouba in the spatial domain: sharp beams, round radiates. */
964
+ declare function directivityFromRoundness(roundness: number): number;
965
+ /** SP6 — early-reflection time scale follows the room (viewport). */
966
+ declare function reflectionScaleFromViewport(vw: number): number;
967
+
968
+ declare const sphere_AZ_MAX: typeof AZ_MAX;
969
+ declare const sphere_EL_MAX: typeof EL_MAX;
970
+ declare const sphere_directivityFromRoundness: typeof directivityFromRoundness;
971
+ declare const sphere_extentFromSize: typeof extentFromSize;
972
+ declare const sphere_reflectionScaleFromViewport: typeof reflectionScaleFromViewport;
973
+ declare const sphere_sphereFromRect: typeof sphereFromRect;
974
+ declare namespace sphere {
975
+ export { sphere_AZ_MAX as AZ_MAX, sphere_EL_MAX as EL_MAX, sphere_directivityFromRoundness as directivityFromRoundness, sphere_extentFromSize as extentFromSize, sphere_reflectionScaleFromViewport as reflectionScaleFromViewport, sphere_sphereFromRect as sphereFromRect };
976
+ }
977
+
978
+ /**
979
+ * Sonarium — drop-in acoustic UX.
980
+ * One script tag turns any webpage into a spatial sound field: layout becomes a stereo stage,
981
+ * geometry becomes timbre (Kiki/Bouba), the DOM tree becomes depth and harmony, the viewport
982
+ * becomes a room, and your cursor becomes your ears.
983
+ *
984
+ * import { create } from 'sonarium' // ESM
985
+ * const space = create({ theme: 'aurora' })
986
+ *
987
+ * <script src=".../sonarium.iife.js" data-auto></script> // zero-code
988
+ *
989
+ * Docs: https://github.com/frank890417/sonarium — start with docs/PLAN.md.
990
+ */
991
+
992
+ declare const version = "0.6.0";
993
+
994
+ /**
995
+ * Create a Sonarium instance. Safe to call before any user gesture: audio arms itself and
996
+ * starts on the first pointer/key interaction (browser autoplay policy treated as a feature).
997
+ */
998
+ declare function create(options?: SonariumOptions): Engine;
999
+
1000
+ export { type Articulation, CUBE_LAYOUT, DEFAULT_FACTORS, DEG, Engine, type PerceptualFactors, type Role, SCALES, type SonariumEvent, type SonariumOptions, type SonicProfile, type SphereProps, type SynthKind, THEMES, type Theme, type TriggerDetail, type VoiceRecipe, type Wave, applyMat3, chroma, create, decodeGains, decodeMatrix, degreeToMidi, foaGains, lookMatrix, mapping, matter, midiToFreq, midiToNoteName, modular, parseKey, pulse, rotationMatrix, siteKey, sphere as sphereMapping, stepInScale, unitVector, version };