@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 +12 -1
- package/dist/index.global.js +87 -2
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +87 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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 };
|
package/dist/index.global.js
CHANGED
|
@@ -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
|
|
1566
|
+
const startQueue = options.deferredStart ? createStartQueue(options.deferredStart) : null;
|
|
1567
|
+
const build = (plan, store, delay) => {
|
|
1517
1568
|
const audio = context;
|
|
1518
|
-
|
|
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();
|