@adatechnology/game-audio 0.3.3 → 0.3.5

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
@@ -649,6 +649,13 @@ type AudioLoopHandle = {
649
649
  stop(): void;
650
650
  };
651
651
 
652
+ type StartQueueOptions = {
653
+ /** Most milliseconds of main-thread work one task spends building voices (at least one voice is always built). Default 2. */
654
+ readonly budgetMs?: number;
655
+ /** A voice still waiting after this many milliseconds is dropped instead of starting late. Default 250. */
656
+ readonly maxLatencyMs?: number;
657
+ };
658
+
652
659
  type PlayOptions = {
653
660
  readonly position?: Vec3;
654
661
  /** Meters; overrides `position` for the distance (not for the pan). */
@@ -694,6 +701,8 @@ type AudioEngineOptions = {
694
701
  /** 0 (clear) to 1 (behind a wall) for a sound at a point; without it there is no occlusion (Q8). */
695
702
  readonly occlusion?: (point: Vec3) => number;
696
703
  readonly createContext?: () => AudioContext | null;
704
+ /** Build voices in timed slices between frames instead of inside `play` (a sound starts a few ms late; a burst never costs a frame). Off by default. */
705
+ readonly deferredStart?: StartQueueOptions;
697
706
  };
698
707
  type AudioEngine = {
699
708
  readonly registry: SoundRegistry;
@@ -719,6 +728,8 @@ type AudioEngine = {
719
728
  setMaster(gain: number): void;
720
729
  /** Creates the context and the mixing chain if needed and resumes it; call from a click or a key. */
721
730
  unlock(): void;
731
+ /** Creates the context, the mixing chain (reverb impulse included) and the shared noise buffer now, so the first sound does not pay for them inside a frame. Returns false without a context. */
732
+ warmUp(): boolean;
722
733
  stats(): AudioStats;
723
734
  /** RMS (0 to 1) of what a bus carries now, read from an analyser the first time you ask; sounds started before that do not reach it. 0 without a context. */
724
735
  level(bus: BusId): number;
@@ -843,4 +854,4 @@ type AudioBenchText = {
843
854
  };
844
855
  declare const audioBenchTextOf: (locale: string) => AudioBenchText;
845
856
 
846
- 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 };
857
+ 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 StartQueueOptions, 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 };
@@ -1064,6 +1064,56 @@ var GameAudio = (() => {
1064
1064
  return store.buffer;
1065
1065
  }
1066
1066
 
1067
+ // src/webaudio/engine-queue.ts
1068
+ var nowMs = () => typeof performance === "undefined" ? Date.now() : performance.now();
1069
+ function scheduleTask(task) {
1070
+ if (typeof MessageChannel === "undefined") {
1071
+ setTimeout(task, 0);
1072
+ return;
1073
+ }
1074
+ const channel = new MessageChannel();
1075
+ channel.port1.onmessage = () => {
1076
+ channel.port1.close();
1077
+ task();
1078
+ };
1079
+ channel.port2.postMessage(0);
1080
+ }
1081
+ function createStartQueue(options = {}) {
1082
+ const budgetMs = options.budgetMs ?? 2;
1083
+ const maxLatencyMs = options.maxLatencyMs ?? 250;
1084
+ let jobs = [];
1085
+ let scheduled = false;
1086
+ const drain = () => {
1087
+ scheduled = false;
1088
+ const began = nowMs();
1089
+ while (jobs.length > 0) {
1090
+ const job = jobs.shift();
1091
+ if (nowMs() - job.queuedAt > maxLatencyMs) job.drop();
1092
+ else job.run();
1093
+ if (nowMs() - began >= budgetMs) break;
1094
+ }
1095
+ if (jobs.length > 0) schedule();
1096
+ };
1097
+ const schedule = () => {
1098
+ if (scheduled) return;
1099
+ scheduled = true;
1100
+ scheduleTask(drain);
1101
+ };
1102
+ return {
1103
+ push(job) {
1104
+ jobs.push({ ...job, queuedAt: nowMs() });
1105
+ schedule();
1106
+ },
1107
+ clear() {
1108
+ for (const job of jobs) job.drop();
1109
+ jobs = [];
1110
+ },
1111
+ get size() {
1112
+ return jobs.length;
1113
+ }
1114
+ };
1115
+ }
1116
+
1067
1117
  // src/webaudio/realize-game.ts
1068
1118
  var end = (layer, start) => start + layer.duration + layer.tail;
1069
1119
  var firstFilter = (layer) => layer.filters?.bank[0];
@@ -1513,15 +1563,43 @@ var GameAudio = (() => {
1513
1563
  ...playOptions.params ? { params: playOptions.params } : {},
1514
1564
  ...options.occlusion && playOptions.position && recipe.falloff?.model !== "none" ? { occlusion: options.occlusion(playOptions.position) } : {}
1515
1565
  });
1516
- const start = (plan, store, voiceId, delay = 0) => {
1566
+ const startQueue = options.deferredStart ? createStartQueue(options.deferredStart) : null;
1567
+ const build = (plan, store, delay) => {
1517
1568
  const audio = context;
1518
- const realized = realize(plan, {
1569
+ return realize(plan, {
1519
1570
  context: audio,
1520
1571
  output: outputFor(plan.bus),
1521
1572
  ...chain?.wet ? { wet: chain.wet } : {},
1522
1573
  noise: () => noiseBuffer(store, audio, mix.noise.seconds),
1523
1574
  time: audio.currentTime + delay
1524
1575
  });
1576
+ };
1577
+ const deferVoice = (plan, store, voiceId, delay) => {
1578
+ let built = null;
1579
+ let cancelled = false;
1580
+ const pending = {
1581
+ get sources() {
1582
+ return built?.sources ?? [];
1583
+ },
1584
+ stop() {
1585
+ cancelled = true;
1586
+ built?.stop();
1587
+ }
1588
+ };
1589
+ startQueue.push({
1590
+ run() {
1591
+ if (!cancelled && context) built = build(plan, store, delay);
1592
+ },
1593
+ drop() {
1594
+ if (voiceId === void 0) return;
1595
+ voices.release(voiceId);
1596
+ active.delete(voiceId);
1597
+ }
1598
+ });
1599
+ return pending;
1600
+ };
1601
+ const start = (plan, store, voiceId, delay = 0) => {
1602
+ const realized = startQueue ? deferVoice(plan, store, voiceId, delay) : build(plan, store, delay);
1525
1603
  if (voiceId === void 0) return null;
1526
1604
  voices.setLength(voiceId, plan.length + delay);
1527
1605
  active.set(voiceId, realized);
@@ -1637,6 +1715,12 @@ var GameAudio = (() => {
1637
1715
  const audio = ready();
1638
1716
  if (audio) unlockContext(audio);
1639
1717
  },
1718
+ warmUp() {
1719
+ const audio = ready();
1720
+ if (!audio) return false;
1721
+ noiseBuffer(shared, audio, mix.noise.seconds);
1722
+ return true;
1723
+ },
1640
1724
  level(bus) {
1641
1725
  watched.add(bus);
1642
1726
  ensureBus(bus);
@@ -1660,6 +1744,7 @@ var GameAudio = (() => {
1660
1744
  };
1661
1745
  },
1662
1746
  dispose() {
1747
+ startQueue?.clear();
1663
1748
  for (const id of active.keys()) voices.release(id);
1664
1749
  for (const loop of [...loops]) loop.stop();
1665
1750
  active.clear();