@adatechnology/game-audio 0.3.6 → 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.
- package/dist/chunk-DZITVFMJ.js +2235 -0
- package/dist/chunk-DZITVFMJ.js.map +1 -0
- package/dist/index.d.ts +31 -626
- package/dist/index.global.js +427 -81
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +231 -2057
- package/dist/index.js.map +1 -1
- package/dist/testing-BZQid7h0.d.ts +653 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +11 -0
- package/dist/testing.js.map +1 -0
- package/package.json +6 -2
package/dist/index.global.js
CHANGED
|
@@ -22,6 +22,10 @@ var GameAudio = (() => {
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
BENCH_BANK: () => BENCH_BANK,
|
|
25
|
+
BODY_FALLOFF: () => BODY_FALLOFF,
|
|
26
|
+
BODY_FOLEY_BANK: () => BODY_FOLEY_BANK,
|
|
27
|
+
BODY_FOLEY_SET_IDS: () => BODY_FOLEY_SET_IDS,
|
|
28
|
+
BODY_FOLEY_TAKES: () => BODY_FOLEY_TAKES,
|
|
25
29
|
BUILD_ORDERS: () => BUILD_ORDERS,
|
|
26
30
|
BUS_IDS: () => BUS_IDS,
|
|
27
31
|
BUS_PRIORITY: () => BUS_PRIORITY,
|
|
@@ -29,6 +33,7 @@ var GameAudio = (() => {
|
|
|
29
33
|
DEFAULT_MAX_VOICES: () => DEFAULT_MAX_VOICES,
|
|
30
34
|
DEFAULT_MIX: () => DEFAULT_MIX,
|
|
31
35
|
DISTANCE_CURVES: () => DISTANCE_CURVES,
|
|
36
|
+
FOOTSTEP_FALLOFF: () => FOOTSTEP_FALLOFF,
|
|
32
37
|
GAME_SFX_BANK: () => GAME_SFX_BANK,
|
|
33
38
|
GAME_SFX_CLASSES: () => GAME_SFX_CLASSES,
|
|
34
39
|
GAME_SFX_FALLBACK: () => GAME_SFX_FALLBACK,
|
|
@@ -51,6 +56,7 @@ var GameAudio = (() => {
|
|
|
51
56
|
createAudioTestModule: () => createAudioTestModule,
|
|
52
57
|
createLoopState: () => createLoopState,
|
|
53
58
|
createRng: () => createRng,
|
|
59
|
+
createSetPicker: () => createSetPicker,
|
|
54
60
|
createSoundRegistry: () => createSoundRegistry,
|
|
55
61
|
createVoiceManager: () => createVoiceManager,
|
|
56
62
|
cutoffDivisor: () => cutoffDivisor,
|
|
@@ -66,6 +72,7 @@ var GameAudio = (() => {
|
|
|
66
72
|
planSound: () => planSound,
|
|
67
73
|
realize: () => realize,
|
|
68
74
|
resolveMix: () => resolveMix,
|
|
75
|
+
retunePlan: () => retunePlan,
|
|
69
76
|
sceneMany: () => sceneMany,
|
|
70
77
|
sceneNoise: () => sceneNoise,
|
|
71
78
|
sceneRecipe: () => sceneRecipe,
|
|
@@ -197,7 +204,7 @@ var GameAudio = (() => {
|
|
|
197
204
|
if (!group) return void 0;
|
|
198
205
|
if (group.maxDistance !== void 0 && !((request.distance ?? 0) < group.maxDistance)) return "distance";
|
|
199
206
|
if (group.cooldown !== void 0 && (budgets.get(request.group) ?? 0) > 0) return "cooldown";
|
|
200
|
-
const members = voices.filter((
|
|
207
|
+
const members = voices.filter((voice2) => voice2.group === request.group);
|
|
201
208
|
if (group.maxConcurrent === void 0 || members.length < group.maxConcurrent) return void 0;
|
|
202
209
|
if (!group.steal) return "group_full";
|
|
203
210
|
evicted.push(members[0].id);
|
|
@@ -209,9 +216,9 @@ var GameAudio = (() => {
|
|
|
209
216
|
const refusal = groupCheck(request, evicted);
|
|
210
217
|
if (refusal) return refuse(refusal);
|
|
211
218
|
const score = request.priority * request.gain;
|
|
212
|
-
const remaining = voices.filter((
|
|
219
|
+
const remaining = voices.filter((voice2) => !evicted.includes(voice2.id));
|
|
213
220
|
if (remaining.length >= maxVoices) {
|
|
214
|
-
const lowest = remaining.reduce((low,
|
|
221
|
+
const lowest = remaining.reduce((low, voice2) => voice2.score < low.score ? voice2 : low);
|
|
215
222
|
if (score <= lowest.score) return refuse("global_full");
|
|
216
223
|
evicted.push(lowest.id);
|
|
217
224
|
dropped += 1;
|
|
@@ -219,25 +226,25 @@ var GameAudio = (() => {
|
|
|
219
226
|
const group = request.group === void 0 ? void 0 : groups.get(request.group);
|
|
220
227
|
if (group?.cooldown !== void 0) budgets.set(request.group, group.cooldown);
|
|
221
228
|
const id = nextId++;
|
|
222
|
-
voices = [...voices.filter((
|
|
229
|
+
voices = [...voices.filter((voice2) => !evicted.includes(voice2.id)), { id, ...request.group === void 0 ? {} : { group: request.group }, score, remaining: request.length }];
|
|
223
230
|
peak = Math.max(peak, voices.length);
|
|
224
231
|
return { accepted: true, id, evicted };
|
|
225
232
|
},
|
|
226
233
|
release(id) {
|
|
227
|
-
voices = voices.filter((
|
|
234
|
+
voices = voices.filter((voice2) => voice2.id !== id);
|
|
228
235
|
},
|
|
229
236
|
setLength(id, seconds) {
|
|
230
|
-
const
|
|
231
|
-
if (
|
|
237
|
+
const voice2 = voices.find((entry) => entry.id === id);
|
|
238
|
+
if (voice2) voice2.remaining = seconds;
|
|
232
239
|
},
|
|
233
240
|
advance(dt) {
|
|
234
241
|
for (const [group, budget] of budgets) budgets.set(group, budget - dt);
|
|
235
242
|
const ended = [];
|
|
236
|
-
for (const
|
|
237
|
-
|
|
238
|
-
if (
|
|
243
|
+
for (const voice2 of voices) {
|
|
244
|
+
voice2.remaining -= dt;
|
|
245
|
+
if (voice2.remaining <= 0) ended.push(voice2.id);
|
|
239
246
|
}
|
|
240
|
-
if (ended.length > 0) voices = voices.filter((
|
|
247
|
+
if (ended.length > 0) voices = voices.filter((voice2) => voice2.remaining > 0);
|
|
241
248
|
return ended;
|
|
242
249
|
},
|
|
243
250
|
setGroup(id, group) {
|
|
@@ -345,14 +352,14 @@ var GameAudio = (() => {
|
|
|
345
352
|
const source = layer.source;
|
|
346
353
|
if (source.kind === "noise") return { kind: "noise", offset, loop: source.loop === true };
|
|
347
354
|
if (source.kind === "osc") return { kind: "osc", wave: source.wave, frequency: finishAutomation(pending.automation ?? {}, duration) };
|
|
348
|
-
const
|
|
355
|
+
const voice2 = pending.voice;
|
|
349
356
|
return {
|
|
350
357
|
kind: "formant_voice",
|
|
351
358
|
wave: source.wave,
|
|
352
|
-
frequency: finishAutomation(
|
|
353
|
-
vibrato: { rate:
|
|
354
|
-
formants:
|
|
355
|
-
breath: { ...
|
|
359
|
+
frequency: finishAutomation(voice2.frequency, duration),
|
|
360
|
+
vibrato: { rate: voice2.vibratoRate, depth: startFrequency(voice2.frequency) * source.vibrato.depth },
|
|
361
|
+
formants: voice2.formants,
|
|
362
|
+
breath: { ...voice2.breath, offset },
|
|
356
363
|
mix: source.mix
|
|
357
364
|
};
|
|
358
365
|
}
|
|
@@ -408,51 +415,51 @@ var GameAudio = (() => {
|
|
|
408
415
|
return { ...layer, filters: { ...layer.filters, bank } };
|
|
409
416
|
}
|
|
410
417
|
var layerEnd = (layer) => layer.at + layer.duration + layer.tail;
|
|
411
|
-
function planSound(
|
|
418
|
+
function planSound(recipe2, input = {}) {
|
|
412
419
|
const listener = input.listener ?? DEFAULT_LISTENER;
|
|
413
420
|
const distance = input.distance ?? (input.position ? listenerDistance(listener, input.position, input.distanceMode) : 0);
|
|
414
421
|
const occlusion = Math.min(1, Math.max(0, input.occlusion ?? 0));
|
|
415
|
-
const order =
|
|
416
|
-
const falloffLevel = falloffGain(
|
|
422
|
+
const order = recipe2.order ?? "layered";
|
|
423
|
+
const falloffLevel = falloffGain(recipe2.falloff, input.volume ?? 1, distance);
|
|
417
424
|
const level = occlusion > 0 ? falloffLevel * (1 - OCCLUSION_GAIN * occlusion) : falloffLevel;
|
|
418
|
-
const silent = isSilent(
|
|
425
|
+
const silent = isSilent(recipe2.falloff, falloffLevel);
|
|
419
426
|
const base = {
|
|
420
|
-
id:
|
|
421
|
-
bus:
|
|
427
|
+
id: recipe2.id,
|
|
428
|
+
bus: recipe2.bus,
|
|
422
429
|
order,
|
|
423
|
-
...
|
|
424
|
-
priority:
|
|
430
|
+
...recipe2.group === void 0 ? {} : { group: recipe2.group },
|
|
431
|
+
priority: recipe2.priority ?? busPriority(recipe2.bus),
|
|
425
432
|
level,
|
|
426
433
|
distance,
|
|
427
|
-
...
|
|
434
|
+
...recipe2.pan ? { pan: input.position ? panFor(listener, input.position) : 0 } : {}
|
|
428
435
|
};
|
|
429
|
-
if (silent) return
|
|
436
|
+
if (silent) return recipe2.keepSilent ? { ...base, silent: true, length: 0, layers: [] } : null;
|
|
430
437
|
const scope = { random: input.random ?? createRng(1), params: { ...input.params } };
|
|
431
|
-
for (const [name, range] of Object.entries(
|
|
438
|
+
for (const [name, range] of Object.entries(recipe2.vars ?? {})) if (scope.params[name] === void 0) scope.params[name] = draw(range, scope);
|
|
432
439
|
const defaults = {
|
|
433
|
-
...
|
|
434
|
-
...
|
|
440
|
+
...recipe2.duration === void 0 ? {} : { duration: draw(recipe2.duration, scope) },
|
|
441
|
+
...recipe2.tail === void 0 ? {} : { tail: recipe2.tail },
|
|
435
442
|
noiseSeconds: input.noiseSeconds ?? DEFAULT_NOISE_SECONDS
|
|
436
443
|
};
|
|
437
|
-
const divisor = cutoffDivisor(
|
|
438
|
-
const layers =
|
|
444
|
+
const divisor = cutoffDivisor(recipe2.falloff, distance) / (1 - OCCLUSION_CUTOFF * occlusion);
|
|
445
|
+
const layers = recipe2.layers.flatMap((layer) => planLayer(layer, scope, defaults, order !== "layered", level)).map((layer) => muffle(layer, divisor));
|
|
439
446
|
return { ...base, silent: false, length: Math.max(0, ...layers.map(layerEnd)), layers };
|
|
440
447
|
}
|
|
441
448
|
|
|
442
449
|
// src/loops.ts
|
|
443
450
|
var LOOP_START_ABOVE = 2e-3;
|
|
444
451
|
var LOOP_SMOOTHING = { volume: 0.15, frequency: 0.08 };
|
|
445
|
-
function createLoopState(
|
|
446
|
-
const startAbove =
|
|
452
|
+
function createLoopState(recipe2, open = false) {
|
|
453
|
+
const startAbove = recipe2.startAbove ?? LOOP_START_ABOVE;
|
|
447
454
|
let created = open;
|
|
448
|
-
let volume =
|
|
455
|
+
let volume = recipe2.volume ?? 0;
|
|
449
456
|
return {
|
|
450
457
|
set(state) {
|
|
451
458
|
volume = state.volume ?? volume;
|
|
452
459
|
const create = !created;
|
|
453
460
|
if (create && !(volume >= startAbove)) return null;
|
|
454
461
|
created = true;
|
|
455
|
-
const frequency =
|
|
462
|
+
const frequency = recipe2.source.kind === "osc" && state.frequency ? state.frequency : void 0;
|
|
456
463
|
return { create, volume, ...frequency === void 0 ? {} : { frequency } };
|
|
457
464
|
},
|
|
458
465
|
get created() {
|
|
@@ -520,6 +527,7 @@ var GameAudio = (() => {
|
|
|
520
527
|
function createSoundRegistry(banks = []) {
|
|
521
528
|
const sounds = /* @__PURE__ */ new Map();
|
|
522
529
|
const loops = /* @__PURE__ */ new Map();
|
|
530
|
+
const sets = /* @__PURE__ */ new Map();
|
|
523
531
|
const owners = /* @__PURE__ */ new Map();
|
|
524
532
|
const registry = {
|
|
525
533
|
register(bank) {
|
|
@@ -531,6 +539,11 @@ var GameAudio = (() => {
|
|
|
531
539
|
loops.set(loop.id, loop);
|
|
532
540
|
owners.set(loop.id, bank.id);
|
|
533
541
|
}
|
|
542
|
+
for (const set of bank.sets ?? []) {
|
|
543
|
+
if (set.variants.length === 0) throw new Error(`sound set ${set.id} has no variant`);
|
|
544
|
+
sets.set(set.id, set);
|
|
545
|
+
owners.set(set.id, bank.id);
|
|
546
|
+
}
|
|
534
547
|
},
|
|
535
548
|
patch(id, patch) {
|
|
536
549
|
const sound = sounds.get(id);
|
|
@@ -541,11 +554,14 @@ var GameAudio = (() => {
|
|
|
541
554
|
remove(id) {
|
|
542
555
|
sounds.delete(id);
|
|
543
556
|
loops.delete(id);
|
|
557
|
+
sets.delete(id);
|
|
544
558
|
owners.delete(id);
|
|
545
559
|
},
|
|
546
560
|
sound: (id) => sounds.get(id),
|
|
547
561
|
loop: (id) => loops.get(id),
|
|
562
|
+
set: (id) => sets.get(id),
|
|
548
563
|
soundIds: () => [...sounds.keys()],
|
|
564
|
+
setIds: () => [...sets.keys()],
|
|
549
565
|
loopIds: () => [...loops.keys()],
|
|
550
566
|
bankOf: (id) => owners.get(id)
|
|
551
567
|
};
|
|
@@ -780,6 +796,293 @@ var GameAudio = (() => {
|
|
|
780
796
|
var LIBRARY_LOOPS = [engine_saw_default, rotor_blades_default, crowd_rumble_default];
|
|
781
797
|
var LIBRARY_BANK = { id: "library", sounds: LIBRARY_SOUNDS, loops: LIBRARY_LOOPS };
|
|
782
798
|
|
|
799
|
+
// ../game-contracts/dist/index.js
|
|
800
|
+
var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
|
|
801
|
+
var DEFERRED_QUEUE_ID = {
|
|
802
|
+
DESTRUCTION_EVENTS: "destruction.events",
|
|
803
|
+
BREACH_PAINT: "floorplan.breach-paint",
|
|
804
|
+
AUDIO_VOICES: "audio.voices",
|
|
805
|
+
VEHICLE_CREASE: "vehicles.crease"
|
|
806
|
+
};
|
|
807
|
+
var DEFERRED_PRIORITY = {
|
|
808
|
+
[DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
|
|
809
|
+
[DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
|
|
810
|
+
[DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
|
|
811
|
+
[DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
|
|
812
|
+
};
|
|
813
|
+
var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
|
|
814
|
+
var GAIT_IDS = ["walk", "run"];
|
|
815
|
+
var LANDING_IDS = ["light", "heavy"];
|
|
816
|
+
var BODY_SOUND_IDS = [
|
|
817
|
+
"jump",
|
|
818
|
+
"cloth_rustle",
|
|
819
|
+
"body_fall",
|
|
820
|
+
"fall_damage",
|
|
821
|
+
"player_hurt",
|
|
822
|
+
"player_breath_run",
|
|
823
|
+
"player_death",
|
|
824
|
+
"bite",
|
|
825
|
+
"grab",
|
|
826
|
+
"punch_swing",
|
|
827
|
+
"punch_hit_body",
|
|
828
|
+
"punch_hit_face",
|
|
829
|
+
"punch_hit_object",
|
|
830
|
+
"kick_swing",
|
|
831
|
+
"kick_hit",
|
|
832
|
+
"kick_hit_object",
|
|
833
|
+
"shove_two_hands",
|
|
834
|
+
"knife_swing",
|
|
835
|
+
"knife_stab",
|
|
836
|
+
"knife_slash",
|
|
837
|
+
"knife_hit_object",
|
|
838
|
+
"blunt_swing",
|
|
839
|
+
"blunt_hit",
|
|
840
|
+
"blunt_hit_object",
|
|
841
|
+
"axe_swing",
|
|
842
|
+
"axe_chop",
|
|
843
|
+
"axe_hit_object"
|
|
844
|
+
];
|
|
845
|
+
var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
|
|
846
|
+
var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
|
|
847
|
+
var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing2) => `land_${surface}_${landing2}`));
|
|
848
|
+
var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
|
|
849
|
+
var SWING_LEAD_SECONDS = { punch: 0.07, kick: 0.09, shove: 0, blade: 0.05, blunt: 0.1, chop: 0.11 };
|
|
850
|
+
|
|
851
|
+
// src/recipes/body-foley.ts
|
|
852
|
+
var BODY_FOLEY_TAKES = 4;
|
|
853
|
+
var TAKES = [
|
|
854
|
+
{ f: 1, t: 1, v: 1 },
|
|
855
|
+
{ f: 1.08, t: 0.9, v: 0.92 },
|
|
856
|
+
{ f: 0.93, t: 1.12, v: 1.05 },
|
|
857
|
+
{ f: 1.03, t: 1.05, v: 0.88 }
|
|
858
|
+
];
|
|
859
|
+
var FOOTSTEP_FALLOFF = { model: "inverse_square", k: 0.03, cutoffK: 0.03, minGain: 4e-3 };
|
|
860
|
+
var BODY_FALLOFF = { model: "inverse_square", k: 0.012, cutoffK: 0.015, minGain: 4e-3 };
|
|
861
|
+
var SURFACES = {
|
|
862
|
+
concrete: { heel: { f: 95, d: 0.05, vol: 0.55 }, roll: { type: "bandpass", f: 2300, q: 1.1, d: 0.05, vol: 0.38 }, grains: { count: 2, f: 4200, q: 2, spread: 0.04, vol: 0.08 } },
|
|
863
|
+
asphalt: { heel: { f: 90, d: 0.05, vol: 0.5 }, roll: { type: "bandpass", f: 1700, q: 0.9, d: 0.06, vol: 0.36 }, grains: { count: 4, f: 3600, q: 2.5, spread: 0.06, vol: 0.1 } },
|
|
864
|
+
wood: { heel: { f: 130, d: 0.08, vol: 0.55 }, roll: { type: "lowpass", f: 1300, q: 0.8, d: 0.06, vol: 0.3 }, ring: [{ f: 210, d: 0.1, vol: 0.18 }, { f: 470, d: 0.06, vol: 0.06 }] },
|
|
865
|
+
tile: { heel: { f: 110, d: 0.04, vol: 0.45 }, roll: { type: "bandpass", f: 3600, q: 2, d: 0.03, vol: 0.42 }, ring: [{ f: 1900, d: 0.03, vol: 0.05 }] },
|
|
866
|
+
dirt: { heel: { f: 75, d: 0.06, vol: 0.45 }, roll: { type: "lowpass", f: 750, q: 0.7, d: 0.09, vol: 0.4 }, grains: { count: 3, f: 1800, q: 1.5, spread: 0.07, vol: 0.06 } },
|
|
867
|
+
grass: { heel: { f: 70, d: 0.05, vol: 0.32 }, roll: { type: "highpass", f: 2600, q: 0.7, d: 0.13, vol: 0.28 }, grains: { count: 4, f: 5200, q: 1.2, spread: 0.1, vol: 0.07 } },
|
|
868
|
+
gravel: { heel: { f: 80, d: 0.05, vol: 0.4 }, roll: { type: "lowpass", f: 950, q: 0.8, d: 0.1, vol: 0.3 }, grains: { count: 8, f: 2600, q: 3, spread: 0.13, vol: 0.2 } },
|
|
869
|
+
sand: { heel: { f: 65, d: 0.06, vol: 0.3 }, roll: { type: "bandpass", f: 1500, q: 0.6, d: 0.16, vol: 0.3 }, grains: { count: 5, f: 4800, q: 1.5, spread: 0.12, vol: 0.05 } },
|
|
870
|
+
metal: { heel: { f: 120, d: 0.05, vol: 0.5 }, roll: { type: "bandpass", f: 2800, q: 1.5, d: 0.03, vol: 0.25 }, ring: [{ f: 420, d: 0.25, vol: 0.16 }, { f: 1130, d: 0.18, vol: 0.09 }, { f: 2470, d: 0.1, vol: 0.04 }] },
|
|
871
|
+
water: { heel: { f: 70, d: 0.04, vol: 0.25 }, roll: { type: "bandpass", f: 3e3, q: 1.2, d: 0.1, vol: 0.18 }, splash: { f: 1100, f2: 380, d: 0.22, vol: 0.55 }, grains: { count: 4, f: 2400, q: 6, spread: 0.18, vol: 0.12 } },
|
|
872
|
+
glass: { heel: { f: 90, d: 0.04, vol: 0.4 }, roll: { type: "highpass", f: 4500, q: 0.8, d: 0.06, vol: 0.2 }, grains: { count: 9, f: 6200, q: 5, spread: 0.12, vol: 0.22 }, ring: [{ f: 3900, d: 0.04, vol: 0.04 }, { f: 5300, d: 0.03, vol: 0.03 }] },
|
|
873
|
+
carpet: { heel: { f: 70, d: 0.05, vol: 0.32 }, roll: { type: "lowpass", f: 420, q: 0.7, d: 0.07, vol: 0.3 } }
|
|
874
|
+
};
|
|
875
|
+
var GAITS = {
|
|
876
|
+
walk: { vol: 1, bright: 1, toe: 0.075, short: 1 },
|
|
877
|
+
run: { vol: 1.35, bright: 1.25, toe: 0.035, short: 0.75 }
|
|
878
|
+
};
|
|
879
|
+
var grainLayers = (g, take, start, scale) => Array.from(
|
|
880
|
+
{ length: g.count },
|
|
881
|
+
(_, i) => sceneNoise({ type: "bandpass", f: [g.f * take.f * 0.85, g.f * take.f * 1.15], q: g.q, d: [8e-3, 0.02], vol: g.vol * scale * take.v, delay: start + (i + 0.5) / g.count * g.spread * take.t })
|
|
882
|
+
);
|
|
883
|
+
var ringLayers = (rings, take, scale, at = 0) => rings.map((r) => sceneTone({ f: r.f * take.f, d: r.d * take.t, vol: r.vol * scale * take.v, delay: at }));
|
|
884
|
+
function footLayers(surface, take, gait, weight = 1) {
|
|
885
|
+
const s = SURFACES[surface];
|
|
886
|
+
const toe = gait.toe * take.t;
|
|
887
|
+
return [
|
|
888
|
+
sceneTone({ f: s.heel.f * take.f * weight ** -0.3, f2: s.heel.f * take.f * 0.6, d: s.heel.d * gait.short * take.t * weight ** 0.5, vol: s.heel.vol * gait.vol * take.v * weight ** 0.5 }),
|
|
889
|
+
sceneNoise({ type: "lowpass", f: 500 * gait.bright, d: 0.02 * take.t, vol: 0.18 * gait.vol * take.v * weight ** 0.5 }),
|
|
890
|
+
sceneNoise({ type: s.roll.type, f: [s.roll.f * take.f * gait.bright * 0.92, s.roll.f * take.f * gait.bright * 1.08], q: s.roll.q, d: s.roll.d * gait.short * take.t * weight ** 0.3, vol: s.roll.vol * gait.vol * take.v, delay: toe }),
|
|
891
|
+
...s.grains ? grainLayers(s.grains, take, toe * 0.5, gait.vol * weight ** 0.5) : [],
|
|
892
|
+
...s.ring ? ringLayers(s.ring, take, gait.vol * weight ** 0.5) : [],
|
|
893
|
+
...s.splash ? [sceneNoise({ type: "bandpass", f: s.splash.f * take.f, f2: s.splash.f2 * take.f, q: 1.4, d: s.splash.d * take.t * weight ** 0.4, vol: s.splash.vol * gait.vol * take.v * weight ** 0.5 })] : []
|
|
894
|
+
];
|
|
895
|
+
}
|
|
896
|
+
var quieter = (level, layers) => layers.map((layer) => {
|
|
897
|
+
const gain = layer.gain ?? 1;
|
|
898
|
+
if (typeof gain === "number") return { ...layer, gain: gain * level };
|
|
899
|
+
if (Array.isArray(gain)) return { ...layer, gain: [gain[0] * level, gain[1] * level] };
|
|
900
|
+
return layer;
|
|
901
|
+
});
|
|
902
|
+
var recipe = (id, layers, falloff, bus = "world") => ({ id, bus, pan: true, layers, falloff, sources: ["sdk"] });
|
|
903
|
+
var takeId = (id, index) => `${id}_${String(index + 1).padStart(2, "0")}`;
|
|
904
|
+
function takes(id, layersOf, falloff, rate = [0.96, 1.04], bus) {
|
|
905
|
+
const sounds = TAKES.map((take, index) => recipe(takeId(id, index), layersOf(take, index), falloff, bus));
|
|
906
|
+
return { sounds, set: { id, variants: sounds.map((sound) => sound.id), rate } };
|
|
907
|
+
}
|
|
908
|
+
var footstep = (surface, gait) => takes(footstepSoundId(surface, gait), (take) => footLayers(surface, take, GAITS[gait]), FOOTSTEP_FALLOFF);
|
|
909
|
+
var LANDINGS = { light: { weight: 1.4, cloth: 0.12 }, heavy: { weight: 2.4, cloth: 0.22 } };
|
|
910
|
+
var LANDING_LEVEL = 0.65;
|
|
911
|
+
var landing = (surface, landing2) => takes(
|
|
912
|
+
`land_${surface}_${landing2}`,
|
|
913
|
+
(take) => quieter(LANDING_LEVEL, [
|
|
914
|
+
// both feet, a hair apart, then the knees taking the weight (a low body thump) and the clothes settling
|
|
915
|
+
...footLayers(surface, take, GAITS.run, LANDINGS[landing2].weight),
|
|
916
|
+
...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((layer) => ({ ...layer, at: (typeof layer.at === "number" ? layer.at : 0) + 0.018 * take.t })),
|
|
917
|
+
sceneTone({ f: 60 * take.f, f2: 38, d: 0.14 * LANDINGS[landing2].weight * take.t, vol: 0.22 * LANDINGS[landing2].weight ** 0.7 * take.v, delay: 0.02 }),
|
|
918
|
+
sceneNoise({ type: "bandpass", f: [1800, 2600], q: 0.8, d: 0.18, vol: LANDINGS[landing2].cloth * take.v, delay: 0.04 })
|
|
919
|
+
]),
|
|
920
|
+
FOOTSTEP_FALLOFF
|
|
921
|
+
);
|
|
922
|
+
var whoosh = (blow, from, to, d, vol, q = 1.6) => (take) => [
|
|
923
|
+
// the air: a band sweeping up as the limb speeds up; it peaks `SWING_LEAD_SECONDS` in, the moment before the contact, then dies away
|
|
924
|
+
sceneNoise({ type: "bandpass", f: from * take.f, f2: to * take.f, q, a: SWING_LEAD_SECONDS[blow], d: d * take.t, vol: vol * take.v }),
|
|
925
|
+
sceneNoise({ type: "lowpass", f: 600 * take.f, a: SWING_LEAD_SECONDS[blow], d: d * take.t * 0.8, vol: vol * 0.35 * take.v })
|
|
926
|
+
];
|
|
927
|
+
var fleshHit = (o) => (take) => [
|
|
928
|
+
sceneNoise({ type: "bandpass", f: o.slapF * take.f, q: 1.2, d: 0.03 * take.t, vol: o.slap * take.v }),
|
|
929
|
+
sceneNoise({ type: "highpass", f: 3500 * take.f, d: 0.012, vol: o.slap * 0.4 * take.v }),
|
|
930
|
+
sceneTone({ f: o.thudF * take.f, f2: o.thudF * 0.5, d: o.thudD * take.t, vol: o.thud * take.v }),
|
|
931
|
+
sceneNoise({ type: "lowpass", f: 600 * take.f, d: o.thudD * 1.2 * take.t, vol: o.thud * 0.5 * take.v }),
|
|
932
|
+
...o.crack ? [sceneNoise({ type: "highpass", f: 2500, d: 0.015, vol: o.crack * take.v, delay: 6e-3 }), sceneTone({ wave: "square", f: 900 * take.f, f2: 300, d: 0.02, vol: o.crack * 0.15 * take.v, delay: 6e-3 })] : [],
|
|
933
|
+
...o.wet ? [sceneNoise({ type: "bandpass", f: 1400 * take.f, f2: 500, q: 2.5, d: 0.12 * take.t, vol: o.wet * take.v, delay: 0.01 })] : []
|
|
934
|
+
];
|
|
935
|
+
var knock = (o) => (take) => [
|
|
936
|
+
sceneNoise({ type: "bandpass", f: o.clickF * take.f, q: 1.5, d: 0.018, vol: o.click * take.v }),
|
|
937
|
+
...o.tones.map(([f, d, vol]) => sceneTone({ f: f * take.f, d: d * take.t, vol: vol * take.v }))
|
|
938
|
+
];
|
|
939
|
+
var voice = (o) => (take) => [
|
|
940
|
+
sceneVoice({ f0: o.f0 * take.f, f1: o.f1 * take.f, d: o.d * take.t, fA: o.fA * take.f, fB: o.fB * take.f, noise: o.noise, vol: o.vol * take.v })
|
|
941
|
+
];
|
|
942
|
+
var BODY = {
|
|
943
|
+
jump: (take) => [
|
|
944
|
+
sceneNoise({ type: "bandpass", f: 900 * take.f, f2: 1700 * take.f, q: 1, a: 0.03, d: 0.12, vol: 0.18 * take.v }),
|
|
945
|
+
sceneNoise({ type: "bandpass", f: 2200 * take.f, q: 0.8, d: 0.04, vol: 0.12 * take.v }),
|
|
946
|
+
sceneTone({ f: 85 * take.f, f2: 60, d: 0.04, vol: 0.2 * take.v })
|
|
947
|
+
],
|
|
948
|
+
cloth_rustle: (take) => [
|
|
949
|
+
{ ...sceneNoise({ type: "bandpass", f: 2400 * take.f, q: 0.8, a: 0.06, d: 0.22 * take.t, vol: 0.12 * take.v }), modulators: [{ target: "gain", wave: "triangle", rate: [9, 14], depth: 0.5 }] }
|
|
950
|
+
],
|
|
951
|
+
body_fall: (take) => [
|
|
952
|
+
sceneTone({ f: 58 * take.f, f2: 35, d: 0.28 * take.t, vol: 0.9 * take.v }),
|
|
953
|
+
sceneNoise({ type: "lowpass", f: 480 * take.f, d: 0.3 * take.t, vol: 0.55 * take.v }),
|
|
954
|
+
sceneNoise({ type: "bandpass", f: 1300 * take.f, q: 1, d: 0.05, vol: 0.3 * take.v }),
|
|
955
|
+
// the limbs a moment later, and the clothes
|
|
956
|
+
sceneTone({ f: 75 * take.f, f2: 45, d: 0.12, vol: 0.4 * take.v, delay: 0.13 * take.t }),
|
|
957
|
+
sceneNoise({ type: "bandpass", f: 2200, q: 0.8, d: 0.2, vol: 0.12 * take.v, delay: 0.05 })
|
|
958
|
+
],
|
|
959
|
+
fall_damage: (take) => [
|
|
960
|
+
...BODY.body_fall(take),
|
|
961
|
+
sceneNoise({ type: "highpass", f: 2800, d: 0.02, vol: 0.35 * take.v, delay: 0.01 }),
|
|
962
|
+
...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((layer) => ({ ...layer, at: 0.08 }))
|
|
963
|
+
],
|
|
964
|
+
player_hurt: voice({ f0: 150, f1: 105, d: 0.26, fA: 620, fB: 1100, noise: 0.3, vol: 0.35 }),
|
|
965
|
+
player_breath_run: (take) => [
|
|
966
|
+
sceneNoise({ type: "bandpass", f: 1200 * take.f, q: 1, a: 0.08, d: 0.22 * take.t, vol: 0.25 * take.v }),
|
|
967
|
+
sceneNoise({ type: "bandpass", f: 900 * take.f, q: 1, a: 0.06, d: 0.26 * take.t, vol: 0.3 * take.v, delay: 0.3 * take.t })
|
|
968
|
+
],
|
|
969
|
+
player_death: voice({ f0: 135, f1: 68, d: 1, fA: 560, fB: 980, noise: 0.35, vol: 0.4 }),
|
|
970
|
+
bite: (take) => [
|
|
971
|
+
sceneNoise({ type: "highpass", f: 2600, d: 0.015, vol: 0.4 * take.v }),
|
|
972
|
+
sceneNoise({ type: "bandpass", f: 1600 * take.f, f2: 450, q: 2.2, d: 0.16 * take.t, vol: 0.55 * take.v, delay: 0.01 }),
|
|
973
|
+
sceneNoise({ type: "bandpass", f: 3200 * take.f, f2: 1800, q: 1.2, d: 0.22 * take.t, vol: 0.25 * take.v, delay: 0.09 }),
|
|
974
|
+
sceneTone({ f: 120 * take.f, f2: 70, d: 0.06, vol: 0.3 * take.v })
|
|
975
|
+
],
|
|
976
|
+
grab: (take) => [
|
|
977
|
+
sceneNoise({ type: "bandpass", f: 1900 * take.f, q: 0.9, a: 4e-3, d: 0.13 * take.t, vol: 0.32 * take.v }),
|
|
978
|
+
sceneTone({ f: 95 * take.f, f2: 60, d: 0.07, vol: 0.3 * take.v }),
|
|
979
|
+
sceneNoise({ type: "lowpass", f: 700, d: 0.08, vol: 0.2 * take.v })
|
|
980
|
+
],
|
|
981
|
+
punch_swing: whoosh("punch", 450, 1500, 0.16, 0.9),
|
|
982
|
+
punch_hit_body: fleshHit({ slap: 0.55, slapF: 1500, thud: 0.6, thudF: 115, thudD: 0.09 }),
|
|
983
|
+
punch_hit_face: fleshHit({ slap: 0.75, slapF: 2300, thud: 0.45, thudF: 150, thudD: 0.07, crack: 0.25 }),
|
|
984
|
+
punch_hit_object: knock({ clickF: 1800, click: 0.3, tones: [[210, 0.04, 0.25]] }),
|
|
985
|
+
kick_swing: whoosh("kick", 280, 950, 0.22, 1, 1.3),
|
|
986
|
+
kick_hit: fleshHit({ slap: 0.45, slapF: 1100, thud: 0.85, thudF: 82, thudD: 0.14 }),
|
|
987
|
+
kick_hit_object: knock({ clickF: 1e3, click: 0.3, tones: [[92, 0.08, 0.5]] }),
|
|
988
|
+
shove_two_hands: (take) => [
|
|
989
|
+
sceneNoise({ type: "bandpass", f: 950 * take.f, q: 0.9, a: 0.01, d: 0.13 * take.t, vol: 0.38 * take.v }),
|
|
990
|
+
sceneTone({ f: 100 * take.f, f2: 60, d: 0.1 * take.t, vol: 0.45 * take.v }),
|
|
991
|
+
sceneNoise({ type: "bandpass", f: 2300, q: 0.8, d: 0.16, vol: 0.12 * take.v, delay: 0.03 })
|
|
992
|
+
],
|
|
993
|
+
knife_swing: whoosh("blade", 1900, 4600, 0.12, 1.1, 2.2),
|
|
994
|
+
knife_stab: fleshHit({ slap: 0.2, slapF: 1200, thud: 0.4, thudF: 160, thudD: 0.06, wet: 0.5 }),
|
|
995
|
+
knife_slash: (take) => [
|
|
996
|
+
sceneNoise({ type: "highpass", f: 3200 * take.f, f2: 1800, d: 0.13 * take.t, vol: 0.35 * take.v }),
|
|
997
|
+
sceneNoise({ type: "bandpass", f: 1300 * take.f, f2: 600, q: 2.5, d: 0.12 * take.t, vol: 0.35 * take.v, delay: 0.02 })
|
|
998
|
+
],
|
|
999
|
+
knife_hit_object: knock({ clickF: 3200, click: 0.25, tones: [[2800, 0.08, 0.07], [4150, 0.06, 0.05]] }),
|
|
1000
|
+
blunt_swing: whoosh("blunt", 220, 760, 0.28, 1.1, 1.5),
|
|
1001
|
+
blunt_hit: fleshHit({ slap: 0.35, slapF: 900, thud: 1, thudF: 70, thudD: 0.18, crack: 0.3 }),
|
|
1002
|
+
blunt_hit_object: knock({ clickF: 1400, click: 0.35, tones: [[300, 0.12, 0.25], [760, 0.08, 0.12]] }),
|
|
1003
|
+
axe_swing: whoosh("chop", 190, 620, 0.3, 1.2, 1.4),
|
|
1004
|
+
axe_chop: fleshHit({ slap: 0.3, slapF: 1400, thud: 0.75, thudF: 140, thudD: 0.1, crack: 0.35, wet: 0.45 }),
|
|
1005
|
+
axe_hit_object: knock({ clickF: 2200, click: 0.4, tones: [[1500, 0.15, 0.1], [2300, 0.1, 0.06], [140, 0.1, 0.35]] })
|
|
1006
|
+
};
|
|
1007
|
+
var BODY_BUS = { player_hurt: "voices", player_death: "voices", player_breath_run: "voices", fall_damage: "voices" };
|
|
1008
|
+
var built = [
|
|
1009
|
+
...SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstep(surface, gait))),
|
|
1010
|
+
...SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((l) => landing(surface, l))),
|
|
1011
|
+
...BODY_SOUND_IDS.map((id) => takes(id, BODY[id], BODY_FALLOFF, id.endsWith("_swing") ? [0.94, 1.06] : [0.96, 1.04], BODY_BUS[id]))
|
|
1012
|
+
];
|
|
1013
|
+
var BODY_FOLEY_BANK = {
|
|
1014
|
+
id: "body_foley",
|
|
1015
|
+
sounds: built.flatMap((entry) => entry.sounds),
|
|
1016
|
+
sets: built.map((entry) => entry.set)
|
|
1017
|
+
};
|
|
1018
|
+
var BODY_FOLEY_SET_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
|
|
1019
|
+
|
|
1020
|
+
// src/variation.ts
|
|
1021
|
+
var tuneAutomation = (automation, rate) => {
|
|
1022
|
+
const tuned = { ...automation };
|
|
1023
|
+
if (automation.value !== void 0) tuned.value = automation.value * rate;
|
|
1024
|
+
if (automation.from !== void 0) tuned.from = automation.from * rate;
|
|
1025
|
+
if (automation.to !== void 0) tuned.to = automation.to * rate;
|
|
1026
|
+
if (automation.at !== void 0) tuned.at = automation.at / rate;
|
|
1027
|
+
return tuned;
|
|
1028
|
+
};
|
|
1029
|
+
var tuneFilter = (filter, rate) => ({ ...filter, frequency: tuneAutomation(filter.frequency, rate) });
|
|
1030
|
+
var tuneSource = (source, rate) => {
|
|
1031
|
+
switch (source.kind) {
|
|
1032
|
+
case "noise":
|
|
1033
|
+
return source;
|
|
1034
|
+
case "osc":
|
|
1035
|
+
return { ...source, frequency: tuneAutomation(source.frequency, rate) };
|
|
1036
|
+
case "formant_voice":
|
|
1037
|
+
return {
|
|
1038
|
+
...source,
|
|
1039
|
+
frequency: tuneAutomation(source.frequency, rate),
|
|
1040
|
+
formants: source.formants.map((formant) => ({ ...formant, frequency: formant.frequency * rate })),
|
|
1041
|
+
breath: { ...source.breath, frequency: source.breath.frequency * rate }
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
var tuneModulator = (modulator, rate) => ({
|
|
1046
|
+
...modulator,
|
|
1047
|
+
rate: modulator.rate * rate,
|
|
1048
|
+
...modulator.target === "frequency" ? { depth: modulator.depth * rate } : {}
|
|
1049
|
+
});
|
|
1050
|
+
var tuneLayer = (layer, rate) => ({
|
|
1051
|
+
...layer,
|
|
1052
|
+
at: layer.at / rate,
|
|
1053
|
+
duration: layer.duration / rate,
|
|
1054
|
+
tail: layer.tail / rate,
|
|
1055
|
+
source: tuneSource(layer.source, rate),
|
|
1056
|
+
...layer.filters ? { filters: { ...layer.filters, bank: layer.filters.bank.map((filter) => tuneFilter(filter, rate)) } } : {},
|
|
1057
|
+
modulators: layer.modulators.map((modulator) => tuneModulator(modulator, rate)),
|
|
1058
|
+
envelope: layer.envelope.map((point) => ({ ...point, time: point.time / rate }))
|
|
1059
|
+
});
|
|
1060
|
+
function retunePlan(plan, rate) {
|
|
1061
|
+
if (rate === 1 || !(rate > 0)) return plan;
|
|
1062
|
+
return { ...plan, length: plan.length / rate, layers: plan.layers.map((layer) => tuneLayer(layer, rate)) };
|
|
1063
|
+
}
|
|
1064
|
+
function createSetPicker() {
|
|
1065
|
+
const last = /* @__PURE__ */ new Map();
|
|
1066
|
+
return {
|
|
1067
|
+
pick(set, random) {
|
|
1068
|
+
const count = set.variants.length;
|
|
1069
|
+
const previous = last.get(set.id);
|
|
1070
|
+
let index;
|
|
1071
|
+
if (count === 1) index = 0;
|
|
1072
|
+
else if (set.repeat || previous === void 0) index = Math.min(count - 1, Math.floor(random() * count));
|
|
1073
|
+
else {
|
|
1074
|
+
index = Math.min(count - 2, Math.floor(random() * (count - 1)));
|
|
1075
|
+
if (index >= previous) index += 1;
|
|
1076
|
+
}
|
|
1077
|
+
last.set(set.id, index);
|
|
1078
|
+
const [min, max] = set.rate ?? [1, 1];
|
|
1079
|
+
const rate = min === max ? min : min + random() * (max - min);
|
|
1080
|
+
return { id: set.variants[index], rate };
|
|
1081
|
+
},
|
|
1082
|
+
reset: () => last.clear()
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
783
1086
|
// src/webaudio/noise.ts
|
|
784
1087
|
function fillNoise(random, length) {
|
|
785
1088
|
const samples = new Float32Array(length);
|
|
@@ -908,11 +1211,11 @@ var GameAudio = (() => {
|
|
|
908
1211
|
// src/webaudio/realize-loop.ts
|
|
909
1212
|
var fixed = (range) => typeof range === "number" ? range : 0;
|
|
910
1213
|
var fixedFrequency = (automation) => fixed("set" in automation ? automation.set : automation.from);
|
|
911
|
-
function sourceOf(
|
|
912
|
-
if (
|
|
1214
|
+
function sourceOf(recipe2, target) {
|
|
1215
|
+
if (recipe2.source.kind === "noise") return { source: noiseSource(target, true) };
|
|
913
1216
|
const oscillator = target.context.createOscillator();
|
|
914
|
-
oscillator.type =
|
|
915
|
-
if (
|
|
1217
|
+
oscillator.type = recipe2.source.wave;
|
|
1218
|
+
if (recipe2.source.frequency) oscillator.frequency.value = fixedFrequency(recipe2.source.frequency);
|
|
916
1219
|
return { source: oscillator, frequency: oscillator.frequency };
|
|
917
1220
|
}
|
|
918
1221
|
function stopAll(sources) {
|
|
@@ -925,23 +1228,23 @@ var GameAudio = (() => {
|
|
|
925
1228
|
}
|
|
926
1229
|
};
|
|
927
1230
|
}
|
|
928
|
-
function realizeGameLoop(
|
|
1231
|
+
function realizeGameLoop(recipe2, target) {
|
|
929
1232
|
const { context } = target;
|
|
930
1233
|
const gain = context.createGain();
|
|
931
|
-
const filters = (
|
|
1234
|
+
const filters = (recipe2.filters?.bank ?? []).map(() => context.createBiquadFilter());
|
|
932
1235
|
gain.gain.value = 0;
|
|
933
1236
|
const last = filters.reduce((previous, filter) => previous ? previous.connect(filter) : filter, void 0);
|
|
934
1237
|
last?.connect(gain);
|
|
935
1238
|
gain.connect(target.output);
|
|
936
|
-
const { source, frequency } = sourceOf(
|
|
937
|
-
|
|
1239
|
+
const { source, frequency } = sourceOf(recipe2, target);
|
|
1240
|
+
recipe2.filters?.bank.forEach((spec, index) => {
|
|
938
1241
|
const filter = filters[index];
|
|
939
1242
|
filter.type = spec.type;
|
|
940
1243
|
filter.frequency.value = fixedFrequency(spec.frequency);
|
|
941
1244
|
if (spec.q !== void 0) filter.Q.value = fixed(spec.q);
|
|
942
1245
|
});
|
|
943
1246
|
const sources = [source];
|
|
944
|
-
for (const modulator of
|
|
1247
|
+
for (const modulator of recipe2.modulators ?? []) {
|
|
945
1248
|
const lfo = context.createOscillator();
|
|
946
1249
|
const depth = context.createGain();
|
|
947
1250
|
const stage = context.createGain();
|
|
@@ -961,14 +1264,14 @@ var GameAudio = (() => {
|
|
|
961
1264
|
source.start();
|
|
962
1265
|
return { gain, source, ...frequency ? { frequency } : {}, stop: stopAll(sources) };
|
|
963
1266
|
}
|
|
964
|
-
function realizeChainLoop(
|
|
1267
|
+
function realizeChainLoop(recipe2, target, volume) {
|
|
965
1268
|
const { context } = target;
|
|
966
|
-
const { source, frequency } = sourceOf(
|
|
967
|
-
const filters = (
|
|
1269
|
+
const { source, frequency } = sourceOf(recipe2, target);
|
|
1270
|
+
const filters = (recipe2.filters?.bank ?? []).map(
|
|
968
1271
|
(spec) => filterNode(context, { type: spec.type, frequency: { value: fixedFrequency(spec.frequency) }, ...spec.q === void 0 ? {} : { q: fixed(spec.q) } }, target.time)
|
|
969
1272
|
);
|
|
970
1273
|
const gain = gainNode(context, volume);
|
|
971
|
-
const lfos = (
|
|
1274
|
+
const lfos = (recipe2.modulators ?? []).map((modulator) => {
|
|
972
1275
|
const lfo = context.createOscillator();
|
|
973
1276
|
if (modulator.wave) lfo.type = modulator.wave;
|
|
974
1277
|
lfo.frequency.value = fixed(modulator.rate);
|
|
@@ -990,9 +1293,9 @@ var GameAudio = (() => {
|
|
|
990
1293
|
var INERT = { set() {
|
|
991
1294
|
}, stop() {
|
|
992
1295
|
} };
|
|
993
|
-
function createLoopHandle(
|
|
994
|
-
if (!
|
|
995
|
-
const loopRecipe =
|
|
1296
|
+
function createLoopHandle(recipe2, host, volume) {
|
|
1297
|
+
if (!recipe2) return INERT;
|
|
1298
|
+
const loopRecipe = recipe2;
|
|
996
1299
|
const isChain = loopRecipe.order === "chain_loop";
|
|
997
1300
|
const opening = volume ?? loopRecipe.volume ?? 0;
|
|
998
1301
|
const smoothing = loopRecipe.smoothing ?? LOOP_SMOOTHING;
|
|
@@ -1083,23 +1386,28 @@ var GameAudio = (() => {
|
|
|
1083
1386
|
const maxLatencyMs = options.maxLatencyMs ?? 250;
|
|
1084
1387
|
let jobs = [];
|
|
1085
1388
|
let scheduled = false;
|
|
1389
|
+
let attached = false;
|
|
1390
|
+
const runNext = () => {
|
|
1391
|
+
const job = jobs.shift();
|
|
1392
|
+
if (nowMs() - job.queuedAt > maxLatencyMs) job.drop();
|
|
1393
|
+
else job.run();
|
|
1394
|
+
};
|
|
1086
1395
|
const drain = () => {
|
|
1087
1396
|
scheduled = false;
|
|
1397
|
+
if (attached) return;
|
|
1088
1398
|
const began = nowMs();
|
|
1089
1399
|
while (jobs.length > 0) {
|
|
1090
|
-
|
|
1091
|
-
if (nowMs() - job.queuedAt > maxLatencyMs) job.drop();
|
|
1092
|
-
else job.run();
|
|
1400
|
+
runNext();
|
|
1093
1401
|
if (nowMs() - began >= budgetMs) break;
|
|
1094
1402
|
}
|
|
1095
1403
|
if (jobs.length > 0) schedule();
|
|
1096
1404
|
};
|
|
1097
1405
|
const schedule = () => {
|
|
1098
|
-
if (scheduled) return;
|
|
1406
|
+
if (scheduled || attached) return;
|
|
1099
1407
|
scheduled = true;
|
|
1100
1408
|
scheduleTask(drain);
|
|
1101
1409
|
};
|
|
1102
|
-
|
|
1410
|
+
const queue = {
|
|
1103
1411
|
push(job) {
|
|
1104
1412
|
jobs.push({ ...job, queuedAt: nowMs() });
|
|
1105
1413
|
schedule();
|
|
@@ -1110,8 +1418,33 @@ var GameAudio = (() => {
|
|
|
1110
1418
|
},
|
|
1111
1419
|
get size() {
|
|
1112
1420
|
return jobs.length;
|
|
1421
|
+
},
|
|
1422
|
+
deferredQueue: () => deferredQueue
|
|
1423
|
+
};
|
|
1424
|
+
const deferredQueue = {
|
|
1425
|
+
id: DEFERRED_QUEUE_ID.AUDIO_VOICES,
|
|
1426
|
+
pending: () => jobs.length,
|
|
1427
|
+
run(deadline, clock) {
|
|
1428
|
+
let ran = 0;
|
|
1429
|
+
while (jobs.length > 0) {
|
|
1430
|
+
runNext();
|
|
1431
|
+
ran++;
|
|
1432
|
+
if (clock() >= deadline) break;
|
|
1433
|
+
}
|
|
1434
|
+
return ran;
|
|
1435
|
+
},
|
|
1436
|
+
flush() {
|
|
1437
|
+
while (jobs.length > 0) runNext();
|
|
1438
|
+
},
|
|
1439
|
+
attach() {
|
|
1440
|
+
attached = true;
|
|
1441
|
+
},
|
|
1442
|
+
detach() {
|
|
1443
|
+
attached = false;
|
|
1444
|
+
if (jobs.length > 0) schedule();
|
|
1113
1445
|
}
|
|
1114
1446
|
};
|
|
1447
|
+
return queue;
|
|
1115
1448
|
}
|
|
1116
1449
|
|
|
1117
1450
|
// src/webaudio/realize-game.ts
|
|
@@ -1489,7 +1822,7 @@ var GameAudio = (() => {
|
|
|
1489
1822
|
}
|
|
1490
1823
|
|
|
1491
1824
|
// src/webaudio/engine.ts
|
|
1492
|
-
var usesNoise = (
|
|
1825
|
+
var usesNoise = (recipe2) => recipe2.layers.some((layer) => layer.source.kind !== "osc");
|
|
1493
1826
|
var planUsesNoise = (plan) => plan.layers.some((layer) => layer.source.kind !== "osc");
|
|
1494
1827
|
function createAudioEngine(options = {}) {
|
|
1495
1828
|
const seed = options.seed ?? 1;
|
|
@@ -1552,7 +1885,7 @@ var GameAudio = (() => {
|
|
|
1552
1885
|
lastTime = context.currentTime;
|
|
1553
1886
|
if (elapsed > 0) for (const id of voices.advance(elapsed)) active.delete(id);
|
|
1554
1887
|
};
|
|
1555
|
-
const inputFor = (
|
|
1888
|
+
const inputFor = (recipe2, playOptions, random) => ({
|
|
1556
1889
|
random,
|
|
1557
1890
|
listener,
|
|
1558
1891
|
noiseSeconds: mix.noise.seconds,
|
|
@@ -1561,7 +1894,7 @@ var GameAudio = (() => {
|
|
|
1561
1894
|
...playOptions.distance === void 0 ? {} : { distance: playOptions.distance },
|
|
1562
1895
|
...playOptions.position ? { position: playOptions.position } : {},
|
|
1563
1896
|
...playOptions.params ? { params: playOptions.params } : {},
|
|
1564
|
-
...options.occlusion && playOptions.position &&
|
|
1897
|
+
...options.occlusion && playOptions.position && recipe2.falloff?.model !== "none" ? { occlusion: options.occlusion(playOptions.position) } : {}
|
|
1565
1898
|
});
|
|
1566
1899
|
const startQueue = options.deferredStart ? createStartQueue(options.deferredStart) : null;
|
|
1567
1900
|
const build = (plan, store, delay) => {
|
|
@@ -1575,20 +1908,20 @@ var GameAudio = (() => {
|
|
|
1575
1908
|
});
|
|
1576
1909
|
};
|
|
1577
1910
|
const deferVoice = (plan, store, voiceId, delay) => {
|
|
1578
|
-
let
|
|
1911
|
+
let built2 = null;
|
|
1579
1912
|
let cancelled = false;
|
|
1580
1913
|
const pending = {
|
|
1581
1914
|
get sources() {
|
|
1582
|
-
return
|
|
1915
|
+
return built2?.sources ?? [];
|
|
1583
1916
|
},
|
|
1584
1917
|
stop() {
|
|
1585
1918
|
cancelled = true;
|
|
1586
|
-
|
|
1919
|
+
built2?.stop();
|
|
1587
1920
|
}
|
|
1588
1921
|
};
|
|
1589
1922
|
startQueue.push({
|
|
1590
1923
|
run() {
|
|
1591
|
-
if (!cancelled && context)
|
|
1924
|
+
if (!cancelled && context) built2 = build(plan, store, delay);
|
|
1592
1925
|
},
|
|
1593
1926
|
drop() {
|
|
1594
1927
|
if (voiceId === void 0) return;
|
|
@@ -1613,8 +1946,8 @@ var GameAudio = (() => {
|
|
|
1613
1946
|
}
|
|
1614
1947
|
};
|
|
1615
1948
|
};
|
|
1616
|
-
const admit = (
|
|
1617
|
-
const decision = voices.request({ ...
|
|
1949
|
+
const admit = (recipe2, gain, distance) => {
|
|
1950
|
+
const decision = voices.request({ ...recipe2.group ? { group: recipe2.group } : {}, priority: recipe2.priority ?? busPriority(recipe2.bus), gain, distance, length: 0 });
|
|
1618
1951
|
if (!decision.accepted) return null;
|
|
1619
1952
|
for (const id of decision.evicted) {
|
|
1620
1953
|
active.get(id)?.stop();
|
|
@@ -1622,22 +1955,31 @@ var GameAudio = (() => {
|
|
|
1622
1955
|
}
|
|
1623
1956
|
return decision.id;
|
|
1624
1957
|
};
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
if (
|
|
1958
|
+
const picker = createSetPicker();
|
|
1959
|
+
const resolve = (id, random, chooser = picker) => {
|
|
1960
|
+
if (registry.sound(id)) return { id, rate: 1 };
|
|
1961
|
+
const set = registry.set(id);
|
|
1962
|
+
return set ? chooser.pick(set, random) : { id, rate: 1 };
|
|
1963
|
+
};
|
|
1964
|
+
const playWith = (asked, playOptions, streamRandom, store) => {
|
|
1965
|
+
const seeded = playOptions.seed === void 0 ? null : createRng(playOptions.seed);
|
|
1966
|
+
const { id, rate } = resolve(asked, seeded ?? streamRandom);
|
|
1967
|
+
const random = seeded ?? streamRandom;
|
|
1968
|
+
const recipe2 = registry.sound(id);
|
|
1969
|
+
if (!recipe2 || busSettings.get(recipe2.bus)?.muted) return null;
|
|
1628
1970
|
const distance = playOptions.distance ?? (playOptions.position ? listenerDistance(listener, playOptions.position, options.distance) : 0);
|
|
1629
|
-
const level = falloffGain(
|
|
1630
|
-
const silent = isSilent(
|
|
1631
|
-
if (silent && !
|
|
1971
|
+
const level = falloffGain(recipe2.falloff, playOptions.volume ?? 1, distance);
|
|
1972
|
+
const silent = isSilent(recipe2.falloff, level);
|
|
1973
|
+
if (silent && !recipe2.keepSilent) return null;
|
|
1632
1974
|
const audio = ready();
|
|
1633
1975
|
if (!audio) return null;
|
|
1634
1976
|
unlockContext(audio);
|
|
1635
1977
|
advance();
|
|
1636
|
-
const voiceId = silent ? void 0 : admit(
|
|
1978
|
+
const voiceId = silent ? void 0 : admit(recipe2, level, distance);
|
|
1637
1979
|
if (voiceId === null) return null;
|
|
1638
|
-
if (!silent && usesNoise(
|
|
1639
|
-
const plan = planSound(
|
|
1640
|
-
return plan ? start(plan, store, voiceId, playOptions.delay) : null;
|
|
1980
|
+
if (!silent && usesNoise(recipe2)) prepareNoise(store, audio, mix.noise.seconds);
|
|
1981
|
+
const plan = planSound(recipe2, inputFor(recipe2, playOptions, random));
|
|
1982
|
+
return plan ? start(retunePlan(plan, rate), store, voiceId, playOptions.delay) : null;
|
|
1641
1983
|
};
|
|
1642
1984
|
const randomFor = (playOptions) => {
|
|
1643
1985
|
if (playOptions.channel === void 0) return engineRandom;
|
|
@@ -1647,9 +1989,11 @@ var GameAudio = (() => {
|
|
|
1647
1989
|
named.set(playOptions.channel, stream);
|
|
1648
1990
|
return stream;
|
|
1649
1991
|
};
|
|
1650
|
-
const planWith = (
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1992
|
+
const planWith = (asked, playOptions, random) => {
|
|
1993
|
+
const { id, rate } = resolve(asked, random, createSetPicker());
|
|
1994
|
+
const recipe2 = registry.sound(id);
|
|
1995
|
+
const plan = recipe2 ? planSound(recipe2, inputFor(recipe2, playOptions, random)) : null;
|
|
1996
|
+
return plan ? retunePlan(plan, rate) : null;
|
|
1653
1997
|
};
|
|
1654
1998
|
const loopHost = {
|
|
1655
1999
|
ready,
|
|
@@ -1668,8 +2012,8 @@ var GameAudio = (() => {
|
|
|
1668
2012
|
const audio = ready();
|
|
1669
2013
|
if (!audio) return null;
|
|
1670
2014
|
advance();
|
|
1671
|
-
const
|
|
1672
|
-
const voiceId = plan.silent ? void 0 : admit(
|
|
2015
|
+
const recipe2 = { id: plan.id, bus: plan.bus, layers: [], sources: [], priority: plan.priority, ...plan.group ? { group: plan.group } : {} };
|
|
2016
|
+
const voiceId = plan.silent ? void 0 : admit(recipe2, plan.level, plan.distance);
|
|
1673
2017
|
if (voiceId === null) return null;
|
|
1674
2018
|
if (planUsesNoise(plan)) prepareNoise(shared, audio, mix.noise.seconds);
|
|
1675
2019
|
return start(plan, shared, voiceId, playOptions.delay);
|
|
@@ -1731,6 +2075,7 @@ var GameAudio = (() => {
|
|
|
1731
2075
|
for (const sample of meter.data) sum += sample * sample;
|
|
1732
2076
|
return Math.sqrt(sum / meter.data.length);
|
|
1733
2077
|
},
|
|
2078
|
+
deferredQueue: () => startQueue?.deferredQueue() ?? null,
|
|
1734
2079
|
stats() {
|
|
1735
2080
|
advance();
|
|
1736
2081
|
const voiceStats = voices.stats();
|
|
@@ -1768,7 +2113,8 @@ var GameAudio = (() => {
|
|
|
1768
2113
|
...request.position ? { position: request.position } : {},
|
|
1769
2114
|
...request.volume === void 0 ? {} : { volume: request.volume },
|
|
1770
2115
|
...request.params ? { params: request.params } : {},
|
|
1771
|
-
...request.seed === void 0 ? {} : { seed: request.seed }
|
|
2116
|
+
...request.seed === void 0 ? {} : { seed: request.seed },
|
|
2117
|
+
...request.delay === void 0 ? {} : { delay: request.delay }
|
|
1772
2118
|
});
|
|
1773
2119
|
if (!handle) return null;
|
|
1774
2120
|
playing.set(handle.id, handle);
|