@yali-engine/audio 0.0.0-stage → 0.4.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.
@@ -0,0 +1,174 @@
1
+ import { type AssetLoader, type Component, type World, type YaliPlugin } from '@yali-engine/core';
2
+ import { type AudioClips, AudioMixer, type ClipId } from './audio.ts';
3
+ /** `audio.Source`'s fields (ADR-0096). */
4
+ export declare const SOURCE_FIELDS: {
5
+ readonly clip: "u32";
6
+ readonly volume: "f32";
7
+ readonly pitch: "f32";
8
+ readonly loop: "u8";
9
+ /** 1: playing (set it to start); the plugin sets 0 when a clip that does not loop ends. */
10
+ readonly play: "u8";
11
+ readonly spatial: "u8";
12
+ readonly distanceModel: "u8";
13
+ readonly refDistance: "f32";
14
+ readonly maxDistance: "f32";
15
+ readonly rolloff: "f32";
16
+ readonly coneInner: "f32";
17
+ readonly coneOuter: "f32";
18
+ readonly coneOuterGain: "f32";
19
+ };
20
+ /** `audio.ReverbZone`'s fields (ADR-0116). */
21
+ export declare const REVERB_ZONE_FIELDS: {
22
+ /** 0: a sphere of radius `x`; 1: a box of half extents `x`, `y`, `z` (turned with the entity). */
23
+ readonly shape: "u8";
24
+ readonly x: "f32";
25
+ readonly y: "f32";
26
+ readonly z: "f32";
27
+ /** How far inside the edge it reaches full strength, m. */
28
+ readonly fade: "f32";
29
+ /** Seconds to fall 60 dB. */
30
+ readonly decay: "f32";
31
+ /** Its level at full strength (linear). */
32
+ readonly wet: "f32";
33
+ };
34
+ /** The `audio.Source`, `audio.Listener` and `audio.ReverbZone` components (the plugin registers them). */
35
+ export interface AudioComponents {
36
+ readonly Source: Component;
37
+ /** On the entity the scene is heard from (its `GlobalTransform`; forward −Z); none: the camera. */
38
+ readonly Listener: Component;
39
+ /** A space with its own reverb (ADR-0116), heard while the listener is inside it. */
40
+ readonly ReverbZone: Component;
41
+ }
42
+ /** Registers the audio components on `world` (the same ids every call). */
43
+ export declare function registerAudioComponents(world: World): AudioComponents;
44
+ /** What `reverbZone` takes. */
45
+ export interface ReverbZoneOptions {
46
+ /** A sphere of this radius (m), or a box of these half extents (m). */
47
+ readonly sphere?: number;
48
+ readonly box?: readonly [number, number, number];
49
+ /** Seconds the reverb takes to fall 60 dB (default 1.5: a hall; 0.4 a small room, 4 a cathedral). */
50
+ readonly decay?: number;
51
+ /** Its level once inside (default 0.4). */
52
+ readonly wet?: number;
53
+ /** Metres inside the edge at which it reaches full strength (default 2). */
54
+ readonly fade?: number;
55
+ }
56
+ /**
57
+ * `audio.ReverbZone` values (ADR-0116): put on an entity with a transform; while the listener is
58
+ * inside, every spatial sound also plays through this reverb, faded in over `fade` metres. Zones may
59
+ * overlap (each is heard at its own weight).
60
+ *
61
+ * @example
62
+ * world.insert(hall, audio.ReverbZone, reverbZone({ box: [12, 6, 20], decay: 2.2, wet: 0.5 }));
63
+ */
64
+ export declare function reverbZone(o: ReverbZoneOptions): Record<string, number>;
65
+ /**
66
+ * An asset-server loader for clips (ADR-0116): `assets.load<ClipId>('sounds/step.ogg')` decodes into
67
+ * `clips`, and a hot reload of the file swaps the sound under the same id. `audioPlugin` registers
68
+ * it with the app's asset server itself when there is one.
69
+ */
70
+ export declare function audioClipLoader(clips: AudioClips): AssetLoader<ClipId>;
71
+ /** What `audioSource` takes; every field optional. */
72
+ export interface SourceOptions {
73
+ readonly volume?: number;
74
+ readonly pitch?: number;
75
+ readonly loop?: boolean;
76
+ /** Start playing on insert (default true). */
77
+ readonly play?: boolean;
78
+ /** Placed in 3D (default true); false: heard as it is (music, UI). */
79
+ readonly spatial?: boolean;
80
+ readonly distanceModel?: 'linear' | 'inverse' | 'exponential';
81
+ /** Full volume up to this distance (default 1 m). */
82
+ readonly refDistance?: number;
83
+ /** Where `linear` falls to its end (default 100 m). */
84
+ readonly maxDistance?: number;
85
+ readonly rolloff?: number;
86
+ /** A directional source: inner and outer cone angles in degrees, the gain outside (default: none). */
87
+ readonly cone?: {
88
+ readonly inner: number;
89
+ readonly outer: number;
90
+ readonly outerGain: number;
91
+ };
92
+ }
93
+ /**
94
+ * `audio.Source` values (ADR-0096) for clip `clip`: put on an entity with a transform; it plays
95
+ * from there. Set `play` to 0 to stop it, 1 to start it again.
96
+ *
97
+ * @example
98
+ * world.insert(engine, audio.Source, audioSource(hum, { loop: true, refDistance: 3 }));
99
+ */
100
+ export declare function audioSource(clip: number, o?: SourceOptions): Record<string, number>;
101
+ /** `@yali-engine/audio` options. */
102
+ export interface AudioPluginOptions {
103
+ /** The context to play through (default a new AudioContext; tests pass an OfflineAudioContext). */
104
+ readonly context?: BaseAudioContext;
105
+ /** Master volume (default 1). */
106
+ readonly volume?: number;
107
+ /** Resume a suspended AudioContext on the first pointer or key press (default true; browsers start it suspended). */
108
+ readonly resumeOnGesture?: boolean;
109
+ /** Register the clip loader with the app's asset server (default true when there is one; ADR-0116). */
110
+ readonly assets?: boolean;
111
+ /** Makes a streamed clip's element (tests; default `new Audio(url)`). */
112
+ readonly element?: (url: string) => HTMLMediaElement;
113
+ /**
114
+ * Doppler (ADR-0127, default on): a moving source or listener plays faster or slower, from their
115
+ * velocities (each entity's move between frames, smoothed; a jump faster than `maxSpeed` is a
116
+ * teleport, not a speed). `scale` 0 turns it off, 1 is physical; `speedOfSound` in m/s.
117
+ */
118
+ readonly doppler?: boolean | {
119
+ readonly scale?: number;
120
+ readonly speedOfSound?: number;
121
+ readonly maxSpeed?: number;
122
+ };
123
+ /**
124
+ * Occlusion (ADR-0127, default on when the app has physics): a ray from the listener to each
125
+ * playing spatial source through `physics.raycasts`; blocked, the source is muffled and quieter
126
+ * (`AudioMixer.occlusionHz`, `.occlusionGain`), gliding in and out. The ray starts and stops
127
+ * `margin` m short (the listener's and the source's own colliders); `mask` picks the colliders that
128
+ * block sound. `query` replaces physics: rays [origin, dir, distance], one blocked flag each.
129
+ *
130
+ * ADR-0130: with physics, how much is in the way counts. A second ray from the source back finds the
131
+ * last wall's far side, and the solid span between the two hits occludes `1 − exp(−span / thickness)`
132
+ * (`thickness` 0.25 m: a 5 cm partition muffles a little, a 30 cm wall nearly fully). Diffraction: a
133
+ * source occluded now also casts four detours through points `radius` m around it (across the line
134
+ * to the listener); each detour clear both ways lets `diffraction / 4` of the sound around the wall.
135
+ * `diffraction: 0` turns it off. With `query` (blocked or clear only), occlusion stays all or nothing.
136
+ */
137
+ readonly occlusion?: boolean | {
138
+ readonly mask?: number;
139
+ readonly margin?: number;
140
+ readonly thickness?: number;
141
+ readonly diffraction?: number;
142
+ readonly radius?: number;
143
+ readonly query?: (rays: readonly (readonly [readonly [number, number, number], readonly [number, number, number], number])[]) => readonly boolean[];
144
+ };
145
+ /**
146
+ * Early reflections (ADR-0130, default on when the app has physics): six rays a frame from the
147
+ * listener (±x, ±y, ±z, up to `distance` m) find the nearest walls, and each puts a delayed echo of
148
+ * every spatial sound in the mix (2d / c later, `gain` / (1 + 2d) as loud, panned to its side), so a
149
+ * corridor sounds different from a hall. `mask` picks the colliders that reflect.
150
+ */
151
+ readonly reflections?: boolean | {
152
+ readonly distance?: number;
153
+ readonly gain?: number;
154
+ readonly mask?: number;
155
+ };
156
+ }
157
+ /** The `audio` resource. */
158
+ export interface AudioContextResource extends AudioComponents {
159
+ readonly context: BaseAudioContext;
160
+ readonly clips: AudioClips;
161
+ readonly mixer: AudioMixer;
162
+ }
163
+ /**
164
+ * Spatial audio (ADR-0096): every entity with `audio.Source` and a `GlobalTransform` plays its
165
+ * clip from where it is, heard from the `audio.Listener` entity or, without one, the render
166
+ * camera. Runs in Extract, after transforms propagate. Provides `audio`; tools `audio.voices`
167
+ * and `audio.volume`.
168
+ *
169
+ * @example
170
+ * App.create({ core, plugins: [renderPlugin({ canvas }), audioPlugin(), transformsPlugin(), game] });
171
+ * const audio = app.resource<AudioContextResource>('audio');
172
+ * world.insert(e, audio.Source, audioSource(await audio.clips.load('hum.ogg'), { loop: true }));
173
+ */
174
+ export declare function audioPlugin(options?: AudioPluginOptions): YaliPlugin;