@adatechnology/game-audio 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -578,6 +578,64 @@ declare function noiseClassRecipe(row: NoiseClass, id?: string): SoundRecipe;
578
578
  /** The game's classes as a bank (`sfx_pistol`... and `sfx_unknown`). Subject banks pick their own classes from `GAME_SFX_CLASSES`. */
579
579
  declare const GAME_SFX_BANK: SoundBank;
580
580
 
581
+ /** A length in seconds: a number, a `[min, max]` range or a named number. */
582
+ type SceneLength = number | readonly [number, number] | ParamRef;
583
+ type SceneNoise = {
584
+ readonly type?: BiquadFilterType;
585
+ readonly f: Range;
586
+ /** The filter slides here over `d` (needs a fixed `d`). */
587
+ readonly f2?: number;
588
+ readonly q?: number;
589
+ readonly a?: number;
590
+ readonly d: SceneLength;
591
+ readonly vol: Range;
592
+ readonly wet?: number;
593
+ readonly delay?: Range;
594
+ };
595
+ /** `sNoise` (cena L1323): noise from a random point, a filter (default lowpass, Q 0.7), the gain rising over `a` and falling over `d`. */
596
+ declare function sceneNoise(o: SceneNoise): Layer;
597
+ type SceneTone = {
598
+ readonly wave?: OscillatorType;
599
+ readonly f: Range;
600
+ readonly f2?: number;
601
+ readonly d: SceneLength;
602
+ readonly vol: Range;
603
+ readonly wet?: number;
604
+ readonly delay?: Range;
605
+ };
606
+ /** `sTone` (cena L1324): an oscillator sliding from `f` to `f2` (back to `f` when none), the gain rising over 0.002 s and falling over `d`. */
607
+ declare function sceneTone(o: SceneTone): Layer;
608
+ type SceneVoice = {
609
+ /** Start and end frequency of the sweep (the page ramps linearly from `f0` to `f1` over the length). */
610
+ readonly f0: Range;
611
+ readonly f1: Range;
612
+ /** Length in seconds. */
613
+ readonly d: SceneLength;
614
+ /** Centre of the first and second formant (Q 4 and 6) and of the breath band (Q 1.1). */
615
+ readonly fA: Range;
616
+ readonly fB: Range;
617
+ /** Level of the breath noise. */
618
+ readonly noise: Range;
619
+ readonly vol: Range;
620
+ readonly wave?: OscillatorType;
621
+ };
622
+ /**
623
+ * `voice` (cena L1325): an oscillator swept from `f0` to `f1` with a vibrato of 4 to 10 Hz (depth 0.08 of `f0`) through two formant bands plus a band of
624
+ * breath noise, summed and boosted 3.2 times, the gain rising over 0.1 s, held to 0.6 of the length and falling; reverb send 0.45. It is the one
625
+ * layer the page builds with `voice(...)`, so a scream is two of them.
626
+ */
627
+ declare function sceneVoice(o: SceneVoice): Layer;
628
+ /** The layer played `count` times (a team `for` loop whose body draws numbers): each pass is its own layer, so the draws stay in the loop's order. */
629
+ declare const sceneMany: (count: number, make: () => Layer) => Layer[];
630
+ type SceneRecipeOptions = {
631
+ /** Named numbers drawn first (`PlayOptions.params` can override them). */
632
+ readonly vars?: SoundRecipe['vars'];
633
+ readonly bus?: BusId;
634
+ readonly group?: string;
635
+ };
636
+ /** A recipe in the art scene's mix: positional with the art scene's curve (`vol / (1 + d² × 0.008)`) and pan; `line` is the team code it comes from (`cena:<line>`). */
637
+ declare function sceneRecipe(id: string, layers: readonly Layer[], line: string, options?: SceneRecipeOptions): SoundRecipe;
638
+
581
639
  declare const LIBRARY_SOUNDS: readonly SoundRecipe[];
582
640
  declare const LIBRARY_LOOPS: readonly LoopRecipe[];
583
641
  declare const LIBRARY_BANK: SoundBank;
@@ -780,4 +838,4 @@ type AudioBenchText = {
780
838
  };
781
839
  declare const audioBenchTextOf: (locale: string) => AudioBenchText;
782
840
 
