@adatechnology/game-audio 0.7.0 → 0.9.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.
@@ -1,340 +1,119 @@
1
+ import { B as BusId, q as SampleEnvironment, p as SampleCrossover, F as Falloff, c as SampleSound, t as SampleTier, h as BuildOrder, C as Curve, b as SoundRecipe, S as SoundRegistry, a as SoundBank } from './bank-Dsj1VAht.js';
1
2
  import { Vec3, DeferredQueue, TestableModule } from '@adatechnology/game-contracts';
2
3
 
3
- /**
4
- * Sound recipes are data. A recipe says which nodes a sound has and which numbers drive them; ranges are drawn when the sound is planned, in
5
- * the order the fields are written (see `planSound`), so a recipe ported from a team routine draws its random numbers in the routine's order.
6
- */
7
- declare const BUS_IDS: readonly ["weapons", "voices", "world", "ambience", "weather", "ui"];
8
- type BusId = (typeof BUS_IDS)[number];
9
- /** A named number: from `PlayOptions.params` or the recipe's `vars`, times `scale`, plus `add`. */
10
- type ParamRef = {
11
- readonly param: string;
12
- readonly scale?: number;
13
- readonly add?: number;
14
- };
15
- /** A number, a `[min, max]` drawn uniformly (`min + random × (max − min)`), or a named number. */
16
- type Range = number | readonly [min: number, max: number] | ParamRef;
17
- /** Seconds from the layer start: a range, the layer `'duration'`, or `'0.6d'` (0.6 × the layer duration). */
18
- type Time = Range | 'duration' | `${number}d`;
19
- type Curve = 'linear' | 'exp';
20
- /**
21
- * How an AudioParam moves:
22
- * - `{ set }` assigns `.value`;
23
- * - `{ set, to, curve, over }` assigns `.value`, then ramps to `to` at `over`;
24
- * - `{ from }` calls `setValueAtTime(from)` at the start; with `to`, `curve` and `over` it then ramps to `to` (`'from'` is the value drawn for `from`: the art scene ramps a tone back to its own start when it gives no end).
25
- */
26
- type Automation = {
27
- readonly set: Range;
28
- readonly to?: Range | 'from';
29
- readonly curve?: Curve;
30
- readonly over?: Time;
31
- } | {
32
- readonly from: Range;
33
- readonly to?: Range | 'from';
34
- readonly curve?: Curve;
35
- readonly over?: Time;
36
- };
37
- /**
38
- * One gain point: time (seconds, or `'0.6d'`), value and how it is reached (`set` = `setValueAtTime`, or a ramp). The value is multiplied by the
39
- * layer level (volume, distance and layer gain) except when the fourth item is `'fixed'` (the floor a ramp falls to, such as 0.001).
40
- */
41
- type EnvelopePoint = readonly [time: number | `${number}d`, value: number, curve: 'set' | Curve] | readonly [time: number | `${number}d`, value: number, curve: 'set' | Curve, fixed: 'fixed'];
42
- type Envelope = readonly EnvelopePoint[];
43
- type FilterSpec = {
44
- readonly type: BiquadFilterType;
45
- readonly frequency: Automation;
46
- readonly q?: Range;
47
- /** The BiquadFilter gain (shelf and peaking types). */
48
- readonly gain?: Range;
49
- /** A gain node after the filter, for a parallel bank where each band has its own level (the game's scream formants). */
50
- readonly level?: Range;
51
- };
52
- type FilterBank = {
53
- readonly mode: 'serial' | 'parallel';
54
- readonly bank: readonly FilterSpec[];
55
- };
56
- /** An LFO. `depth` is absolute, or a factor of the source start frequency. With `base`, gain modulation goes through its own gain stage (the game rotor). */
57
- type Modulator = {
58
- readonly target: 'gain' | 'frequency';
59
- /** Left out, the oscillator keeps its default (sine) and no type is set. */
60
- readonly wave?: OscillatorType;
61
- readonly rate: Range;
62
- readonly depth: Range | {
63
- readonly scaleOf: 'frequency';
64
- readonly factor: number;
65
- };
66
- readonly base?: number;
67
- };
68
- /**
69
- * Noise from the engine's seeded buffer. `offset` is where playback starts in it: `'random'` (random × `offsetScale`, default 1), `'fit'`
70
- * (random × (buffer length − duration)) or seconds. `loop` sets the source to loop (the art scene sets it on every noise source).
71
- */
72
- type NoiseSource = {
73
- readonly kind: 'noise';
74
- readonly offset?: 'random' | 'fit' | number;
75
- readonly offsetScale?: number;
76
- readonly loop?: boolean;
77
- };
78
- type OscSource = {
79
- readonly kind: 'osc';
80
- readonly wave: OscillatorType;
81
- readonly frequency: Automation;
82
- };
83
- /** The art scene's `voice`: an oscillator with vibrato through formant bands, plus a band of breath noise, summed and boosted. */
84
- type FormantVoiceSource = {
85
- readonly kind: 'formant_voice';
86
- readonly wave: OscillatorType;
87
- readonly frequency: Automation;
88
- /** Vibrato rate, and depth as a factor of the start frequency. */
89
- readonly vibrato: {
90
- readonly rate: Range;
91
- readonly depth: number;
92
- };
93
- readonly formants: readonly {
94
- readonly frequency: Range;
95
- readonly q: number;
96
- }[];
97
- readonly breath: {
98
- readonly frequency: Range;
99
- readonly q: number;
100
- readonly gain: Range;
101
- };
102
- readonly mix: number;
103
- };
104
- type LayerSource = NoiseSource | OscSource | FormantVoiceSource;
105
- type Layer = {
106
- /** Start, seconds after the sound starts. */
107
- readonly at?: Range;
108
- /** Overrides the recipe duration for this layer. */
109
- readonly duration?: Range;
110
- /** Seconds the source keeps running after the duration (the `+ 0.05` of the team routines). Default: the recipe's, else 0.05. */
111
- readonly tail?: number;
112
- /** The layer played `count` times, `spacing` seconds apart (the game's animal calls). */
113
- readonly repeat?: {
114
- readonly count: number;
115
- readonly spacing: number;
116
- };
117
- /** Layer level before distance. Default 1. */
118
- readonly gain?: Range;
119
- /** Send to the reverb, 0..1 or more (the art scene uses up to 1.2). */
120
- readonly wet?: number;
121
- readonly source: LayerSource;
122
- readonly filters?: FilterBank;
123
- readonly modulators?: readonly Modulator[];
124
- readonly envelope: Envelope;
4
+ /** Where the ears are. `yaw` turns around y like a three.js camera: the right ear points at `(cos yaw, 0, −sin yaw)`. */
5
+ type Listener = {
6
+ readonly position: Vec3;
7
+ readonly yaw: number;
125
8
  };
9
+ type DistanceMode = 'xz' | '3d';
10
+ declare const DEFAULT_LISTENER: Listener;
11
+ /** The art scene clamps the pan to ±0.85 and never divides by less than 2 m (cena L1319). */
12
+ declare const PAN_LIMIT = 0.85;
13
+ declare const PAN_MIN_DISTANCE = 2;
14
+ /** Distance from the listener: on the ground (`xz`, the teams' choice) or in 3D. */
15
+ declare function listenerDistance(listener: Listener, position: Vec3, mode?: DistanceMode): number;
16
+ /** The art scene's `panFor`: the side the sound is on, −0.85 (left) to 0.85 (right). No HRTF, for cost. */
17
+ declare function panFor(listener: Listener, position: Vec3): number;
18
+
126
19
  /**
127
- * Distance falloff of a sound's level:
128
- * - `inverse_square`: `volume / (1 + d² × k)`; `cutoffK` also divides every lowpass cutoff by `1 + d × cutoffK` (the game's muffling);
129
- * - `linear`: `max(0, 1 − d / range)`, silent at or below `minGain`;
130
- * - `none`: no distance.
131
- * `scale` multiplies the level (the game's boom is 1.4 at the source), and below `minGain` the sound is not played.
20
+ * Sounds made of recorded files (`.ogg`), as data. A `SampleSound` lists the takes of each layer: `close` (the sound as heard near, the only one
21
+ * required), `far` (the same event heard from far away: a muffled bang with its echo) and the tail of the place it happens in (`indoor` / `outdoor`).
22
+ * `planSample` decides, from a seeded stream, which take plays (round-robin: never the same take twice in a row), the pitch (± a fraction), how the
23
+ * near and far layers cross over with the distance, the level, the pan and the distance lowpass. It is pure, so it runs in Node and in tests; the
24
+ * WebAudio layer (`realizeSample`) plays the plan from decoded buffers.
132
25
  */
133
- type Falloff = {
134
- readonly model: 'inverse_square';
135
- readonly k: number;
136
- readonly minGain?: number;
137
- readonly cutoffK?: number;
138
- readonly scale?: number;
139
- } | {
140
- readonly model: 'linear';
141
- readonly range: number;
142
- readonly minGain?: number;
143
- readonly scale?: number;
144
- } | {
145
- readonly model: 'none';
26
+
27
+ declare const SAMPLE_ENVIRONMENTS: readonly ["indoor", "outdoor"];
28
+ /** The role of a layer in a plan. */
29
+ type SampleLayerRole = 'close' | 'far' | 'tail';
30
+ declare const DEFAULT_SAMPLE_FALLOFF: Falloff;
31
+ declare const DEFAULT_CROSSOVER: SampleCrossover;
32
+ declare const DEFAULT_SAMPLE_PITCH = 0.04;
33
+ declare const DEFAULT_TAIL_GAIN = 0.6;
34
+ declare const DEFAULT_SAMPLE_CUTOFF_K = 0.01;
35
+ /** The open lowpass: at or above it the realizer leaves the filter out. */
36
+ declare const OPEN_CUTOFF = 20000;
37
+ /** A layer quieter than this is left out of the plan. */
38
+ declare const LAYER_FLOOR = 0.001;
39
+ /** One file to play: which take, at what level (already times the sound level), rate and start. */
40
+ type PlannedSampleLayer = {
41
+ readonly role: SampleLayerRole;
42
+ readonly url: string;
43
+ /** Index of the take in its list. */
44
+ readonly take: number;
45
+ readonly gain: number;
46
+ readonly rate: number;
47
+ readonly at: number;
48
+ /** Seconds into the file (the take's trim). */
49
+ readonly offset?: number;
50
+ /** Seconds of the file played (the take's trim). */
51
+ readonly duration?: number;
146
52
  };
147
- /**
148
- * The order the realizer creates and wires the nodes in. Each one is the order of the team routine it reproduces, so the trace of the SDK
149
- * matches the trace of the team code node for node:
150
- * - `layered` (default): the art scene's `sNoise`, `sTone` and `voice`; every layer has its own output stage (distance gain, pan, reverb send);
151
- * - `burst`: the game's `sfx` (jogo L62089);
152
- * - `scream`: the game's `grito` (L62128);
153
- * - `groan`: the game's `voz` (L62143);
154
- * - `boom`: the game's `boom` (L68278);
155
- * - `static`: the game's `chiado` (L69998);
156
- * - `chord`: the game's `buzina` (L70272);
157
- * - `calls`: the game's `bichoSom` (L66625);
158
- * - `siren`: the art scene's `siren` (L1369);
159
- * - `weapon`: the weapon audio of the kit (`weapons-three`): per layer a filtered noise burst or an oscillator, then its own gain, chained to the bus.
160
- * Every order but `layered` puts the level into the envelope and connects straight to the bus.
161
- */
162
- declare const BUILD_ORDERS: readonly ["layered", "burst", "scream", "groan", "boom", "static", "chord", "calls", "siren", "weapon"];
163
- type BuildOrder = (typeof BUILD_ORDERS)[number];
164
- type SoundRecipe = {
53
+ type SamplePlan = {
54
+ readonly kind: 'sample';
165
55
  readonly id: string;
166
56
  readonly bus: BusId;
167
- readonly order?: BuildOrder;
168
- /** Named numbers drawn first, in the order written; layers read them with `{ param }`. */
169
- readonly vars?: Readonly<Record<string, Range>>;
170
- readonly duration?: Range;
171
- readonly tail?: number;
172
- readonly layers: readonly Layer[];
173
- readonly falloff?: Falloff;
174
- /** Stereo pan from the listener (the art scene's `panFor`). Only the `layered` order has an output stage to pan. */
175
- readonly pan?: boolean;
176
- /** Voice group for the limiter (`undead_voice`...). */
57
+ readonly level: number;
58
+ readonly distance: number;
59
+ readonly pan?: number;
60
+ /** Lowpass cutoff in Hz (`OPEN_CUTOFF` = no filter). */
61
+ readonly cutoff: number;
62
+ /** Playback rate of every layer (the pitch drawn for this play). */
63
+ readonly rate: number;
64
+ readonly environment: SampleEnvironment;
65
+ /** The tier played (`undefined` without tiers or intensity). */
66
+ readonly tier?: string;
67
+ /** 0 = all near, 1 = all far. */
68
+ readonly farMix: number;
69
+ readonly layers: readonly PlannedSampleLayer[];
70
+ readonly priority: number;
177
71
  readonly group?: string;
178
- /** Limiter priority; default from the bus (weapons > voices > world > ambience). */
179
- readonly priority?: number;
180
- /**
181
- * The team routine creates nodes before it checks the level (the game's `bichoSom` creates its gain and filter first): a silent sound is kept as
182
- * a plan with `silent: true` so the realizer repeats those nodes. Off by default.
183
- */
184
- readonly keepSilent?: boolean;
185
- /** Where the numbers come from: `"jogo:L62128"`, `"cena:L1342"`, or `"sdk"` for values the kit chose. */
186
- readonly sources: readonly string[];
187
72
  };
188
- /**
189
- * The order the loop realizer creates the nodes in:
190
- * - `game_loop`: the game's `loopSet` (L62109): gain and filter first, nodes created the first time the volume passes `startAbove`, gain from 0;
191
- * - `chain_loop`: the art scene's loops in `initAudio` (L1314–1316): source, filters, gain at its starting volume, created when the loop opens.
192
- */
193
- type LoopOrder = 'game_loop' | 'chain_loop';
194
- type LoopRecipe = {
195
- readonly id: string;
196
- readonly bus: BusId;
197
- readonly order?: LoopOrder;
198
- /** An oscillator loop may leave the frequency out: it starts at the default and `set({ frequency })` glides it (the game's motor). */
199
- readonly source: (NoiseSource & {
200
- readonly loop: true;
201
- }) | {
202
- readonly kind: 'osc';
203
- readonly wave: OscillatorType;
204
- readonly frequency?: Automation;
205
- readonly loop: true;
206
- };
207
- readonly filters?: FilterBank;
208
- readonly modulators?: readonly Modulator[];
209
- /** `setTargetAtTime` time constants (jogo L62125–62126: 0.15 for the volume, 0.08 for the frequency). */
210
- readonly smoothing?: {
211
- readonly volume: number;
212
- readonly frequency: number;
213
- };
214
- /** Nodes are created the first time the volume reaches this (jogo L62112: 0.002). */
215
- readonly startAbove?: number;
216
- /** Volume the gain starts at when the loop opens (`chain_loop`). */
73
+ /** The last take each sound layer played (`<id>/<role>` → index), so the next play never repeats it. Keep one per engine or channel. */
74
+ type SampleMemory = Map<string, number>;
75
+ type SamplePlanInput = {
76
+ readonly random: () => number;
77
+ readonly listener?: Listener;
78
+ readonly distanceMode?: DistanceMode;
79
+ readonly position?: Vec3;
80
+ /** Metres; overrides `position` for the distance (not for the pan). */
81
+ readonly distance?: number;
217
82
  readonly volume?: number;
218
- /**
219
- * Distance falloff of the loop, applied each time `set` gives a `position` (an engine, a fire, a skid heard from where it is). Left out, the loop is
220
- * heard at the listener, as the team loops are. `cutoffK` also lowers the first filter's cutoff with the distance, as it does for one-shots.
221
- */
222
- readonly falloff?: Falloff;
223
- /** Stereo pan from the listener, from the `position` of each `set` (a panner after the gain). Off by default. */
224
- readonly pan?: boolean;
225
- readonly sources: readonly string[];
83
+ /** Default `outdoor`. */
84
+ readonly environment?: SampleEnvironment;
85
+ /** 0 (clear) to 1 (behind a wall): lowers the level and closes the lowpass. */
86
+ readonly occlusion?: number;
87
+ /** Round-robin memory; without it each plan is drawn on its own (and may repeat). */
88
+ readonly memory?: SampleMemory;
89
+ /** Limiter priority when the sound has none. */
90
+ readonly priority?: number;
91
+ /** 0 (light) to 1 (heavy): picks the tier (see `SampleTier`). */
92
+ readonly intensity?: number;
226
93
  };
227
94
  /**
228
- * One id that plays one of several recipes (the takes of a footstep, of a punch): `play(id)` draws a variant, never the one it played last time (unless
229
- * `repeat`), and plays it at a rate drawn from `rate` (1.04 is 4% higher and 4% shorter; see `retunePlan`), so ten footsteps never sound like one.
95
+ * Round-robin without repeat: a take drawn among all but the last one played. With one take it is always 0; without `last` any take may come.
96
+ * One random number per call.
230
97
  */
231
- type SoundSet = {
232
- readonly id: string;
233
- /** Recipe ids, at least one. */
234
- readonly variants: readonly string[];
235
- /** `[min, max]` of the playback rate (pitch and speed together). Default `[1, 1]`. */
236
- readonly rate?: readonly [min: number, max: number];
237
- /** Allow the same variant twice in a row (default false). */
238
- readonly repeat?: boolean;
239
- };
240
- type SoundBank = {
241
- readonly id: string;
242
- readonly sounds?: readonly SoundRecipe[];
243
- readonly loops?: readonly LoopRecipe[];
244
- readonly sets?: readonly SoundSet[];
245
- /** Sounds played from recorded files (see `sample.ts`). A sample and a recipe may share an id: the sample plays once its files are loaded. */
246
- readonly samples?: readonly SampleSound[];
247
- };
248
- /** One take: a file and its own trim (linear gain, default 1). */
249
- type SampleRef = {
250
- readonly url: string;
251
- readonly gain?: number;
252
- };
253
- /** Where a sound happens: a closed room rings short and dense, the open field long and thin. */
254
- type SampleEnvironment = 'indoor' | 'outdoor';
255
- /** Where the near take hands over to the far one: all near up to `from` metres, all far from `to`, an equal-power crossfade between. */
256
- type SampleCrossover = {
257
- readonly from: number;
258
- readonly to: number;
259
- };
260
- type SampleTail = {
261
- readonly indoor?: readonly SampleRef[];
262
- readonly outdoor?: readonly SampleRef[];
263
- /** Level of the tail against the dry take (default 0.6). */
264
- readonly gain?: number;
265
- /** Seconds after the start (default 0: the tail file carries its own pre-delay). */
266
- readonly at?: number;
98
+ declare function pickTake(count: number, last: number | undefined, random: () => number): number;
99
+ /** 0 at or below `from`, 1 at or beyond `to`, linear between. */
100
+ declare function farMixAt(crossover: SampleCrossover, distance: number): number;
101
+ /** Equal-power gains of the near and far layers for a far mix (so the crossover has no dip in loudness). */
102
+ declare function crossGains(farMix: number): {
103
+ readonly close: number;
104
+ readonly far: number;
267
105
  };
106
+ /** The distance lowpass, closed further behind a wall (an occluded sound loses its highs first). */
107
+ declare function sampleCutoff(cutoffK: number, distance: number, occlusion?: number): number;
108
+ /** Every file a sound may play (to load before its first play). */
109
+ declare function sampleUrls(sound: SampleSound): string[];
110
+ /** The tier for an intensity: the last whose `from` is at or below it. */
111
+ declare function tierFor(sound: SampleSound, intensity: number | undefined): SampleTier | undefined;
268
112
  /**
269
- * A set of takes for a strength of the action (criterion C7: a light blow, a heavy one, a car at 10 or 60 km/h): from `from` (0..1) up, these takes
270
- * replace `close` and `far`. The tier played is the last one whose `from` is at or below `PlayOptions.intensity`.
113
+ * The plan of one play, or `null` when the sound is too far to be heard. Random numbers, in order: the pitch, the near take, the far take, the
114
+ * tail take (each drawn only when that layer has more than zero takes and is heard).
271
115
  */
272
- type SampleTier = {
273
- readonly name: string;
274
- readonly from: number;
275
- readonly close: readonly SampleRef[];
276
- readonly far?: readonly SampleRef[];
277
- };
278
- type SampleSound = {
279
- readonly id: string;
280
- readonly bus: BusId;
281
- /** The takes heard near; at least one (the criteria ask four for a sound heard all the time). */
282
- readonly close: readonly SampleRef[];
283
- /** The takes heard far; without them the near take plays at every distance (muffled by the lowpass). */
284
- readonly far?: readonly SampleRef[];
285
- /** Takes by strength (light, medium, heavy), in rising `from`; without `intensity` the near takes above play. */
286
- readonly tiers?: readonly SampleTier[];
287
- /** Default `{ from: 30, to: 80 }`. */
288
- readonly crossover?: SampleCrossover;
289
- readonly tail?: SampleTail;
290
- /** ± fraction of the playback rate drawn per play (default 0.04: ±4 %, inside the 3–5 % of the criteria). 0 plays every take at its pitch. */
291
- readonly pitch?: number;
292
- /** Level of the sound (default 1). */
293
- readonly gain?: number;
294
- /** Default `{ model: 'inverse_square', k: 0.002, minGain: 0.002 }`: a recorded take already sounds near or far, so the curve is gentler than a recipe's. */
295
- readonly falloff?: Falloff;
296
- /** The lowpass cutoff is `20000 / (1 + d × cutoffK)` Hz (default 0.01: 10 kHz at 100 m). 0 turns the distance lowpass off. */
297
- readonly cutoffK?: number;
298
- /** Stereo pan from the listener (default true). */
299
- readonly pan?: boolean;
300
- readonly group?: string;
301
- readonly priority?: number;
302
- /**
303
- * At most `voices` plays of this sound ring at once: a new one fades the oldest out in `fade` seconds (default 0.03) at its own start. Rapid fire
304
- * then stays shot by shot instead of a wall of overlapping takes (criterion C9). Without it every play rings to its end.
305
- */
306
- readonly choke?: {
307
- readonly voices: number;
308
- readonly fade?: number;
309
- };
310
- /**
311
- * A continuous sound (fire, rain, an engine idling) that `engine.loop(id)` plays: the near takes chained without a seam, each crossed into the next
312
- * in `crossfade` seconds (default 0.25, equal power), never the same take twice in a row. While the files load, the recipe loop of `fallback`
313
- * (or of the same id) plays. `play(id)` still plays one take as a one-shot.
314
- */
315
- readonly loop?: boolean | {
316
- readonly crossfade?: number;
317
- };
318
- /** A recipe id the engine plays while the files are not loaded yet (or failed): the synthesized sound stays as the fallback. */
319
- readonly fallback?: string;
320
- /** Where each file came from: `"freesound:123456 autor CC0"`, or `"sdk"` for files the kit made. */
321
- readonly sources: readonly string[];
322
- };
323
-
324
- /** Where the ears are. `yaw` turns around y like a three.js camera: the right ear points at `(cos yaw, 0, −sin yaw)`. */
325
- type Listener = {
326
- readonly position: Vec3;
327
- readonly yaw: number;
328
- };
329
- type DistanceMode = 'xz' | '3d';
330
- declare const DEFAULT_LISTENER: Listener;
331
- /** The art scene clamps the pan to ±0.85 and never divides by less than 2 m (cena L1319). */
332
- declare const PAN_LIMIT = 0.85;
333
- declare const PAN_MIN_DISTANCE = 2;
334
- /** Distance from the listener: on the ground (`xz`, the teams' choice) or in 3D. */
335
- declare function listenerDistance(listener: Listener, position: Vec3, mode?: DistanceMode): number;
336
- /** The art scene's `panFor`: the side the sound is on, −0.85 (left) to 0.85 (right). No HRTF, for cost. */
337
- declare function panFor(listener: Listener, position: Vec3): number;
116
+ declare function planSample(sound: SampleSound, input: SamplePlanInput): SamplePlan | null;
338
117
 
339
118
  /** How an AudioParam moves, with every number resolved and every time in seconds from the layer start. */
340
119
  type PlannedAutomation = {
@@ -448,101 +227,6 @@ type PlanInput = {
448
227
  */
449
228
  declare function planSound(recipe: SoundRecipe, input?: PlanInput): SoundPlan | null;
450
229
 
451
- /**
452
- * Sounds made of recorded files (`.ogg`), as data. A `SampleSound` lists the takes of each layer: `close` (the sound as heard near, the only one
453
- * required), `far` (the same event heard from far away: a muffled bang with its echo) and the tail of the place it happens in (`indoor` / `outdoor`).
454
- * `planSample` decides, from a seeded stream, which take plays (round-robin: never the same take twice in a row), the pitch (± a fraction), how the
455
- * near and far layers cross over with the distance, the level, the pan and the distance lowpass. It is pure, so it runs in Node and in tests; the
456
- * WebAudio layer (`realizeSample`) plays the plan from decoded buffers.
457
- */
458
-
459
- declare const SAMPLE_ENVIRONMENTS: readonly ["indoor", "outdoor"];
460
- /** The role of a layer in a plan. */
461
- type SampleLayerRole = 'close' | 'far' | 'tail';
462
- declare const DEFAULT_SAMPLE_FALLOFF: Falloff;
463
- declare const DEFAULT_CROSSOVER: SampleCrossover;
464
- declare const DEFAULT_SAMPLE_PITCH = 0.04;
465
- declare const DEFAULT_TAIL_GAIN = 0.6;
466
- declare const DEFAULT_SAMPLE_CUTOFF_K = 0.01;
467
- /** The open lowpass: at or above it the realizer leaves the filter out. */
468
- declare const OPEN_CUTOFF = 20000;
469
- /** A layer quieter than this is left out of the plan. */
470
- declare const LAYER_FLOOR = 0.001;
471
- /** One file to play: which take, at what level (already times the sound level), rate and start. */
472
- type PlannedSampleLayer = {
473
- readonly role: SampleLayerRole;
474
- readonly url: string;
475
- /** Index of the take in its list. */
476
- readonly take: number;
477
- readonly gain: number;
478
- readonly rate: number;
479
- readonly at: number;
480
- };
481
- type SamplePlan = {
482
- readonly kind: 'sample';
483
- readonly id: string;
484
- readonly bus: BusId;
485
- readonly level: number;
486
- readonly distance: number;
487
- readonly pan?: number;
488
- /** Lowpass cutoff in Hz (`OPEN_CUTOFF` = no filter). */
489
- readonly cutoff: number;
490
- /** Playback rate of every layer (the pitch drawn for this play). */
491
- readonly rate: number;
492
- readonly environment: SampleEnvironment;
493
- /** The tier played (`undefined` without tiers or intensity). */
494
- readonly tier?: string;
495
- /** 0 = all near, 1 = all far. */
496
- readonly farMix: number;
497
- readonly layers: readonly PlannedSampleLayer[];
498
- readonly priority: number;
499
- readonly group?: string;
500
- };
501
- /** The last take each sound layer played (`<id>/<role>` → index), so the next play never repeats it. Keep one per engine or channel. */
502
- type SampleMemory = Map<string, number>;
503
- type SamplePlanInput = {
504
- readonly random: () => number;
505
- readonly listener?: Listener;
506
- readonly distanceMode?: DistanceMode;
507
- readonly position?: Vec3;
508
- /** Metres; overrides `position` for the distance (not for the pan). */
509
- readonly distance?: number;
510
- readonly volume?: number;
511
- /** Default `outdoor`. */
512
- readonly environment?: SampleEnvironment;
513
- /** 0 (clear) to 1 (behind a wall): lowers the level and closes the lowpass. */
514
- readonly occlusion?: number;
515
- /** Round-robin memory; without it each plan is drawn on its own (and may repeat). */
516
- readonly memory?: SampleMemory;
517
- /** Limiter priority when the sound has none. */
518
- readonly priority?: number;
519
- /** 0 (light) to 1 (heavy): picks the tier (see `SampleTier`). */
520
- readonly intensity?: number;
521
- };
522
- /**
523
- * Round-robin without repeat: a take drawn among all but the last one played. With one take it is always 0; without `last` any take may come.
524
- * One random number per call.
525
- */
526
- declare function pickTake(count: number, last: number | undefined, random: () => number): number;
527
- /** 0 at or below `from`, 1 at or beyond `to`, linear between. */
528
- declare function farMixAt(crossover: SampleCrossover, distance: number): number;
529
- /** Equal-power gains of the near and far layers for a far mix (so the crossover has no dip in loudness). */
530
- declare function crossGains(farMix: number): {
531
- readonly close: number;
532
- readonly far: number;
533
- };
534
- /** The distance lowpass, closed further behind a wall (an occluded sound loses its highs first). */
535
- declare function sampleCutoff(cutoffK: number, distance: number, occlusion?: number): number;
536
- /** Every file a sound may play (to load before its first play). */
537
- declare function sampleUrls(sound: SampleSound): string[];
538
- /** The tier for an intensity: the last whose `from` is at or below it. */
539
- declare function tierFor(sound: SampleSound, intensity: number | undefined): SampleTier | undefined;
540
- /**
541
- * The plan of one play, or `null` when the sound is too far to be heard. Random numbers, in order: the pitch, the near take, the far take, the
542
- * tail take (each drawn only when that layer has more than zero takes and is heard).
543
- */
544
- declare function planSample(sound: SampleSound, input: SamplePlanInput): SamplePlan | null;
545
-
546
230
  /**
547
231
  * Decoded sample buffers by url. Files load once (concurrent asks share the same promise), decode on the engine's context and stay until `clear`.
548
232
  * The bytes come from a `SampleLoader`: `fetch` by default, or anything else (a single-file page passes the files inlined as base64).
@@ -624,29 +308,6 @@ declare function resolveMix(mix: string | MixProfile, profiles?: Readonly<Record
624
308
  /** The gain a bus plays at: 0 when muted, its gain (default 1) otherwise. */
625
309
  declare function busGain(setting: BusSetting | undefined): number;
626
310
 
627
- type DeepPartial<TValue> = TValue extends readonly unknown[] ? TValue : TValue extends object ? {
628
- readonly [Key in keyof TValue]?: DeepPartial<TValue[Key]>;
629
- } : TValue;
630
- /** Every sound and loop the engine knows, by id. Banks register in order; a later bank replaces an earlier recipe with the same id. */
631
- type SoundRegistry = {
632
- register(bank: SoundBank): void;
633
- /** Merged into the recipe by id (objects merge, arrays such as `layers` are replaced); an unknown id is ignored. */
634
- patch(id: string, patch: DeepPartial<SoundRecipe> | DeepPartial<LoopRecipe> | DeepPartial<SampleSound>): void;
635
- remove(id: string): void;
636
- sound(id: string): SoundRecipe | undefined;
637
- loop(id: string): LoopRecipe | undefined;
638
- /** The set an id names (`SoundBank.sets`), if any; a recipe with the same id wins in `play`. */
639
- set(id: string): SoundSet | undefined;
640
- sample(id: string): SampleSound | undefined;
641
- soundIds(): string[];
642
- setIds(): string[];
643
- loopIds(): string[];
644
- sampleIds(): string[];
645
- /** Which bank each id came from. */
646
- bankOf(id: string): string | undefined;
647
- };
648
- declare function createSoundRegistry(banks?: readonly SoundBank[]): SoundRegistry;
649
-
650
311
  /**
651
312
  * Rules for a group of voices (`undead_voice`...). A request passes when every rule passes:
652
313
  * - `maxConcurrent`: at most this many of the group play at once (`steal` replaces the group's oldest instead of refusing);
@@ -859,7 +520,10 @@ type AudioEngine = {
859
520
  plan(id: string, options?: PlayOptions): SoundPlan | null;
860
521
  /** Pure: the plan a sample sound would play with (no round-robin memory), from `options.seed` or the engine seed. */
861
522
  planSample(id: string, options?: PlayOptions): SamplePlan | null;
862
- /** Loads the files of these sample sounds (default all registered). Creates the context; resolves when every file is decoded or failed. */
523
+ /**
524
+ * Loads the files of these sample sounds (default all registered). Creates the context; resolves when every file is decoded or failed. An id named
525
+ * here that has no files yet (its manifest not registered) loads as soon as a bank that resolves it registers.
526
+ */
863
527
  loadSamples(ids?: readonly string[]): Promise<SampleLoadReport>;
864
528
  /** `ready` when every file of the sound is decoded. */
865
529
  sampleStatus(id: string): SampleStatus;
@@ -1124,6 +788,10 @@ type UserSoundPick = {
1124
788
  /** 0–10: how well the files fit the sound (rate, clipping, band). */
1125
789
  readonly score: number;
1126
790
  readonly loop?: boolean;
791
+ /** Who made the files, when the pack is not the list's `author`. */
792
+ readonly author?: string;
793
+ /** Their licence, when not the list's `license`. */
794
+ readonly license?: string;
1127
795
  };
1128
796
  type UserSoundPicks = {
1129
797
  readonly version: 1;
@@ -1247,4 +915,4 @@ type AudioBenchText = {
1247
915
  };
1248
916
  declare const audioBenchTextOf: (locale: string) => AudioBenchText;
1249
917
 
1250
- export { type FormantVoiceSource as $, type AudioLoopHandle as A, type BusId as B, CRITERIA as C, CRITERIA_TARGETS as D, type CriterionId as E, type Falloff as F, type CriterionResult as G, type Curve as H, DEFAULT_CROSSOVER as I, DEFAULT_LISTENER as J, DEFAULT_MAX_VOICES as K, type LoopRecipe as L, type MixProfile as M, DEFAULT_MIX as N, DEFAULT_SAMPLE_CUTOFF_K as O, type ParamRef as P, DEFAULT_SAMPLE_FALLOFF as Q, type Range as R, type SampleSound as S, DEFAULT_SAMPLE_PITCH as T, DEFAULT_TAIL_GAIN as U, type DeepPartial as V, type DistanceMode as W, type Envelope as X, type EnvelopePoint as Y, type FilterBank as Z, type FilterSpec as _, type SampleLoader as a, type UserSampleBankOptions as a$, KIT_SAMPLES_BANK as a0, LAYER_FLOOR as a1, type LayerSource as a2, type Listener as a3, type LoadUserSamplesOptions as a4, type LoopOrder as a5, type LoopSet as a6, MIX_PROFILES as a7, type MeasureOptions as a8, type Modulator as a9, type SampleLayerRole as aA, type SampleLoadReport as aB, type SampleMemory as aC, type SampleOptions as aD, type SamplePlanInput as aE, type SampleRef as aF, type SampleStatus as aG, type SampleStore as aH, type SampleTail as aI, type SampleTier as aJ, type SdkLicense as aK, type SfxBlow as aL, type SfxCategory as aM, type SfxEntry as aN, type SfxFileEntry as aO, type SfxLayer as aP, type SfxManifestLike as aQ, type SfxManifestSound as aR, type SfxMaterial as aS, type SfxPriority as aT, type SfxState as aU, type SoundRegistry as aV, type SoundSource as aW, type StartQueueOptions as aX, type Time as aY, USER_SAMPLES_BANK as aZ, USER_SOUND_PREFIX as a_, type NoiseSource as aa, OPEN_CUTOFF as ab, type OscSource as ac, PAN_LIMIT as ad, PAN_MIN_DISTANCE as ae, type PlanInput as af, type PlannedAutomation as ag, type PlannedEnvelopePoint as ah, type PlannedFilter as ai, type PlannedLayer as aj, type PlannedModulator as ak, type PlannedSampleLayer as al, type PlannedSource as am, type PlayOptions as an, SAMPLE_ENVIRONMENTS as ao, SDK_LICENSES as ap, SFX_BLOWS as aq, SFX_CATALOG as ar, SFX_CATEGORIES as as, SFX_IMPACTS as at, SFX_LAYERS as au, SFX_MATERIALS as av, SFX_TIERS as aw, type SampleBankOptions as ax, type SampleCrossover as ay, type SampleEnvironment as az, type SfxManifest as b, type UserSampleBankResult as b0, type UserSfxFile as b1, type UserSfxManifest as b2, type UserSfxSound as b3, type UserSoundPick as b4, type UserSoundPicks as b5, type UserSoundRole as b6, VOICE_GROUPS as b7, type VoiceDecision as b8, type VoiceGroup as b9, pickTake as bA, planSample as bB, planSound as bC, recordOutput as bD, resolveMix as bE, sampleBankFromManifest as bF, sampleCutoff as bG, sampleUrls as bH, sfxState as bI, soundSourceOf as bJ, splitTier as bK, tierFor as bL, userSampleBank as bM, type VoiceHandle as ba, type VoiceManager as bb, type VoiceManagerOptions as bc, type VoiceRefusal as bd, type VoiceRequest as be, type VoiceStartEvent as bf, type VoiceStats as bg, audioBenchTextOf as bh, busGain as bi, busPriority as bj, createAudioEngine as bk, createAudioTestModule as bl, createSampleStore as bm, createSoundRegistry as bn, createVoiceManager as bo, crossGains as bp, farMixAt as bq, fetchLoader as br, impactSoundId as bs, inlineLoader as bt, limiterCurve as bu, listenerDistance as bv, loadUserSamples as bw, measureCriteria as bx, panFor as by, parseSfxName as bz, type SoundBank as c, type SampleDefaults as d, type SoundRecipe as e, type Layer as f, type SoundSet as g, type SoundPlan as h, type SamplePlan as i, type LoopEntry as j, type AudioEngine as k, type ActionCheck as l, type AudioBenchText as m, type AudioChannel as n, type AudioEngineOptions as o, type AudioScenarioLike as p, type AudioStats as q, type AudioTestModule as r, type AudioTestModuleOptions as s, type Automation as t, BENCH_BANK as u, BUILD_ORDERS as v, BUS_IDS as w, BUS_PRIORITY as x, type BuildOrder as y, type BusSetting as z };
918
+ export { SDK_LICENSES as $, type AudioEngine as A, BENCH_BANK as B, CRITERIA as C, type DistanceMode as D, DEFAULT_TAIL_GAIN as E, LAYER_FLOOR as F, type LoadUserSamplesOptions as G, type LoopSet as H, MIX_PROFILES as I, type MeasureOptions as J, KIT_SAMPLES_BANK as K, type LoopEntry as L, type MixProfile as M, PAN_MIN_DISTANCE as N, OPEN_CUTOFF as O, PAN_LIMIT as P, type PlanInput as Q, type PlannedAutomation as R, type SampleLoader as S, type PlannedEnvelopePoint as T, type PlannedFilter as U, type PlannedLayer as V, type PlannedModulator as W, type PlannedSampleLayer as X, type PlannedSource as Y, type PlayOptions as Z, SAMPLE_ENVIRONMENTS as _, type SfxManifest as a, measureCriteria as a$, SFX_BLOWS as a0, SFX_CATALOG as a1, SFX_CATEGORIES as a2, SFX_IMPACTS as a3, SFX_LAYERS as a4, SFX_MATERIALS as a5, SFX_TIERS as a6, type SampleBankOptions as a7, type SampleLayerRole as a8, type SampleLoadReport as a9, type UserSoundPicks as aA, type UserSoundRole as aB, VOICE_GROUPS as aC, type VoiceDecision as aD, type VoiceGroup as aE, type VoiceHandle as aF, type VoiceManager as aG, type VoiceManagerOptions as aH, type VoiceRefusal as aI, type VoiceRequest as aJ, type VoiceStartEvent as aK, type VoiceStats as aL, audioBenchTextOf as aM, busGain as aN, busPriority as aO, createAudioEngine as aP, createAudioTestModule as aQ, createSampleStore as aR, createVoiceManager as aS, crossGains as aT, farMixAt as aU, fetchLoader as aV, impactSoundId as aW, inlineLoader as aX, limiterCurve as aY, listenerDistance as aZ, loadUserSamples as a_, type SampleMemory as aa, type SampleOptions as ab, type SamplePlanInput as ac, type SampleStatus as ad, type SampleStore as ae, type SdkLicense as af, type SfxBlow as ag, type SfxCategory as ah, type SfxEntry as ai, type SfxFileEntry as aj, type SfxLayer as ak, type SfxManifestLike as al, type SfxManifestSound as am, type SfxMaterial as an, type SfxPriority as ao, type SfxState as ap, type SoundSource as aq, type StartQueueOptions as ar, USER_SAMPLES_BANK as as, USER_SOUND_PREFIX as at, type UserSampleBankOptions as au, type UserSampleBankResult as av, type UserSfxFile as aw, type UserSfxManifest as ax, type UserSfxSound as ay, type UserSoundPick as az, type SampleDefaults as b, panFor as b0, parseSfxName as b1, pickTake as b2, planSample as b3, planSound as b4, recordOutput as b5, resolveMix as b6, sampleBankFromManifest as b7, sampleCutoff as b8, sampleUrls as b9, sfxState as ba, soundSourceOf as bb, splitTier as bc, tierFor as bd, userSampleBank as be, type SoundPlan as c, type SamplePlan as d, type AudioLoopHandle as e, type Listener as f, type ActionCheck as g, type AudioBenchText as h, type AudioChannel as i, type AudioEngineOptions as j, type AudioScenarioLike as k, type AudioStats as l, type AudioTestModule as m, type AudioTestModuleOptions as n, BUS_PRIORITY as o, type BusSetting as p, CRITERIA_TARGETS as q, type CriterionId as r, type CriterionResult as s, DEFAULT_CROSSOVER as t, DEFAULT_LISTENER as u, DEFAULT_MAX_VOICES as v, DEFAULT_MIX as w, DEFAULT_SAMPLE_CUTOFF_K as x, DEFAULT_SAMPLE_FALLOFF as y, DEFAULT_SAMPLE_PITCH as z };
package/dist/testing.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { aL as SfxBlow, aS as SfxMaterial, az as SampleEnvironment, aU as SfxState, E as CriterionId, aW as SoundSource, p as AudioScenarioLike, k as AudioEngine, G as CriterionResult, bf as VoiceStartEvent, ba as VoiceHandle, c as SoundBank, a as SampleLoader, aN as SfxEntry, l as ActionCheck } from './locale-CJfDpWnI.js';
2
- export { m as AudioBenchText, r as AudioTestModule, s as AudioTestModuleOptions, u as BENCH_BANK, bh as audioBenchTextOf, bl as createAudioTestModule } from './locale-CJfDpWnI.js';
1
+ import { ag as SfxBlow, an as SfxMaterial, ap as SfxState, r as CriterionId, aq as SoundSource, k as AudioScenarioLike, A as AudioEngine, s as CriterionResult, aK as VoiceStartEvent, aF as VoiceHandle, S as SampleLoader, ai as SfxEntry, g as ActionCheck } from './locale-YHyJwN2P.js';
2
+ export { h as AudioBenchText, m as AudioTestModule, n as AudioTestModuleOptions, B as BENCH_BANK, aM as audioBenchTextOf, aQ as createAudioTestModule } from './locale-YHyJwN2P.js';
3
3
  import { TestableModule } from '@adatechnology/game-contracts';
4
+ import { q as SampleEnvironment, a as SoundBank } from './bank-Dsj1VAht.js';
4
5
 
5
6
  type Strength = 'light' | 'medium' | 'heavy';
6
7
  type SoundCheckText = {
@@ -15,9 +16,9 @@ type SoundCheckText = {
15
16
  readonly strengths: Readonly<Record<Strength, string>>;
16
17
  readonly states: Readonly<Record<SfxState, string>>;
17
18
  readonly criteria: Readonly<Record<CriterionId, string>>;
18
- readonly metrics: Readonly<Record<'source' | 'chosen' | 'matrix' | 'last' | 'ab' | 'blind' | 'verdicts' | 'state' | 'count', string>>;
19
+ readonly metrics: Readonly<Record<'source' | 'chosen' | 'matrix' | 'last' | 'ab' | 'blind' | 'verdicts' | 'state' | 'count' | 'unlicensed', string>>;
19
20
  readonly run: Readonly<Record<'idle' | 'measuring' | 'done' | 'failed' | 'recording' | 'recorded', string>>;
20
- readonly words: Readonly<Record<'none' | 'hidden' | 'prefers' | 'approved', string>>;
21
+ readonly words: Readonly<Record<'none' | 'hidden' | 'prefers' | 'approved' | 'local_file' | 'sdk_file' | 'synth', string>>;
21
22
  /** Where the voice came from: the kit's file, the owner's file, the synthesized recipe. */
22
23
  readonly sources: Readonly<Record<SoundSource, string>>;
23
24
  readonly probe: string;