783
- export { type AudioBenchText, type AudioChannel, type AudioEngine, type AudioEngineOptions, type AudioLoopHandle, type AudioScenarioLike, type AudioStats, type AudioTestModule, type AudioTestModuleOptions, type Automation, BENCH_BANK, BUILD_ORDERS, BUS_IDS, BUS_PRIORITY, type BuildOrder, type BusId, type BusSetting, type Curve, DEFAULT_LISTENER, DEFAULT_MAX_VOICES, DEFAULT_MIX, DISTANCE_CURVES, type DeepPartial, type DistanceCurveName, type DistanceMode, type EngineSink, type Envelope, type EnvelopePoint, type Falloff, type FilterBank, type FilterSpec, type FormantVoiceSource, GAME_SFX_BANK, GAME_SFX_CLASSES, GAME_SFX_FALLBACK, LIBRARY_BANK, LIBRARY_LOOPS, LIBRARY_SOUNDS, LOOP_SMOOTHING, LOOP_START_ABOVE, type Layer, type LayerSource, type Listener, type LoopAction, type LoopOrder, type LoopRecipe, type LoopState, MIX_PROFILES, type MixChain, type MixProfile, type Modulator, type NoiseClass, type NoiseSource, type OscSource, PAN_LIMIT, PAN_MIN_DISTANCE, type ParamRef, type PlanInput, type PlannedAutomation, type PlannedEnvelopePoint, type PlannedFilter, type PlannedLayer, type PlannedModulator, type PlannedSource, type PlayOptions, type Range, type RealizeTarget, type RealizedVoice, type SoundBank, type SoundPlan, type SoundRecipe, type SoundRegistry, type Time, VOICE_GROUPS, type VoiceDecision, type VoiceGroup, type VoiceHandle, type VoiceManager, type VoiceManagerOptions, type VoiceRefusal, type VoiceRequest, type VoiceStats, audioBenchTextOf, bufferOf, buildChain, busGain, busPriority, createAudioEngine, createAudioSink, createAudioTestModule, createLoopState, createRng, createSoundRegistry, createVoiceManager, cutoffDivisor, drawBetween, falloffGain, fillImpulse, fillNoise, globalContext, isSilent, listenerDistance, noiseClassRecipe, panFor, planSound, realize, resolveMix, streamFor, streamSeed, unlockContext };
841
+ export { type AudioBenchText, type AudioChannel, type AudioEngine, type AudioEngineOptions, type AudioLoopHandle, type AudioScenarioLike, type AudioStats, type AudioTestModule, type AudioTestModuleOptions, type Automation, BENCH_BANK, BUILD_ORDERS, BUS_IDS, BUS_PRIORITY, type BuildOrder, type BusId, type BusSetting, type Curve, DEFAULT_LISTENER, DEFAULT_MAX_VOICES, DEFAULT_MIX, DISTANCE_CURVES, type DeepPartial, type DistanceCurveName, type DistanceMode, type EngineSink, type Envelope, type EnvelopePoint, type Falloff, type FilterBank, type FilterSpec, type FormantVoiceSource, GAME_SFX_BANK, GAME_SFX_CLASSES, GAME_SFX_FALLBACK, LIBRARY_BANK, LIBRARY_LOOPS, LIBRARY_SOUNDS, LOOP_SMOOTHING, LOOP_START_ABOVE, type Layer, type LayerSource, type Listener, type LoopAction, type LoopOrder, type LoopRecipe, type LoopState, MIX_PROFILES, type MixChain, type MixProfile, type Modulator, type NoiseClass, type NoiseSource, type OscSource, PAN_LIMIT, PAN_MIN_DISTANCE, type ParamRef, type PlanInput, type PlannedAutomation, type PlannedEnvelopePoint, type PlannedFilter, type PlannedLayer, type PlannedModulator, type PlannedSource, type PlayOptions, type Range, type RealizeTarget, type RealizedVoice, type SceneLength, type SceneNoise, type SceneRecipeOptions, type SceneTone, type SceneVoice, type SoundBank, type SoundPlan, type SoundRecipe, type SoundRegistry, type Time, VOICE_GROUPS, type VoiceDecision, type VoiceGroup, type VoiceHandle, type VoiceManager, type VoiceManagerOptions, type VoiceRefusal, type VoiceRequest, type VoiceStats, audioBenchTextOf, bufferOf, buildChain, busGain, busPriority, createAudioEngine, createAudioSink, createAudioTestModule, createLoopState, createRng, createSoundRegistry, createVoiceManager, cutoffDivisor, drawBetween, falloffGain, fillImpulse, fillNoise, globalContext, isSilent, listenerDistance, noiseClassRecipe, panFor, planSound, realize, resolveMix, sceneMany, sceneNoise, sceneRecipe, sceneTone, sceneVoice, streamFor, streamSeed, unlockContext };
@@ -66,6 +66,11 @@ var GameAudio = (() => {
66
66
  planSound: () => planSound,
67
67
  realize: () => realize,
68
68
  resolveMix: () => resolveMix,
69
+ sceneMany: () => sceneMany,
70
+ sceneNoise: () => sceneNoise,
71
+ sceneRecipe: () => sceneRecipe,
72
+ sceneTone: () => sceneTone,
73
+ sceneVoice: () => sceneVoice,
69
74
  streamFor: () => streamFor,
70
75
  streamSeed: () => streamSeed,
71
76
  unlockContext: () => unlockContext
@@ -356,8 +361,8 @@ var GameAudio = (() => {
356
361
  const depth = typeof modulator.depth === "object" && "scaleOf" in modulator.depth ? sourceFrequency * modulator.depth.factor : pending.depth ?? 0;
357
362
  return { target: modulator.target, ...modulator.wave ? { wave: modulator.wave } : {}, rate: pending.rate, depth, ...modulator.base === void 0 ? {} : { base: modulator.base } };
358
363
  }
359
- function finishEnvelope(envelope, duration, level) {
360
- return envelope.map(([time, value, curve, fixed2]) => ({ time: timeOf(time, duration), value: fixed2 === "fixed" ? value : value * level, curve }));
364
+ function finishEnvelope(envelope2, duration, level) {
365
+ return envelope2.map(([time, value, curve, fixed2]) => ({ time: timeOf(time, duration), value: fixed2 === "fixed" ? value : value * level, curve }));
361
366
  }
362
367
  function planLayer(layer, scope, defaults, levelInEnvelope, soundLevel) {
363
368
  const pending = drawLayer(layer, scope);
@@ -596,6 +601,83 @@ var GameAudio = (() => {
596
601
  }
597
602
  var GAME_SFX_BANK = { id: "game_sfx", sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) };
598
603
 
604
+ // src/recipes/scene.ts
605
+ var ATTACK = 2e-3;
606
+ function lengthOf(d, attack) {
607
+ if (typeof d === "number") return attack + d;
608
+ if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
609
+ return { ...d, add: (d.add ?? 0) + attack };
610
+ }
611
+ var envelope = (attack) => [
612
+ [0, 1e-4, "set", "fixed"],
613
+ [attack, 1, "exp"],
614
+ ["1d", 1e-4, "exp", "fixed"]
615
+ ];
616
+ function sceneNoise(o) {
617
+ const a = o.a ?? ATTACK;
618
+ const over = typeof o.d === "number" ? o.d : 0;
619
+ return {
620
+ filters: {
621
+ mode: "serial",
622
+ bank: [{ type: o.type ?? "lowpass", frequency: o.f2 === void 0 ? { set: o.f } : { set: o.f, to: o.f2, curve: "exp", over }, q: o.q ?? 0.7 }]
623
+ },
624
+ duration: lengthOf(o.d, a),
625
+ gain: o.vol,
626
+ ...o.delay === void 0 ? {} : { at: o.delay },
627
+ ...o.wet === void 0 ? {} : { wet: o.wet },
628
+ source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
629
+ envelope: envelope(a)
630
+ };
631
+ }
632
+ function sceneTone(o) {
633
+ return {
634
+ source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
635
+ duration: lengthOf(o.d, ATTACK),
636
+ gain: o.vol,
637
+ ...o.delay === void 0 ? {} : { at: o.delay },
638
+ ...o.wet === void 0 ? {} : { wet: o.wet },
639
+ envelope: envelope(ATTACK)
640
+ };
641
+ }
642
+ function sceneVoice(o) {
643
+ return {
644
+ duration: lengthOf(o.d, 0),
645
+ gain: o.vol,
646
+ wet: 0.45,
647
+ source: {
648
+ kind: "formant_voice",
649
+ wave: o.wave ?? "sawtooth",
650
+ frequency: { from: o.f0, to: o.f1, curve: "linear", over: "duration" },
651
+ vibrato: { rate: [4, 10], depth: 0.08 },
652
+ formants: [
653
+ { frequency: o.fA, q: 4 },
654
+ { frequency: o.fB, q: 6 }
655
+ ],
656
+ breath: { frequency: o.fB, q: 1.1, gain: o.noise },
657
+ mix: 3.2
658
+ },
659
+ envelope: [
660
+ [0, 1e-4, "set", "fixed"],
661
+ [0.1, 1, "exp"],
662
+ ["0.6d", 1, "set"],
663
+ ["1d", 1e-4, "exp", "fixed"]
664
+ ]
665
+ };
666
+ }
667
+ var sceneMany = (count, make) => Array.from({ length: count }, make);
668
+ function sceneRecipe(id, layers, line, options = {}) {
669
+ return {
670
+ id,
671
+ bus: options.bus ?? "world",
672
+ pan: true,
673
+ layers,
674
+ falloff: DISTANCE_CURVES.art_scene,
675
+ ...options.vars ? { vars: options.vars } : {},
676
+ ...options.group ? { group: options.group } : {},
677
+ sources: [`cena:${line}`]
678
+ };
679
+ }
680
+
599
681
  // src/library/crowd_rumble.json
600
682
  var crowd_rumble_default = {
601
683
  id: "crowd_rumble",
@@ -1149,10 +1231,10 @@ var GameAudio = (() => {
1149
1231
  assign(filter.frequency, band.frequency, time);
1150
1232
  if (band.q !== void 0) filter.Q.value = band.q;
1151
1233
  }
1152
- const envelope = context.createGain();
1153
- applyEnvelope(envelope.gain, layer.envelope, time);
1154
- oscillator.connect(envelope);
1155
- envelope.connect(filter);
1234
+ const envelope2 = context.createGain();
1235
+ applyEnvelope(envelope2.gain, layer.envelope, time);
1236
+ oscillator.connect(envelope2);
1237
+ envelope2.connect(filter);
1156
1238
  oscillator.start(time);
1157
1239
  oscillator.stop(end(layer, time));
1158
1240
  return oscillator;
@@ -1189,12 +1271,12 @@ var GameAudio = (() => {
1189
1271
  rampTo(node.frequency, filter.frequency, time);
1190
1272
  return node;
1191
1273
  });
1192
- const envelope = context.createGain();
1193
- applyEnvelope(envelope.gain, layer.envelope, time);
1274
+ const envelope2 = context.createGain();
1275
+ applyEnvelope(envelope2.gain, layer.envelope, time);
1194
1276
  let tail = source;
1195
1277
  for (const filter of filters) tail = tail.connect(filter);
1196
- tail.connect(envelope);
1197
- envelope.connect(outputStage(plan, layer, target));
1278
+ tail.connect(envelope2);
1279
+ envelope2.connect(outputStage(plan, layer, target));
1198
1280
  source.start(time, layer.source.kind === "noise" ? layer.source.offset : 0);
1199
1281
  source.stop(end2(layer, time));
1200
1282
  return [source];
@@ -1208,10 +1290,10 @@ var GameAudio = (() => {
1208
1290
  assign(oscillator.frequency, layer.source.frequency, time);
1209
1291
  rampTo(oscillator.frequency, layer.source.frequency, time);
1210
1292
  }
1211
- const envelope = context.createGain();
1212
- applyEnvelope(envelope.gain, layer.envelope, time);
1213
- oscillator.connect(envelope);
1214
- envelope.connect(outputStage(plan, layer, target));
1293
+ const envelope2 = context.createGain();
1294
+ applyEnvelope(envelope2.gain, layer.envelope, time);
1295
+ oscillator.connect(envelope2);
1296
+ envelope2.connect(outputStage(plan, layer, target));
1215
1297
  oscillator.start(time);
1216
1298
  oscillator.stop(end2(layer, time));
1217
1299
  return [oscillator];
@@ -1240,10 +1322,10 @@ var GameAudio = (() => {
1240
1322
  breathBand.connect(breathLevel);
1241
1323
  for (const formant of formants) formant.connect(mix);
1242
1324
  breathLevel.connect(mix);
1243
- const envelope = context.createGain();
1244
- applyEnvelope(envelope.gain, layer.envelope, time);
1245
- mix.connect(envelope);
1246
- envelope.connect(outputStage(plan, layer, target));
1325
+ const envelope2 = context.createGain();
1326
+ applyEnvelope(envelope2.gain, layer.envelope, time);
1327
+ mix.connect(envelope2);
1328
+ envelope2.connect(outputStage(plan, layer, target));
1247
1329
  const stop = end2(layer, time);
1248
1330
  oscillator.start(time);
1249
1331
  vibrato.start(time);
@@ -1281,17 +1363,17 @@ var GameAudio = (() => {
1281
1363
  return { lfo, depth, target: modulator.target };
1282
1364
  });
1283
1365
  const lfos = depths.map(({ lfo }) => lfo);
1284
- const envelope = context.createGain();
1285
- applyEnvelope(envelope.gain, layer.envelope, time);
1286
- for (const { depth, target: modulated } of depths) if (modulated === "gain") depth.connect(envelope.gain);
1366
+ const envelope2 = context.createGain();
1367
+ applyEnvelope(envelope2.gain, layer.envelope, time);
1368
+ for (const { depth, target: modulated } of depths) if (modulated === "gain") depth.connect(envelope2.gain);
1287
1369
  const filters = (layer.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
1288
1370
  let tail = oscillator;
1289
1371
  for (const filter of filters) tail = tail.connect(filter);
1290
- tail.connect(envelope);
1291
- envelope.connect(target.output);
1372
+ tail.connect(envelope2);
1373
+ envelope2.connect(target.output);
1292
1374
  if (layer.wet && target.wet) {
1293
1375
  const send = gainNode(context, layer.wet);
1294
- envelope.connect(send);
1376
+ envelope2.connect(send);
1295
1377
  send.connect(target.wet);
1296
1378
  }
1297
1379
  const stop = end2(layer, time);
@@ -1307,11 +1389,11 @@ var GameAudio = (() => {
1307
1389
  const { context } = target;
1308
1390
  const source = noiseSource(target, layer.source.kind === "noise" && layer.source.loop);
1309
1391
  const filters = (layer.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
1310
- const envelope = context.createGain();
1311
- applyEnvelope(envelope.gain, layer.envelope, time);
1392
+ const envelope2 = context.createGain();
1393
+ applyEnvelope(envelope2.gain, layer.envelope, time);
1312
1394
  let tail = source;
1313
1395
  for (const filter of filters) tail = tail.connect(filter);
1314
- tail.connect(envelope).connect(target.output);
1396
+ tail.connect(envelope2).connect(target.output);
1315
1397
  source.start(time, layer.source.kind === "noise" ? layer.source.offset : 0);
1316
1398
  source.stop(time + layer.duration + layer.tail);
1317
1399
  return source;
@@ -1324,9 +1406,9 @@ var GameAudio = (() => {
1324
1406
  assign(oscillator.frequency, layer.source.frequency, time);
1325
1407
  rampTo(oscillator.frequency, layer.source.frequency, time);
1326
1408
  }
1327
- const envelope = context.createGain();
1328
- applyEnvelope(envelope.gain, layer.envelope, time);
1329
- oscillator.connect(envelope).connect(target.output);
1409
+ const envelope2 = context.createGain();
1410
+ applyEnvelope(envelope2.gain, layer.envelope, time);
1411
+ oscillator.connect(envelope2).connect(target.output);
1330
1412
  oscillator.start(time);
1331
1413
  oscillator.stop(time + layer.duration + layer.tail);
1332
1414
  return oscillator;
@@ -1883,8 +1965,8 @@ var GameAudio = (() => {
1883
1965
  const layers2 = planned.layers.map((layer, index) => {
1884
1966
  const source = layer.source.kind === "noise" ? `noise@${layer.source.offset.toFixed(4)}` : layer.source.kind === "osc" ? `${layer.source.wave}@${layer.source.frequency.value ?? layer.source.frequency.from}` : layer.source.kind;
1885
1967
  const filters = (layer.filters?.bank ?? []).map((filter) => `${filter.type}@${(filter.frequency.value ?? filter.frequency.from ?? 0).toFixed(1)}`).join(" ");
1886
- const envelope = layer.envelope.map((point) => `${point.time.toFixed(3)}:${point.value.toPrecision(3)}`).join(" ");
1887
- return ` ${text.sheet.layer} ${index + 1}: ${source} at ${layer.at.toFixed(3)} for ${layer.duration.toFixed(3)} ${filters} [${envelope}]`;
1968
+ const envelope2 = layer.envelope.map((point) => `${point.time.toFixed(3)}:${point.value.toPrecision(3)}`).join(" ");
1969
+ return ` ${text.sheet.layer} ${index + 1}: ${source} at ${layer.at.toFixed(3)} for ${layer.duration.toFixed(3)} ${filters} [${envelope2}]`;
1888
1970
  });
1889
1971
  return [head, ...layers2].join("\n");
1890
1972
  };