@adatechnology/game-test-room 0.7.0 → 0.8.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/index.d.ts +13 -1
- package/dist/index.global.js +1788 -102
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/dist/index.global.js
CHANGED
|
@@ -341,6 +341,37 @@ var GameTestRoom = (() => {
|
|
|
341
341
|
}
|
|
342
342
|
return { ok: problems.length === 0, problems };
|
|
343
343
|
}
|
|
344
|
+
var DEFAULT_ENVIRONMENT = {
|
|
345
|
+
rain: 0,
|
|
346
|
+
windX: 0,
|
|
347
|
+
windZ: 0,
|
|
348
|
+
windSpeed: 0,
|
|
349
|
+
gust: 0,
|
|
350
|
+
fogFar: 220,
|
|
351
|
+
visibility: 220,
|
|
352
|
+
nightFactor: 0,
|
|
353
|
+
temperatureC: 20,
|
|
354
|
+
wetness: 0
|
|
355
|
+
};
|
|
356
|
+
function createEnvironmentBus(initial = {}) {
|
|
357
|
+
let state = { ...DEFAULT_ENVIRONMENT, ...initial };
|
|
358
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
359
|
+
return {
|
|
360
|
+
get: () => state,
|
|
361
|
+
set(patch) {
|
|
362
|
+
const changed = Object.keys(patch).filter((key) => patch[key] !== void 0 && patch[key] !== state[key]);
|
|
363
|
+
if (changed.length === 0) return;
|
|
364
|
+
const next = { ...state };
|
|
365
|
+
for (const key of changed) next[key] = patch[key];
|
|
366
|
+
state = next;
|
|
367
|
+
for (const listener of [...listeners]) listener(state, changed);
|
|
368
|
+
},
|
|
369
|
+
on(listener) {
|
|
370
|
+
listeners.add(listener);
|
|
371
|
+
return () => void listeners.delete(listener);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
344
375
|
|
|
345
376
|
// ../game-destruction/dist/index.js
|
|
346
377
|
var FIRE_PRIME_C = 300;
|
|
@@ -836,9 +867,9 @@ var GameTestRoom = (() => {
|
|
|
836
867
|
const step = count > 1 && room / (count - 1) < spacing ? room / (count - 1) : spacing;
|
|
837
868
|
return Array.from({ length: count }, (_, i) => (i - (count - 1) / 2) * step);
|
|
838
869
|
};
|
|
839
|
-
var bar = (axis2, along,
|
|
870
|
+
var bar = (axis2, along, fixed2) => {
|
|
840
871
|
const a = [0, 0, 0], b = [0, 0, 0];
|
|
841
|
-
for (const [index, value] of
|
|
872
|
+
for (const [index, value] of fixed2) {
|
|
842
873
|
a[index] = value;
|
|
843
874
|
b[index] = value;
|
|
844
875
|
}
|
|
@@ -3147,15 +3178,15 @@ var GameTestRoom = (() => {
|
|
|
3147
3178
|
return best;
|
|
3148
3179
|
};
|
|
3149
3180
|
const detonatePrimed = (until) => {
|
|
3150
|
-
const
|
|
3181
|
+
const end3 = time;
|
|
3151
3182
|
for (let next = nextPrimed(until); next; next = nextPrimed(until)) {
|
|
3152
3183
|
primed.delete(next.p);
|
|
3153
|
-
time = Math.max(0, Math.min(
|
|
3184
|
+
time = Math.max(0, Math.min(end3, next.p.rt.chainDue));
|
|
3154
3185
|
next.p.rt.chainDue = void 0;
|
|
3155
3186
|
failPart(next.o, next.p, null, null, 0, 40);
|
|
3156
3187
|
drainBlasts();
|
|
3157
3188
|
}
|
|
3158
|
-
time =
|
|
3189
|
+
time = end3;
|
|
3159
3190
|
};
|
|
3160
3191
|
const releaseFront = () => {
|
|
3161
3192
|
if (frontQueue.length === 0) return;
|
|
@@ -3767,6 +3798,1447 @@ var GameTestRoom = (() => {
|
|
|
3767
3798
|
};
|
|
3768
3799
|
var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
|
|
3769
3800
|
|
|
3801
|
+
// ../game-audio/dist/index.js
|
|
3802
|
+
var FNV_OFFSET = 2166136261;
|
|
3803
|
+
var FNV_PRIME = 16777619;
|
|
3804
|
+
var MIX = 2654435761;
|
|
3805
|
+
function createRng2(seed) {
|
|
3806
|
+
let state = seed >>> 0;
|
|
3807
|
+
return () => {
|
|
3808
|
+
state = state + 1831565813 >>> 0;
|
|
3809
|
+
let mixed = Math.imul(state ^ state >>> 15, state | 1);
|
|
3810
|
+
mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, mixed | 61);
|
|
3811
|
+
return ((mixed ^ mixed >>> 14) >>> 0) / 4294967296;
|
|
3812
|
+
};
|
|
3813
|
+
}
|
|
3814
|
+
function streamSeed(seed, name) {
|
|
3815
|
+
const label = String(name);
|
|
3816
|
+
let hash = FNV_OFFSET;
|
|
3817
|
+
for (let index = 0; index < label.length; index++) hash = Math.imul(hash ^ label.charCodeAt(index), FNV_PRIME) >>> 0;
|
|
3818
|
+
return Math.imul(seed >>> 0 ^ hash, MIX) >>> 0;
|
|
3819
|
+
}
|
|
3820
|
+
function streamFor(seed, name) {
|
|
3821
|
+
return createRng2(streamSeed(seed, name));
|
|
3822
|
+
}
|
|
3823
|
+
var DISTANCE_CURVES = {
|
|
3824
|
+
/** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
|
|
3825
|
+
game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
|
|
3826
|
+
/** The game's `grito`: `1 / (1 + d² × 0.006)`, silent below 0.02 (jogo L62129–62130). */
|
|
3827
|
+
game_scream: { model: "inverse_square", k: 6e-3, minGain: 0.02 },
|
|
3828
|
+
/** The game's `voz`: `1 / (1 + d² × 0.01)`, silent below 0.02 (jogo L62146–62147). */
|
|
3829
|
+
game_voice: { model: "inverse_square", k: 0.01, minGain: 0.02 },
|
|
3830
|
+
/** The game's `boom`: `1.4 / (1 + d² × 0.002)`, never silent (jogo L68280). */
|
|
3831
|
+
game_boom: { model: "inverse_square", k: 2e-3, scale: 1.4 },
|
|
3832
|
+
/** The art scene's `outAt`: `vol / (1 + d² × 0.008)` (cena L1320). */
|
|
3833
|
+
art_scene: { model: "inverse_square", k: 8e-3 },
|
|
3834
|
+
/** The game's animal calls: `max(0, 1 − d / 70) × 0.25`, silent at or below 0.005 (jogo L66627–66628). */
|
|
3835
|
+
animals: { model: "linear", range: 70, scale: 0.25, minGain: 5e-3 }
|
|
3836
|
+
};
|
|
3837
|
+
function falloffGain(falloff, volume, distance4) {
|
|
3838
|
+
if (!falloff || falloff.model === "none") return volume;
|
|
3839
|
+
if (falloff.model === "linear") {
|
|
3840
|
+
const level2 = Math.max(0, 1 - distance4 / falloff.range) * volume;
|
|
3841
|
+
return falloff.scale === void 0 ? level2 : level2 * falloff.scale;
|
|
3842
|
+
}
|
|
3843
|
+
const level = volume / (1 + distance4 * distance4 * falloff.k);
|
|
3844
|
+
return falloff.scale === void 0 ? level : falloff.scale * level;
|
|
3845
|
+
}
|
|
3846
|
+
function isSilent(falloff, gain) {
|
|
3847
|
+
if (!falloff || falloff.model === "none" || falloff.minGain === void 0) return false;
|
|
3848
|
+
return falloff.model === "linear" ? gain <= falloff.minGain : gain < falloff.minGain;
|
|
3849
|
+
}
|
|
3850
|
+
function cutoffDivisor(falloff, distance4) {
|
|
3851
|
+
if (!falloff || falloff.model !== "inverse_square" || falloff.cutoffK === void 0) return 1;
|
|
3852
|
+
return 1 + distance4 * falloff.cutoffK;
|
|
3853
|
+
}
|
|
3854
|
+
var DEFAULT_LISTENER = { position: [0, 0, 0], yaw: 0 };
|
|
3855
|
+
var PAN_LIMIT = 0.85;
|
|
3856
|
+
var PAN_MIN_DISTANCE = 2;
|
|
3857
|
+
function listenerDistance(listener, position, mode = "xz") {
|
|
3858
|
+
const dx = position[0] - listener.position[0];
|
|
3859
|
+
const dz = position[2] - listener.position[2];
|
|
3860
|
+
if (mode === "xz") return Math.hypot(dx, dz);
|
|
3861
|
+
return Math.hypot(dx, position[1] - listener.position[1], dz);
|
|
3862
|
+
}
|
|
3863
|
+
function panFor(listener, position) {
|
|
3864
|
+
const rightX = Math.cos(listener.yaw);
|
|
3865
|
+
const rightZ = -Math.sin(listener.yaw);
|
|
3866
|
+
const dx = position[0] - listener.position[0];
|
|
3867
|
+
const dz = position[2] - listener.position[2];
|
|
3868
|
+
const distance4 = Math.hypot(dx, dz) || 1;
|
|
3869
|
+
const pan = (dx * rightX + dz * rightZ) / Math.max(distance4, PAN_MIN_DISTANCE);
|
|
3870
|
+
return pan < -PAN_LIMIT ? -PAN_LIMIT : pan > PAN_LIMIT ? PAN_LIMIT : pan;
|
|
3871
|
+
}
|
|
3872
|
+
var voice_groups_default = {
|
|
3873
|
+
undead_voice: {
|
|
3874
|
+
maxConcurrent: 5,
|
|
3875
|
+
cooldown: 0.25,
|
|
3876
|
+
maxDistance: 40,
|
|
3877
|
+
sources: [
|
|
3878
|
+
"cena:L1342 `AU.groans > 4` refuses, so five play at once",
|
|
3879
|
+
"jogo:L66986 one groan every 0.25 s (`groanBudget`), only nearer than 40 m",
|
|
3880
|
+
"jogo:L70472 the budget counts down by the frame time"
|
|
3881
|
+
]
|
|
3882
|
+
}
|
|
3883
|
+
};
|
|
3884
|
+
var VOICE_GROUPS = voice_groups_default;
|
|
3885
|
+
var BUS_PRIORITY = { ui: 5, weapons: 4, voices: 3, world: 2, weather: 1, ambience: 1 };
|
|
3886
|
+
var DEFAULT_MAX_VOICES = 32;
|
|
3887
|
+
function busPriority(bus) {
|
|
3888
|
+
return BUS_PRIORITY[bus];
|
|
3889
|
+
}
|
|
3890
|
+
function createVoiceManager(options = {}) {
|
|
3891
|
+
const groups = new Map(Object.entries(options.groups ?? VOICE_GROUPS));
|
|
3892
|
+
const maxVoices = options.maxVoices ?? DEFAULT_MAX_VOICES;
|
|
3893
|
+
const budgets = /* @__PURE__ */ new Map();
|
|
3894
|
+
let voices = [];
|
|
3895
|
+
let nextId = 1;
|
|
3896
|
+
let peak = 0;
|
|
3897
|
+
let dropped = 0;
|
|
3898
|
+
const refuse = (reason) => {
|
|
3899
|
+
dropped += 1;
|
|
3900
|
+
return { accepted: false, reason };
|
|
3901
|
+
};
|
|
3902
|
+
const groupCheck = (request, evicted) => {
|
|
3903
|
+
const group = request.group === void 0 ? void 0 : groups.get(request.group);
|
|
3904
|
+
if (!group) return void 0;
|
|
3905
|
+
if (group.maxDistance !== void 0 && !((request.distance ?? 0) < group.maxDistance)) return "distance";
|
|
3906
|
+
if (group.cooldown !== void 0 && (budgets.get(request.group) ?? 0) > 0) return "cooldown";
|
|
3907
|
+
const members = voices.filter((voice) => voice.group === request.group);
|
|
3908
|
+
if (group.maxConcurrent === void 0 || members.length < group.maxConcurrent) return void 0;
|
|
3909
|
+
if (!group.steal) return "group_full";
|
|
3910
|
+
evicted.push(members[0].id);
|
|
3911
|
+
return void 0;
|
|
3912
|
+
};
|
|
3913
|
+
return {
|
|
3914
|
+
request(request) {
|
|
3915
|
+
const evicted = [];
|
|
3916
|
+
const refusal = groupCheck(request, evicted);
|
|
3917
|
+
if (refusal) return refuse(refusal);
|
|
3918
|
+
const score = request.priority * request.gain;
|
|
3919
|
+
const remaining = voices.filter((voice) => !evicted.includes(voice.id));
|
|
3920
|
+
if (remaining.length >= maxVoices) {
|
|
3921
|
+
const lowest = remaining.reduce((low, voice) => voice.score < low.score ? voice : low);
|
|
3922
|
+
if (score <= lowest.score) return refuse("global_full");
|
|
3923
|
+
evicted.push(lowest.id);
|
|
3924
|
+
dropped += 1;
|
|
3925
|
+
}
|
|
3926
|
+
const group = request.group === void 0 ? void 0 : groups.get(request.group);
|
|
3927
|
+
if (group?.cooldown !== void 0) budgets.set(request.group, group.cooldown);
|
|
3928
|
+
const id = nextId++;
|
|
3929
|
+
voices = [...voices.filter((voice) => !evicted.includes(voice.id)), { id, ...request.group === void 0 ? {} : { group: request.group }, score, remaining: request.length }];
|
|
3930
|
+
peak = Math.max(peak, voices.length);
|
|
3931
|
+
return { accepted: true, id, evicted };
|
|
3932
|
+
},
|
|
3933
|
+
release(id) {
|
|
3934
|
+
voices = voices.filter((voice) => voice.id !== id);
|
|
3935
|
+
},
|
|
3936
|
+
setLength(id, seconds) {
|
|
3937
|
+
const voice = voices.find((entry) => entry.id === id);
|
|
3938
|
+
if (voice) voice.remaining = seconds;
|
|
3939
|
+
},
|
|
3940
|
+
advance(dt) {
|
|
3941
|
+
for (const [group, budget] of budgets) budgets.set(group, budget - dt);
|
|
3942
|
+
const ended = [];
|
|
3943
|
+
for (const voice of voices) {
|
|
3944
|
+
voice.remaining -= dt;
|
|
3945
|
+
if (voice.remaining <= 0) ended.push(voice.id);
|
|
3946
|
+
}
|
|
3947
|
+
if (ended.length > 0) voices = voices.filter((voice) => voice.remaining > 0);
|
|
3948
|
+
return ended;
|
|
3949
|
+
},
|
|
3950
|
+
setGroup(id, group) {
|
|
3951
|
+
if (group) groups.set(id, group);
|
|
3952
|
+
else groups.delete(id);
|
|
3953
|
+
},
|
|
3954
|
+
stats: () => ({ active: voices.length, peak, dropped, maxVoices })
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3957
|
+
var DEFAULT_TAIL = 0.05;
|
|
3958
|
+
var DEFAULT_NOISE_SECONDS = 1.5;
|
|
3959
|
+
var OCCLUSION_GAIN = 0.5;
|
|
3960
|
+
var OCCLUSION_CUTOFF = 0.6;
|
|
3961
|
+
function isParamRef(range2) {
|
|
3962
|
+
return typeof range2 === "object" && !Array.isArray(range2);
|
|
3963
|
+
}
|
|
3964
|
+
function draw(range2, scope) {
|
|
3965
|
+
if (typeof range2 === "number") return range2;
|
|
3966
|
+
if (isParamRef(range2)) {
|
|
3967
|
+
const value = scope.params[range2.param];
|
|
3968
|
+
if (value === void 0) throw new Error(`game-audio: the recipe reads the param "${range2.param}", which was not given`);
|
|
3969
|
+
return value * (range2.scale ?? 1) + (range2.add ?? 0);
|
|
3970
|
+
}
|
|
3971
|
+
const [min2, max2] = range2;
|
|
3972
|
+
return min2 + scope.random() * (max2 - min2);
|
|
3973
|
+
}
|
|
3974
|
+
function drawTime(time, scope) {
|
|
3975
|
+
if (time === "duration" || typeof time === "string" && time.endsWith("d")) return time;
|
|
3976
|
+
return draw(time, scope);
|
|
3977
|
+
}
|
|
3978
|
+
function timeOf(time, duration) {
|
|
3979
|
+
if (typeof time === "number") return time;
|
|
3980
|
+
if (time === "duration") return duration;
|
|
3981
|
+
return Number(time.slice(0, -1)) * duration;
|
|
3982
|
+
}
|
|
3983
|
+
function drawAutomation(automation, scope) {
|
|
3984
|
+
const pending = {};
|
|
3985
|
+
for (const key of Object.keys(automation)) {
|
|
3986
|
+
if (key === "set" && "set" in automation) pending.value = draw(automation.set, scope);
|
|
3987
|
+
else if (key === "from" && "from" in automation) pending.from = draw(automation.from, scope);
|
|
3988
|
+
else if (key === "to" && automation.to !== void 0) pending.to = automation.to === "from" ? pending.from ?? pending.value ?? 0 : draw(automation.to, scope);
|
|
3989
|
+
else if (key === "curve") pending.curve = automation.curve;
|
|
3990
|
+
else if (key === "over" && automation.over !== void 0) pending.over = drawTime(automation.over, scope);
|
|
3991
|
+
}
|
|
3992
|
+
return pending;
|
|
3993
|
+
}
|
|
3994
|
+
function finishAutomation(pending, duration) {
|
|
3995
|
+
if (pending.from !== void 0 && pending.to === void 0) return { from: pending.from };
|
|
3996
|
+
if (pending.from !== void 0) return { from: pending.from, to: pending.to, curve: pending.curve ?? "linear", at: timeOf(pending.over ?? "duration", duration) };
|
|
3997
|
+
if (pending.to === void 0 || pending.over === void 0) return { value: pending.value };
|
|
3998
|
+
return { value: pending.value, to: pending.to, curve: pending.curve ?? "linear", at: timeOf(pending.over, duration) };
|
|
3999
|
+
}
|
|
4000
|
+
var startFrequency = (automation) => automation.from ?? automation.value ?? 0;
|
|
4001
|
+
function drawFilter(spec, scope) {
|
|
4002
|
+
const pending = { spec, frequency: {} };
|
|
4003
|
+
for (const key of Object.keys(spec)) {
|
|
4004
|
+
if (key === "frequency") pending.frequency = drawAutomation(spec.frequency, scope);
|
|
4005
|
+
else if (key === "q" && spec.q !== void 0) pending.q = draw(spec.q, scope);
|
|
4006
|
+
else if (key === "gain" && spec.gain !== void 0) pending.gain = draw(spec.gain, scope);
|
|
4007
|
+
else if (key === "level" && spec.level !== void 0) pending.level = draw(spec.level, scope);
|
|
4008
|
+
}
|
|
4009
|
+
return pending;
|
|
4010
|
+
}
|
|
4011
|
+
function drawModulator(modulator, scope) {
|
|
4012
|
+
const pending = { modulator, rate: 0 };
|
|
4013
|
+
for (const key of Object.keys(modulator)) {
|
|
4014
|
+
if (key === "rate") pending.rate = draw(modulator.rate, scope);
|
|
4015
|
+
else if (key === "depth" && !(typeof modulator.depth === "object" && "scaleOf" in modulator.depth)) pending.depth = draw(modulator.depth, scope);
|
|
4016
|
+
}
|
|
4017
|
+
return pending;
|
|
4018
|
+
}
|
|
4019
|
+
function drawVoice(source, scope) {
|
|
4020
|
+
const pending = { frequency: {}, vibratoRate: 0, formants: [], breath: { frequency: 0, q: source.breath.q, gain: 0 } };
|
|
4021
|
+
for (const key of Object.keys(source)) {
|
|
4022
|
+
if (key === "frequency") pending.frequency = drawAutomation(source.frequency, scope);
|
|
4023
|
+
else if (key === "vibrato") pending.vibratoRate = draw(source.vibrato.rate, scope);
|
|
4024
|
+
else if (key === "formants") pending.formants = source.formants.map((formant) => ({ frequency: draw(formant.frequency, scope), q: formant.q }));
|
|
4025
|
+
else if (key === "breath") pending.breath = { frequency: draw(source.breath.frequency, scope), q: source.breath.q, gain: draw(source.breath.gain, scope) };
|
|
4026
|
+
}
|
|
4027
|
+
return pending;
|
|
4028
|
+
}
|
|
4029
|
+
function drawLayer(layer2, scope) {
|
|
4030
|
+
const pending = { at: 0, gain: 1, filters: [], modulators: [] };
|
|
4031
|
+
for (const key of Object.keys(layer2)) {
|
|
4032
|
+
if (key === "at" && layer2.at !== void 0) pending.at = draw(layer2.at, scope);
|
|
4033
|
+
else if (key === "duration" && layer2.duration !== void 0) pending.duration = draw(layer2.duration, scope);
|
|
4034
|
+
else if (key === "gain" && layer2.gain !== void 0) pending.gain = draw(layer2.gain, scope);
|
|
4035
|
+
else if (key === "source" && layer2.source.kind === "osc") pending.automation = drawAutomation(layer2.source.frequency, scope);
|
|
4036
|
+
else if (key === "source" && layer2.source.kind === "formant_voice") pending.voice = drawVoice(layer2.source, scope);
|
|
4037
|
+
else if (key === "filters" && layer2.filters) pending.filters = layer2.filters.bank.map((spec) => drawFilter(spec, scope));
|
|
4038
|
+
else if (key === "modulators" && layer2.modulators) pending.modulators = layer2.modulators.map((modulator) => drawModulator(modulator, scope));
|
|
4039
|
+
}
|
|
4040
|
+
return pending;
|
|
4041
|
+
}
|
|
4042
|
+
function drawOffset(source, duration, scope, noiseSeconds) {
|
|
4043
|
+
if (source.kind === "formant_voice") return scope.random();
|
|
4044
|
+
if (source.kind !== "noise" || source.offset === void 0) return 0;
|
|
4045
|
+
if (typeof source.offset === "number") return source.offset;
|
|
4046
|
+
if (source.offset === "fit") return scope.random() * Math.max(0, noiseSeconds - duration);
|
|
4047
|
+
return scope.random() * (source.offsetScale ?? 1);
|
|
4048
|
+
}
|
|
4049
|
+
function finishSource(layer2, pending, duration, offset) {
|
|
4050
|
+
const source = layer2.source;
|
|
4051
|
+
if (source.kind === "noise") return { kind: "noise", offset, loop: source.loop === true };
|
|
4052
|
+
if (source.kind === "osc") return { kind: "osc", wave: source.wave, frequency: finishAutomation(pending.automation ?? {}, duration) };
|
|
4053
|
+
const voice = pending.voice;
|
|
4054
|
+
return {
|
|
4055
|
+
kind: "formant_voice",
|
|
4056
|
+
wave: source.wave,
|
|
4057
|
+
frequency: finishAutomation(voice.frequency, duration),
|
|
4058
|
+
vibrato: { rate: voice.vibratoRate, depth: startFrequency(voice.frequency) * source.vibrato.depth },
|
|
4059
|
+
formants: voice.formants,
|
|
4060
|
+
breath: { ...voice.breath, offset },
|
|
4061
|
+
mix: source.mix
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
function finishModulator(pending, sourceFrequency) {
|
|
4065
|
+
const { modulator } = pending;
|
|
4066
|
+
const depth = typeof modulator.depth === "object" && "scaleOf" in modulator.depth ? sourceFrequency * modulator.depth.factor : pending.depth ?? 0;
|
|
4067
|
+
return { target: modulator.target, ...modulator.wave ? { wave: modulator.wave } : {}, rate: pending.rate, depth, ...modulator.base === void 0 ? {} : { base: modulator.base } };
|
|
4068
|
+
}
|
|
4069
|
+
function finishEnvelope(envelope2, duration, level) {
|
|
4070
|
+
return envelope2.map(([time, value, curve, fixed2]) => ({ time: timeOf(time, duration), value: fixed2 === "fixed" ? value : value * level, curve }));
|
|
4071
|
+
}
|
|
4072
|
+
function planLayer(layer2, scope, defaults, levelInEnvelope, soundLevel) {
|
|
4073
|
+
const pending = drawLayer(layer2, scope);
|
|
4074
|
+
const duration = pending.duration ?? defaults.duration;
|
|
4075
|
+
if (duration === void 0) throw new Error("game-audio: a layer needs a duration (its own or the recipe\u2019s)");
|
|
4076
|
+
const offset = drawOffset(layer2.source, duration, scope, defaults.noiseSeconds);
|
|
4077
|
+
const level = pending.gain * soundLevel;
|
|
4078
|
+
const sourceFrequency = layer2.source.kind === "noise" ? 0 : startFrequency(pending.automation ?? pending.voice?.frequency ?? {});
|
|
4079
|
+
const planned = {
|
|
4080
|
+
at: pending.at,
|
|
4081
|
+
duration,
|
|
4082
|
+
tail: layer2.tail ?? defaults.tail ?? DEFAULT_TAIL,
|
|
4083
|
+
level,
|
|
4084
|
+
...layer2.wet === void 0 ? {} : { wet: layer2.wet },
|
|
4085
|
+
source: finishSource(layer2, pending, duration, offset),
|
|
4086
|
+
...layer2.filters ? {
|
|
4087
|
+
filters: {
|
|
4088
|
+
mode: layer2.filters.mode,
|
|
4089
|
+
bank: pending.filters.map(({ spec, frequency, q, gain, level: filterLevel }) => ({
|
|
4090
|
+
type: spec.type,
|
|
4091
|
+
frequency: finishAutomation(frequency, duration),
|
|
4092
|
+
...q === void 0 ? {} : { q },
|
|
4093
|
+
...gain === void 0 ? {} : { gain },
|
|
4094
|
+
...filterLevel === void 0 ? {} : { level: filterLevel }
|
|
4095
|
+
}))
|
|
4096
|
+
}
|
|
4097
|
+
} : {},
|
|
4098
|
+
modulators: pending.modulators.map((modulator) => finishModulator(modulator, sourceFrequency)),
|
|
4099
|
+
envelope: finishEnvelope(layer2.envelope, duration, levelInEnvelope ? level : 1)
|
|
4100
|
+
};
|
|
4101
|
+
if (!layer2.repeat) return [planned];
|
|
4102
|
+
return Array.from({ length: layer2.repeat.count }, (_, index) => ({ ...planned, at: planned.at + index * (layer2.repeat?.spacing ?? 0) }));
|
|
4103
|
+
}
|
|
4104
|
+
function scaleAutomation(automation, divisor) {
|
|
4105
|
+
const scale2 = (value) => value === void 0 ? void 0 : value / divisor;
|
|
4106
|
+
const scaled2 = { ...automation };
|
|
4107
|
+
for (const key of ["value", "from", "to"]) if (automation[key] !== void 0) scaled2[key] = scale2(automation[key]);
|
|
4108
|
+
return scaled2;
|
|
4109
|
+
}
|
|
4110
|
+
function muffle(layer2, divisor) {
|
|
4111
|
+
if (divisor === 1 || !layer2.filters) return layer2;
|
|
4112
|
+
const bank = layer2.filters.bank.map((filter) => filter.type === "lowpass" ? { ...filter, frequency: scaleAutomation(filter.frequency, divisor) } : filter);
|
|
4113
|
+
return { ...layer2, filters: { ...layer2.filters, bank } };
|
|
4114
|
+
}
|
|
4115
|
+
var layerEnd = (layer2) => layer2.at + layer2.duration + layer2.tail;
|
|
4116
|
+
function planSound(recipe, input = {}) {
|
|
4117
|
+
const listener = input.listener ?? DEFAULT_LISTENER;
|
|
4118
|
+
const distance4 = input.distance ?? (input.position ? listenerDistance(listener, input.position, input.distanceMode) : 0);
|
|
4119
|
+
const occlusion = Math.min(1, Math.max(0, input.occlusion ?? 0));
|
|
4120
|
+
const order = recipe.order ?? "layered";
|
|
4121
|
+
const falloffLevel = falloffGain(recipe.falloff, input.volume ?? 1, distance4);
|
|
4122
|
+
const level = occlusion > 0 ? falloffLevel * (1 - OCCLUSION_GAIN * occlusion) : falloffLevel;
|
|
4123
|
+
const silent = isSilent(recipe.falloff, falloffLevel);
|
|
4124
|
+
const base = {
|
|
4125
|
+
id: recipe.id,
|
|
4126
|
+
bus: recipe.bus,
|
|
4127
|
+
order,
|
|
4128
|
+
...recipe.group === void 0 ? {} : { group: recipe.group },
|
|
4129
|
+
priority: recipe.priority ?? busPriority(recipe.bus),
|
|
4130
|
+
level,
|
|
4131
|
+
distance: distance4,
|
|
4132
|
+
...recipe.pan ? { pan: input.position ? panFor(listener, input.position) : 0 } : {}
|
|
4133
|
+
};
|
|
4134
|
+
if (silent) return recipe.keepSilent ? { ...base, silent: true, length: 0, layers: [] } : null;
|
|
4135
|
+
const scope = { random: input.random ?? createRng2(1), params: { ...input.params } };
|
|
4136
|
+
for (const [name, range2] of Object.entries(recipe.vars ?? {})) if (scope.params[name] === void 0) scope.params[name] = draw(range2, scope);
|
|
4137
|
+
const defaults = {
|
|
4138
|
+
...recipe.duration === void 0 ? {} : { duration: draw(recipe.duration, scope) },
|
|
4139
|
+
...recipe.tail === void 0 ? {} : { tail: recipe.tail },
|
|
4140
|
+
noiseSeconds: input.noiseSeconds ?? DEFAULT_NOISE_SECONDS
|
|
4141
|
+
};
|
|
4142
|
+
const divisor = cutoffDivisor(recipe.falloff, distance4) / (1 - OCCLUSION_CUTOFF * occlusion);
|
|
4143
|
+
const layers = recipe.layers.flatMap((layer2) => planLayer(layer2, scope, defaults, order !== "layered", level)).map((layer2) => muffle(layer2, divisor));
|
|
4144
|
+
return { ...base, silent: false, length: Math.max(0, ...layers.map(layerEnd)), layers };
|
|
4145
|
+
}
|
|
4146
|
+
var LOOP_START_ABOVE = 2e-3;
|
|
4147
|
+
var LOOP_SMOOTHING = { volume: 0.15, frequency: 0.08 };
|
|
4148
|
+
function createLoopState(recipe, open = false) {
|
|
4149
|
+
const startAbove = recipe.startAbove ?? LOOP_START_ABOVE;
|
|
4150
|
+
let created = open;
|
|
4151
|
+
let volume = recipe.volume ?? 0;
|
|
4152
|
+
return {
|
|
4153
|
+
set(state) {
|
|
4154
|
+
volume = state.volume ?? volume;
|
|
4155
|
+
const create = !created;
|
|
4156
|
+
if (create && !(volume >= startAbove)) return null;
|
|
4157
|
+
created = true;
|
|
4158
|
+
const frequency = recipe.source.kind === "osc" && state.frequency ? state.frequency : void 0;
|
|
4159
|
+
return { create, volume, ...frequency === void 0 ? {} : { frequency } };
|
|
4160
|
+
},
|
|
4161
|
+
get created() {
|
|
4162
|
+
return created;
|
|
4163
|
+
},
|
|
4164
|
+
reset() {
|
|
4165
|
+
created = false;
|
|
4166
|
+
}
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
var mix_profiles_default = [
|
|
4170
|
+
{
|
|
4171
|
+
id: "game",
|
|
4172
|
+
master: 0.5,
|
|
4173
|
+
noise: { seconds: 1.5, create: "lazy" },
|
|
4174
|
+
sources: ["jogo:L62077-62081 master 0.5 to the output", "jogo:L62082-62088 noise 1.5 s made by the first sound that needs it"]
|
|
4175
|
+
},
|
|
4176
|
+
{
|
|
4177
|
+
id: "art_scene",
|
|
4178
|
+
master: 0.85,
|
|
4179
|
+
compressor: { threshold: -12, ratio: 5 },
|
|
4180
|
+
reverb: { seconds: 2.2, decay: 3.4, wet: 0.4 },
|
|
4181
|
+
noise: { seconds: 2, create: "eager" },
|
|
4182
|
+
sources: [
|
|
4183
|
+
"cena:L1308 compressor -12 dB, ratio 5",
|
|
4184
|
+
"cena:L1309 master 0.85",
|
|
4185
|
+
"cena:L1310 convolver, impulse 2.2 s of (1 - i/len)^3.4",
|
|
4186
|
+
"cena:L1311 wet 0.4",
|
|
4187
|
+
"cena:L1312 noise 2 s made with the chain"
|
|
4188
|
+
]
|
|
4189
|
+
}
|
|
4190
|
+
];
|
|
4191
|
+
var MIX_PROFILES = Object.fromEntries(
|
|
4192
|
+
mix_profiles_default.map((profile) => [profile.id, profile])
|
|
4193
|
+
);
|
|
4194
|
+
var DEFAULT_MIX = "art_scene";
|
|
4195
|
+
function resolveMix(mix, profiles = MIX_PROFILES) {
|
|
4196
|
+
if (typeof mix !== "string") return mix;
|
|
4197
|
+
const profile = profiles[mix];
|
|
4198
|
+
if (!profile) throw new Error(`game-audio: unknown mix profile "${mix}" (known: ${Object.keys(profiles).join(", ")})`);
|
|
4199
|
+
return profile;
|
|
4200
|
+
}
|
|
4201
|
+
function busGain(setting) {
|
|
4202
|
+
if (!setting) return 1;
|
|
4203
|
+
return setting.muted ? 0 : setting.gain ?? 1;
|
|
4204
|
+
}
|
|
4205
|
+
function isPlainObject2(value) {
|
|
4206
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4207
|
+
}
|
|
4208
|
+
function mergeRecords(base, patch) {
|
|
4209
|
+
const merged = { ...base };
|
|
4210
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
4211
|
+
if (value === void 0) continue;
|
|
4212
|
+
const current = merged[key];
|
|
4213
|
+
merged[key] = isPlainObject2(current) && isPlainObject2(value) ? mergeRecords(current, value) : value;
|
|
4214
|
+
}
|
|
4215
|
+
return merged;
|
|
4216
|
+
}
|
|
4217
|
+
function createSoundRegistry(banks = []) {
|
|
4218
|
+
const sounds = /* @__PURE__ */ new Map();
|
|
4219
|
+
const loops = /* @__PURE__ */ new Map();
|
|
4220
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4221
|
+
const registry = {
|
|
4222
|
+
register(bank) {
|
|
4223
|
+
for (const sound of bank.sounds ?? []) {
|
|
4224
|
+
sounds.set(sound.id, sound);
|
|
4225
|
+
owners.set(sound.id, bank.id);
|
|
4226
|
+
}
|
|
4227
|
+
for (const loop of bank.loops ?? []) {
|
|
4228
|
+
loops.set(loop.id, loop);
|
|
4229
|
+
owners.set(loop.id, bank.id);
|
|
4230
|
+
}
|
|
4231
|
+
},
|
|
4232
|
+
patch(id, patch) {
|
|
4233
|
+
const sound = sounds.get(id);
|
|
4234
|
+
if (sound) sounds.set(id, mergeRecords(sound, patch));
|
|
4235
|
+
const loop = loops.get(id);
|
|
4236
|
+
if (loop) loops.set(id, mergeRecords(loop, patch));
|
|
4237
|
+
},
|
|
4238
|
+
remove(id) {
|
|
4239
|
+
sounds.delete(id);
|
|
4240
|
+
loops.delete(id);
|
|
4241
|
+
owners.delete(id);
|
|
4242
|
+
},
|
|
4243
|
+
sound: (id) => sounds.get(id),
|
|
4244
|
+
loop: (id) => loops.get(id),
|
|
4245
|
+
soundIds: () => [...sounds.keys()],
|
|
4246
|
+
loopIds: () => [...loops.keys()],
|
|
4247
|
+
bankOf: (id) => owners.get(id)
|
|
4248
|
+
};
|
|
4249
|
+
for (const bank of banks) registry.register(bank);
|
|
4250
|
+
return registry;
|
|
4251
|
+
}
|
|
4252
|
+
var game_sfx_default = {
|
|
4253
|
+
sources: ["jogo:L62089-62107"],
|
|
4254
|
+
note: "The game's sfx table P: [lowpass cutoff Hz, peak gain, seconds]. Played as a noise burst with the game's distance curve (falloff game_sfx) and a 0.05 s tail. Team names in `team`.",
|
|
4255
|
+
classes: [
|
|
4256
|
+
{ id: "pistol", team: "pistola", bus: "weapons", cutoff: 1800, gain: 0.9, seconds: 0.18 },
|
|
4257
|
+
{ id: "shotgun", team: "escopeta", bus: "weapons", cutoff: 900, gain: 1.2, seconds: 0.35 },
|
|
4258
|
+
{ id: "rifle", team: "fuzil", bus: "weapons", cutoff: 1400, gain: 1.1, seconds: 0.28 },
|
|
4259
|
+
{ id: "long_rifle", team: "rifle", bus: "weapons", cutoff: 1100, gain: 1.3, seconds: 0.45 },
|
|
4260
|
+
{ id: "impact", team: "impacto", bus: "world", cutoff: 2500, gain: 0.25, seconds: 0.05 },
|
|
4261
|
+
{ id: "flesh", team: "carne", bus: "world", cutoff: 600, gain: 0.35, seconds: 0.08 },
|
|
4262
|
+
{ id: "step", team: "passo", bus: "world", cutoff: 400, gain: 0.08, seconds: 0.05 },
|
|
4263
|
+
{ id: "glass", team: "vidro", bus: "world", cutoff: 5e3, gain: 0.5, seconds: 0.3 },
|
|
4264
|
+
{ id: "wood", team: "madeira", bus: "world", cutoff: 700, gain: 0.5, seconds: 0.2 },
|
|
4265
|
+
{ id: "reload", team: "recarga", bus: "weapons", cutoff: 3e3, gain: 0.15, seconds: 0.06 },
|
|
4266
|
+
{ id: "empty", team: "vazio", bus: "weapons", cutoff: 4e3, gain: 0.12, seconds: 0.03 },
|
|
4267
|
+
{ id: "explosion", team: "explosao", bus: "world", cutoff: 200, gain: 1.6, seconds: 1.4 }
|
|
4268
|
+
],
|
|
4269
|
+
fallback: { id: "unknown", team: "(any other name)", bus: "world", cutoff: 1e3, gain: 0.5, seconds: 0.1 }
|
|
4270
|
+
};
|
|
4271
|
+
var GAME_SFX = game_sfx_default;
|
|
4272
|
+
var GAME_SFX_CLASSES = GAME_SFX.classes;
|
|
4273
|
+
var GAME_SFX_FALLBACK = GAME_SFX.fallback;
|
|
4274
|
+
function noiseClassRecipe(row, id = `sfx_${row.id}`) {
|
|
4275
|
+
return {
|
|
4276
|
+
id,
|
|
4277
|
+
bus: row.bus,
|
|
4278
|
+
order: "burst",
|
|
4279
|
+
duration: row.seconds,
|
|
4280
|
+
tail: 0.05,
|
|
4281
|
+
layers: [
|
|
4282
|
+
{
|
|
4283
|
+
source: { kind: "noise", offset: "random" },
|
|
4284
|
+
filters: { mode: "serial", bank: [{ type: "lowpass", frequency: { set: row.cutoff } }] },
|
|
4285
|
+
envelope: [
|
|
4286
|
+
[0, row.gain, "set"],
|
|
4287
|
+
["1d", 1e-3, "exp", "fixed"]
|
|
4288
|
+
]
|
|
4289
|
+
}
|
|
4290
|
+
],
|
|
4291
|
+
falloff: DISTANCE_CURVES.game_sfx,
|
|
4292
|
+
sources: [...GAME_SFX.sources.map((source) => `${source} ${row.team}`)]
|
|
4293
|
+
};
|
|
4294
|
+
}
|
|
4295
|
+
var GAME_SFX_BANK = { id: "game_sfx", sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) };
|
|
4296
|
+
function fillNoise(random, length3) {
|
|
4297
|
+
const samples = new Float32Array(length3);
|
|
4298
|
+
for (let index = 0; index < length3; index++) samples[index] = random() * 2 - 1;
|
|
4299
|
+
return samples;
|
|
4300
|
+
}
|
|
4301
|
+
function fillImpulse(random, options) {
|
|
4302
|
+
const size = Math.floor(options.length);
|
|
4303
|
+
return Array.from({ length: options.channels }, () => {
|
|
4304
|
+
const samples = new Float32Array(size);
|
|
4305
|
+
for (let index = 0; index < options.length; index++) {
|
|
4306
|
+
const value = (random() * 2 - 1) * Math.exp(options.decay * Math.log(1 - index / options.length));
|
|
4307
|
+
if (index < size) samples[index] = value;
|
|
4308
|
+
}
|
|
4309
|
+
return samples;
|
|
4310
|
+
});
|
|
4311
|
+
}
|
|
4312
|
+
function bufferOf(context, channels, requestedLength) {
|
|
4313
|
+
const buffer = context.createBuffer(channels.length, requestedLength, context.sampleRate);
|
|
4314
|
+
channels.forEach((samples, channel) => buffer.getChannelData(channel).set(samples));
|
|
4315
|
+
return buffer;
|
|
4316
|
+
}
|
|
4317
|
+
function buildChain(context, profile, random) {
|
|
4318
|
+
let compressor;
|
|
4319
|
+
if (profile.compressor) {
|
|
4320
|
+
compressor = context.createDynamicsCompressor();
|
|
4321
|
+
compressor.threshold.value = profile.compressor.threshold;
|
|
4322
|
+
compressor.ratio.value = profile.compressor.ratio;
|
|
4323
|
+
if (profile.compressor.knee !== void 0) compressor.knee.value = profile.compressor.knee;
|
|
4324
|
+
if (profile.compressor.attack !== void 0) compressor.attack.value = profile.compressor.attack;
|
|
4325
|
+
if (profile.compressor.release !== void 0) compressor.release.value = profile.compressor.release;
|
|
4326
|
+
compressor.connect(context.destination);
|
|
4327
|
+
}
|
|
4328
|
+
const master = context.createGain();
|
|
4329
|
+
master.gain.value = profile.master;
|
|
4330
|
+
master.connect(compressor ?? context.destination);
|
|
4331
|
+
if (!profile.reverb) {
|
|
4332
|
+
return {
|
|
4333
|
+
master,
|
|
4334
|
+
disconnect: () => {
|
|
4335
|
+
master.disconnect();
|
|
4336
|
+
compressor?.disconnect();
|
|
4337
|
+
}
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
const convolver = context.createConvolver();
|
|
4341
|
+
const length3 = context.sampleRate * profile.reverb.seconds;
|
|
4342
|
+
convolver.buffer = bufferOf(context, fillImpulse(random, { channels: 2, length: length3, decay: profile.reverb.decay }), length3);
|
|
4343
|
+
const wet = context.createGain();
|
|
4344
|
+
wet.gain.value = profile.reverb.wet;
|
|
4345
|
+
wet.connect(convolver);
|
|
4346
|
+
convolver.connect(master);
|
|
4347
|
+
return {
|
|
4348
|
+
master,
|
|
4349
|
+
wet,
|
|
4350
|
+
disconnect: () => {
|
|
4351
|
+
wet.disconnect();
|
|
4352
|
+
convolver.disconnect();
|
|
4353
|
+
master.disconnect();
|
|
4354
|
+
compressor?.disconnect();
|
|
4355
|
+
}
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4358
|
+
function globalContext() {
|
|
4359
|
+
const scope = globalThis;
|
|
4360
|
+
const Constructor = scope.AudioContext ?? scope.webkitAudioContext;
|
|
4361
|
+
return Constructor ? new Constructor() : null;
|
|
4362
|
+
}
|
|
4363
|
+
function unlockContext(context) {
|
|
4364
|
+
if (context.state === "suspended") void context.resume();
|
|
4365
|
+
}
|
|
4366
|
+
function assign(param2, automation, time) {
|
|
4367
|
+
if (automation.value !== void 0) param2.value = automation.value;
|
|
4368
|
+
if (automation.from !== void 0) param2.setValueAtTime(automation.from, time);
|
|
4369
|
+
}
|
|
4370
|
+
function ramp(param2, curve, value, at3) {
|
|
4371
|
+
if (curve === "set") param2.setValueAtTime(value, at3);
|
|
4372
|
+
else if (curve === "exp") param2.exponentialRampToValueAtTime(value, at3);
|
|
4373
|
+
else param2.linearRampToValueAtTime(value, at3);
|
|
4374
|
+
}
|
|
4375
|
+
function rampTo(param2, automation, time) {
|
|
4376
|
+
if (automation.to !== void 0 && automation.at !== void 0) ramp(param2, automation.curve ?? "linear", automation.to, time + automation.at);
|
|
4377
|
+
}
|
|
4378
|
+
function applyEnvelope(param2, points, time) {
|
|
4379
|
+
for (const point of points) ramp(param2, point.curve, point.value, time + point.time);
|
|
4380
|
+
}
|
|
4381
|
+
function noiseSource(target, loop) {
|
|
4382
|
+
const source = target.context.createBufferSource();
|
|
4383
|
+
source.buffer = target.noise();
|
|
4384
|
+
if (loop) source.loop = true;
|
|
4385
|
+
return source;
|
|
4386
|
+
}
|
|
4387
|
+
function filterNode(context, filter, time) {
|
|
4388
|
+
const node = context.createBiquadFilter();
|
|
4389
|
+
node.type = filter.type;
|
|
4390
|
+
assign(node.frequency, filter.frequency, time);
|
|
4391
|
+
if (filter.q !== void 0) node.Q.value = filter.q;
|
|
4392
|
+
if (filter.gain !== void 0) node.gain.value = filter.gain;
|
|
4393
|
+
return node;
|
|
4394
|
+
}
|
|
4395
|
+
function gainNode(context, value) {
|
|
4396
|
+
const node = context.createGain();
|
|
4397
|
+
node.gain.value = value;
|
|
4398
|
+
return node;
|
|
4399
|
+
}
|
|
4400
|
+
function voiceOf(sources) {
|
|
4401
|
+
return {
|
|
4402
|
+
sources,
|
|
4403
|
+
stop() {
|
|
4404
|
+
for (const source of sources) {
|
|
4405
|
+
try {
|
|
4406
|
+
source.stop();
|
|
4407
|
+
} catch {
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4413
|
+
var fixed = (range2) => typeof range2 === "number" ? range2 : 0;
|
|
4414
|
+
var fixedFrequency = (automation) => fixed("set" in automation ? automation.set : automation.from);
|
|
4415
|
+
function sourceOf(recipe, target) {
|
|
4416
|
+
if (recipe.source.kind === "noise") return { source: noiseSource(target, true) };
|
|
4417
|
+
const oscillator = target.context.createOscillator();
|
|
4418
|
+
oscillator.type = recipe.source.wave;
|
|
4419
|
+
if (recipe.source.frequency) oscillator.frequency.value = fixedFrequency(recipe.source.frequency);
|
|
4420
|
+
return { source: oscillator, frequency: oscillator.frequency };
|
|
4421
|
+
}
|
|
4422
|
+
function stopAll(sources) {
|
|
4423
|
+
return () => {
|
|
4424
|
+
for (const source of sources) {
|
|
4425
|
+
try {
|
|
4426
|
+
source.stop();
|
|
4427
|
+
} catch {
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
};
|
|
4431
|
+
}
|
|
4432
|
+
function realizeGameLoop(recipe, target) {
|
|
4433
|
+
const { context } = target;
|
|
4434
|
+
const gain = context.createGain();
|
|
4435
|
+
const filters = (recipe.filters?.bank ?? []).map(() => context.createBiquadFilter());
|
|
4436
|
+
gain.gain.value = 0;
|
|
4437
|
+
const last = filters.reduce((previous, filter) => previous ? previous.connect(filter) : filter, void 0);
|
|
4438
|
+
last?.connect(gain);
|
|
4439
|
+
gain.connect(target.output);
|
|
4440
|
+
const { source, frequency } = sourceOf(recipe, target);
|
|
4441
|
+
recipe.filters?.bank.forEach((spec, index) => {
|
|
4442
|
+
const filter = filters[index];
|
|
4443
|
+
filter.type = spec.type;
|
|
4444
|
+
filter.frequency.value = fixedFrequency(spec.frequency);
|
|
4445
|
+
if (spec.q !== void 0) filter.Q.value = fixed(spec.q);
|
|
4446
|
+
});
|
|
4447
|
+
const sources = [source];
|
|
4448
|
+
for (const modulator of recipe.modulators ?? []) {
|
|
4449
|
+
const lfo = context.createOscillator();
|
|
4450
|
+
const depth = context.createGain();
|
|
4451
|
+
const stage = context.createGain();
|
|
4452
|
+
if (modulator.wave) lfo.type = modulator.wave;
|
|
4453
|
+
lfo.frequency.value = fixed(modulator.rate);
|
|
4454
|
+
depth.gain.value = fixed(modulator.depth);
|
|
4455
|
+
stage.gain.value = modulator.base ?? 0;
|
|
4456
|
+
lfo.connect(depth);
|
|
4457
|
+
depth.connect(stage.gain);
|
|
4458
|
+
last?.disconnect();
|
|
4459
|
+
last?.connect(stage);
|
|
4460
|
+
stage.connect(gain);
|
|
4461
|
+
lfo.start();
|
|
4462
|
+
sources.push(lfo);
|
|
4463
|
+
}
|
|
4464
|
+
source.connect(filters[0] ?? gain);
|
|
4465
|
+
source.start();
|
|
4466
|
+
return { gain, source, ...frequency ? { frequency } : {}, stop: stopAll(sources) };
|
|
4467
|
+
}
|
|
4468
|
+
function realizeChainLoop(recipe, target, volume) {
|
|
4469
|
+
const { context } = target;
|
|
4470
|
+
const { source, frequency } = sourceOf(recipe, target);
|
|
4471
|
+
const filters = (recipe.filters?.bank ?? []).map(
|
|
4472
|
+
(spec) => filterNode(context, { type: spec.type, frequency: { value: fixedFrequency(spec.frequency) }, ...spec.q === void 0 ? {} : { q: fixed(spec.q) } }, target.time)
|
|
4473
|
+
);
|
|
4474
|
+
const gain = gainNode(context, volume);
|
|
4475
|
+
const lfos = (recipe.modulators ?? []).map((modulator) => {
|
|
4476
|
+
const lfo = context.createOscillator();
|
|
4477
|
+
if (modulator.wave) lfo.type = modulator.wave;
|
|
4478
|
+
lfo.frequency.value = fixed(modulator.rate);
|
|
4479
|
+
const depth = gainNode(context, fixed(modulator.depth));
|
|
4480
|
+
lfo.connect(depth);
|
|
4481
|
+
depth.connect(gain.gain);
|
|
4482
|
+
return lfo;
|
|
4483
|
+
});
|
|
4484
|
+
let tail = source;
|
|
4485
|
+
for (const filter of filters) tail = tail.connect(filter);
|
|
4486
|
+
tail.connect(gain);
|
|
4487
|
+
gain.connect(target.output);
|
|
4488
|
+
source.start();
|
|
4489
|
+
for (const lfo of lfos) lfo.start();
|
|
4490
|
+
return { gain, source, ...frequency ? { frequency } : {}, stop: stopAll([source, ...lfos]) };
|
|
4491
|
+
}
|
|
4492
|
+
var INERT = { set() {
|
|
4493
|
+
}, stop() {
|
|
4494
|
+
} };
|
|
4495
|
+
function createLoopHandle(recipe, host, volume) {
|
|
4496
|
+
if (!recipe) return INERT;
|
|
4497
|
+
const loopRecipe = recipe;
|
|
4498
|
+
const isChain = loopRecipe.order === "chain_loop";
|
|
4499
|
+
const opening = volume ?? loopRecipe.volume ?? 0;
|
|
4500
|
+
const smoothing = loopRecipe.smoothing ?? LOOP_SMOOTHING;
|
|
4501
|
+
let state = createLoopState(loopRecipe);
|
|
4502
|
+
let nodes;
|
|
4503
|
+
const entry = {
|
|
4504
|
+
get created() {
|
|
4505
|
+
return nodes !== void 0;
|
|
4506
|
+
},
|
|
4507
|
+
rewire() {
|
|
4508
|
+
nodes?.gain.disconnect();
|
|
4509
|
+
nodes?.gain.connect(host.output(loopRecipe.bus));
|
|
4510
|
+
},
|
|
4511
|
+
stop() {
|
|
4512
|
+
nodes?.stop();
|
|
4513
|
+
nodes?.gain.disconnect();
|
|
4514
|
+
nodes = void 0;
|
|
4515
|
+
state = createLoopState(loopRecipe);
|
|
4516
|
+
host.unregister(entry);
|
|
4517
|
+
}
|
|
4518
|
+
};
|
|
4519
|
+
const create = (context, startVolume) => {
|
|
4520
|
+
if (loopRecipe.source.kind === "noise") host.prepareNoise();
|
|
4521
|
+
const target = { context, output: host.output(loopRecipe.bus), noise: host.noise, time: context.currentTime };
|
|
4522
|
+
const made = isChain ? realizeChainLoop(loopRecipe, target, startVolume) : realizeGameLoop(loopRecipe, target);
|
|
4523
|
+
host.register(entry);
|
|
4524
|
+
return made;
|
|
4525
|
+
};
|
|
4526
|
+
const open = () => {
|
|
4527
|
+
const context = host.ready();
|
|
4528
|
+
if (!context) return;
|
|
4529
|
+
nodes = create(context, opening);
|
|
4530
|
+
state = createLoopState({ ...loopRecipe, volume: opening }, true);
|
|
4531
|
+
};
|
|
4532
|
+
if (isChain) open();
|
|
4533
|
+
return {
|
|
4534
|
+
set(next) {
|
|
4535
|
+
if (isChain && !nodes) open();
|
|
4536
|
+
const context = host.ready();
|
|
4537
|
+
if (!context) return;
|
|
4538
|
+
const action = state.set(next);
|
|
4539
|
+
if (!action) return;
|
|
4540
|
+
nodes ?? (nodes = create(context, 0));
|
|
4541
|
+
nodes.gain.gain.setTargetAtTime(action.volume, context.currentTime, smoothing.volume);
|
|
4542
|
+
if (action.frequency !== void 0 && nodes.frequency) nodes.frequency.setTargetAtTime(action.frequency, context.currentTime, smoothing.frequency);
|
|
4543
|
+
},
|
|
4544
|
+
stop: entry.stop
|
|
4545
|
+
};
|
|
4546
|
+
}
|
|
4547
|
+
function createNoiseStore(random) {
|
|
4548
|
+
const store = {
|
|
4549
|
+
random,
|
|
4550
|
+
reset() {
|
|
4551
|
+
store.samples = void 0;
|
|
4552
|
+
store.buffer = void 0;
|
|
4553
|
+
}
|
|
4554
|
+
};
|
|
4555
|
+
return store;
|
|
4556
|
+
}
|
|
4557
|
+
function prepareNoise(store, context, seconds) {
|
|
4558
|
+
store.samples ?? (store.samples = fillNoise(store.random, Math.floor(context.sampleRate * seconds)));
|
|
4559
|
+
return store.samples;
|
|
4560
|
+
}
|
|
4561
|
+
function noiseBuffer(store, context, seconds) {
|
|
4562
|
+
const samples = prepareNoise(store, context, seconds);
|
|
4563
|
+
store.buffer ?? (store.buffer = bufferOf(context, [samples], context.sampleRate * seconds));
|
|
4564
|
+
return store.buffer;
|
|
4565
|
+
}
|
|
4566
|
+
var end = (layer2, start) => start + layer2.duration + layer2.tail;
|
|
4567
|
+
var firstFilter = (layer2) => layer2.filters?.bank[0];
|
|
4568
|
+
var offsetOf = (layer2) => layer2.source.kind === "noise" ? layer2.source.offset : 0;
|
|
4569
|
+
function setFilter(node, filter, time) {
|
|
4570
|
+
if (!filter) return;
|
|
4571
|
+
node.type = filter.type;
|
|
4572
|
+
assign(node.frequency, filter.frequency, time);
|
|
4573
|
+
if (filter.q !== void 0) node.Q.value = filter.q;
|
|
4574
|
+
}
|
|
4575
|
+
function realizeBurst(plan, target) {
|
|
4576
|
+
const { context } = target;
|
|
4577
|
+
const layer2 = plan.layers[0];
|
|
4578
|
+
const time = target.time + layer2.at;
|
|
4579
|
+
const gain = context.createGain();
|
|
4580
|
+
gain.connect(target.output);
|
|
4581
|
+
const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
|
|
4582
|
+
const filter = context.createBiquadFilter();
|
|
4583
|
+
source.connect(filter);
|
|
4584
|
+
filter.connect(gain);
|
|
4585
|
+
setFilter(filter, firstFilter(layer2), time);
|
|
4586
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4587
|
+
source.start(time, offsetOf(layer2));
|
|
4588
|
+
source.stop(end(layer2, time));
|
|
4589
|
+
return voiceOf([source]);
|
|
4590
|
+
}
|
|
4591
|
+
function realizeScream(plan, target) {
|
|
4592
|
+
const { context } = target;
|
|
4593
|
+
const layer2 = plan.layers[0];
|
|
4594
|
+
const time = target.time + layer2.at;
|
|
4595
|
+
const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
|
|
4596
|
+
const gain = context.createGain();
|
|
4597
|
+
gain.connect(target.output);
|
|
4598
|
+
for (const band of layer2.filters?.bank ?? []) {
|
|
4599
|
+
const filter = context.createBiquadFilter();
|
|
4600
|
+
filter.type = band.type;
|
|
4601
|
+
if (band.q !== void 0) filter.Q.value = band.q;
|
|
4602
|
+
assign(filter.frequency, band.frequency, time);
|
|
4603
|
+
rampTo(filter.frequency, band.frequency, time);
|
|
4604
|
+
const level = context.createGain();
|
|
4605
|
+
level.gain.value = band.level ?? 1;
|
|
4606
|
+
source.connect(filter);
|
|
4607
|
+
filter.connect(level);
|
|
4608
|
+
level.connect(gain);
|
|
4609
|
+
}
|
|
4610
|
+
const sources = [source];
|
|
4611
|
+
const lfos = layer2.modulators.map((modulator) => {
|
|
4612
|
+
const lfo = context.createOscillator();
|
|
4613
|
+
const depth = context.createGain();
|
|
4614
|
+
if (modulator.wave) lfo.type = modulator.wave;
|
|
4615
|
+
lfo.frequency.value = modulator.rate;
|
|
4616
|
+
depth.gain.value = modulator.depth;
|
|
4617
|
+
lfo.connect(depth);
|
|
4618
|
+
depth.connect(gain.gain);
|
|
4619
|
+
return lfo;
|
|
4620
|
+
});
|
|
4621
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4622
|
+
source.start(time, offsetOf(layer2));
|
|
4623
|
+
source.stop(end(layer2, time));
|
|
4624
|
+
for (const lfo of lfos) {
|
|
4625
|
+
lfo.start(time);
|
|
4626
|
+
lfo.stop(end(layer2, time));
|
|
4627
|
+
sources.push(lfo);
|
|
4628
|
+
}
|
|
4629
|
+
return voiceOf(sources);
|
|
4630
|
+
}
|
|
4631
|
+
function realizeGroan(plan, target) {
|
|
4632
|
+
const { context } = target;
|
|
4633
|
+
const layer2 = plan.layers[0];
|
|
4634
|
+
const time = target.time + layer2.at;
|
|
4635
|
+
const oscillator = context.createOscillator();
|
|
4636
|
+
const gain = context.createGain();
|
|
4637
|
+
const filter = context.createBiquadFilter();
|
|
4638
|
+
const band = firstFilter(layer2);
|
|
4639
|
+
if (layer2.source.kind === "osc") oscillator.type = layer2.source.wave;
|
|
4640
|
+
if (band) {
|
|
4641
|
+
filter.type = band.type;
|
|
4642
|
+
if (band.q !== void 0) filter.Q.value = band.q;
|
|
4643
|
+
}
|
|
4644
|
+
if (layer2.source.kind === "osc") {
|
|
4645
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
4646
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
4647
|
+
}
|
|
4648
|
+
if (band) assign(filter.frequency, band.frequency, time);
|
|
4649
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4650
|
+
oscillator.connect(filter);
|
|
4651
|
+
filter.connect(gain);
|
|
4652
|
+
gain.connect(target.output);
|
|
4653
|
+
oscillator.start(time);
|
|
4654
|
+
oscillator.stop(end(layer2, time));
|
|
4655
|
+
return voiceOf([oscillator]);
|
|
4656
|
+
}
|
|
4657
|
+
function realizeBoom(plan, target) {
|
|
4658
|
+
const { context } = target;
|
|
4659
|
+
const layer2 = plan.layers[0];
|
|
4660
|
+
const time = target.time + layer2.at;
|
|
4661
|
+
const oscillator = context.createOscillator();
|
|
4662
|
+
const gain = context.createGain();
|
|
4663
|
+
if (layer2.source.kind === "osc") {
|
|
4664
|
+
oscillator.type = layer2.source.wave;
|
|
4665
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
4666
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
4667
|
+
}
|
|
4668
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4669
|
+
oscillator.connect(gain);
|
|
4670
|
+
gain.connect(target.output);
|
|
4671
|
+
oscillator.start(time);
|
|
4672
|
+
oscillator.stop(end(layer2, time));
|
|
4673
|
+
return voiceOf([oscillator]);
|
|
4674
|
+
}
|
|
4675
|
+
function realizeStatic(plan, target) {
|
|
4676
|
+
const { context } = target;
|
|
4677
|
+
const layer2 = plan.layers[0];
|
|
4678
|
+
const time = target.time + layer2.at;
|
|
4679
|
+
const gain = context.createGain();
|
|
4680
|
+
const source = context.createBufferSource();
|
|
4681
|
+
const filter = context.createBiquadFilter();
|
|
4682
|
+
source.buffer = target.noise();
|
|
4683
|
+
setFilter(filter, firstFilter(layer2), time);
|
|
4684
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4685
|
+
source.connect(filter);
|
|
4686
|
+
filter.connect(gain);
|
|
4687
|
+
gain.connect(target.output);
|
|
4688
|
+
source.start(time, offsetOf(layer2));
|
|
4689
|
+
source.stop(end(layer2, time));
|
|
4690
|
+
return voiceOf([source]);
|
|
4691
|
+
}
|
|
4692
|
+
function realizeChord(plan, target) {
|
|
4693
|
+
const { context } = target;
|
|
4694
|
+
const layers = plan.layers;
|
|
4695
|
+
const oscillators = layers.map(() => context.createOscillator());
|
|
4696
|
+
const gain = context.createGain();
|
|
4697
|
+
for (let index = layers.length - 1; index >= 0; index--) {
|
|
4698
|
+
const source = layers[index].source;
|
|
4699
|
+
if (source.kind === "osc") oscillators[index].type = source.wave;
|
|
4700
|
+
}
|
|
4701
|
+
layers.forEach((layer2, index) => {
|
|
4702
|
+
if (layer2.source.kind === "osc") assign(oscillators[index].frequency, layer2.source.frequency, target.time + layer2.at);
|
|
4703
|
+
});
|
|
4704
|
+
applyEnvelope(gain.gain, layers[0].envelope, target.time + layers[0].at);
|
|
4705
|
+
for (const oscillator of oscillators) oscillator.connect(gain);
|
|
4706
|
+
gain.connect(target.output);
|
|
4707
|
+
layers.forEach((layer2, index) => oscillators[index].start(target.time + layer2.at));
|
|
4708
|
+
layers.forEach((layer2, index) => oscillators[index].stop(end(layer2, target.time + layer2.at)));
|
|
4709
|
+
return voiceOf(oscillators);
|
|
4710
|
+
}
|
|
4711
|
+
function realizeCalls(plan, target) {
|
|
4712
|
+
const { context } = target;
|
|
4713
|
+
const gain = context.createGain();
|
|
4714
|
+
const filter = context.createBiquadFilter();
|
|
4715
|
+
if (plan.silent) return voiceOf([]);
|
|
4716
|
+
const band = firstFilter(plan.layers[0]);
|
|
4717
|
+
if (band) filter.type = band.type;
|
|
4718
|
+
filter.connect(gain);
|
|
4719
|
+
gain.connect(target.output);
|
|
4720
|
+
const oscillators = plan.layers.map((layer2) => {
|
|
4721
|
+
const time = target.time + layer2.at;
|
|
4722
|
+
const oscillator = context.createOscillator();
|
|
4723
|
+
if (layer2.source.kind === "osc") {
|
|
4724
|
+
oscillator.type = layer2.source.wave;
|
|
4725
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
4726
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
4727
|
+
}
|
|
4728
|
+
if (band) {
|
|
4729
|
+
assign(filter.frequency, band.frequency, time);
|
|
4730
|
+
if (band.q !== void 0) filter.Q.value = band.q;
|
|
4731
|
+
}
|
|
4732
|
+
const envelope2 = context.createGain();
|
|
4733
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
4734
|
+
oscillator.connect(envelope2);
|
|
4735
|
+
envelope2.connect(filter);
|
|
4736
|
+
oscillator.start(time);
|
|
4737
|
+
oscillator.stop(end(layer2, time));
|
|
4738
|
+
return oscillator;
|
|
4739
|
+
});
|
|
4740
|
+
return voiceOf(oscillators);
|
|
4741
|
+
}
|
|
4742
|
+
var end2 = (layer2, start) => start + layer2.duration + layer2.tail;
|
|
4743
|
+
function outputStage(plan, layer2, target) {
|
|
4744
|
+
const { context } = target;
|
|
4745
|
+
const level = gainNode(context, layer2.level);
|
|
4746
|
+
let node = level;
|
|
4747
|
+
if (plan.pan !== void 0 && typeof context.createStereoPanner === "function") {
|
|
4748
|
+
const panner = context.createStereoPanner();
|
|
4749
|
+
panner.pan.value = plan.pan;
|
|
4750
|
+
level.connect(panner);
|
|
4751
|
+
node = panner;
|
|
4752
|
+
}
|
|
4753
|
+
node.connect(target.output);
|
|
4754
|
+
if (layer2.wet && target.wet) {
|
|
4755
|
+
const send = gainNode(context, layer2.wet);
|
|
4756
|
+
node.connect(send);
|
|
4757
|
+
send.connect(target.wet);
|
|
4758
|
+
}
|
|
4759
|
+
return level;
|
|
4760
|
+
}
|
|
4761
|
+
function realizeNoiseLayer(plan, layer2, target) {
|
|
4762
|
+
const { context } = target;
|
|
4763
|
+
const time = target.time + layer2.at;
|
|
4764
|
+
const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
|
|
4765
|
+
const filters = (layer2.filters?.bank ?? []).map((filter) => {
|
|
4766
|
+
const node = filterNode(context, filter, time);
|
|
4767
|
+
rampTo(node.frequency, filter.frequency, time);
|
|
4768
|
+
return node;
|
|
4769
|
+
});
|
|
4770
|
+
const envelope2 = context.createGain();
|
|
4771
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
4772
|
+
let tail = source;
|
|
4773
|
+
for (const filter of filters) tail = tail.connect(filter);
|
|
4774
|
+
tail.connect(envelope2);
|
|
4775
|
+
envelope2.connect(outputStage(plan, layer2, target));
|
|
4776
|
+
source.start(time, layer2.source.kind === "noise" ? layer2.source.offset : 0);
|
|
4777
|
+
source.stop(end2(layer2, time));
|
|
4778
|
+
return [source];
|
|
4779
|
+
}
|
|
4780
|
+
function realizeToneLayer(plan, layer2, target) {
|
|
4781
|
+
const { context } = target;
|
|
4782
|
+
const time = target.time + layer2.at;
|
|
4783
|
+
const oscillator = context.createOscillator();
|
|
4784
|
+
if (layer2.source.kind === "osc") {
|
|
4785
|
+
oscillator.type = layer2.source.wave;
|
|
4786
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
4787
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
4788
|
+
}
|
|
4789
|
+
const envelope2 = context.createGain();
|
|
4790
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
4791
|
+
oscillator.connect(envelope2);
|
|
4792
|
+
envelope2.connect(outputStage(plan, layer2, target));
|
|
4793
|
+
oscillator.start(time);
|
|
4794
|
+
oscillator.stop(end2(layer2, time));
|
|
4795
|
+
return [oscillator];
|
|
4796
|
+
}
|
|
4797
|
+
function realizeVoiceLayer(plan, layer2, target) {
|
|
4798
|
+
const source = layer2.source;
|
|
4799
|
+
if (source.kind !== "formant_voice") return [];
|
|
4800
|
+
const { context } = target;
|
|
4801
|
+
const time = target.time + layer2.at;
|
|
4802
|
+
const oscillator = context.createOscillator();
|
|
4803
|
+
oscillator.type = source.wave;
|
|
4804
|
+
assign(oscillator.frequency, source.frequency, time);
|
|
4805
|
+
rampTo(oscillator.frequency, source.frequency, time);
|
|
4806
|
+
const vibrato = context.createOscillator();
|
|
4807
|
+
vibrato.frequency.value = source.vibrato.rate;
|
|
4808
|
+
const vibratoDepth = gainNode(context, source.vibrato.depth);
|
|
4809
|
+
vibrato.connect(vibratoDepth);
|
|
4810
|
+
vibratoDepth.connect(oscillator.frequency);
|
|
4811
|
+
const formants = source.formants.map((formant) => filterNode(context, { type: "bandpass", frequency: { value: formant.frequency }, q: formant.q }, time));
|
|
4812
|
+
const breath = noiseSource(target, true);
|
|
4813
|
+
const breathBand = filterNode(context, { type: "bandpass", frequency: { value: source.breath.frequency }, q: source.breath.q }, time);
|
|
4814
|
+
const breathLevel = gainNode(context, source.breath.gain);
|
|
4815
|
+
const mix = gainNode(context, source.mix);
|
|
4816
|
+
for (const formant of formants) oscillator.connect(formant);
|
|
4817
|
+
breath.connect(breathBand);
|
|
4818
|
+
breathBand.connect(breathLevel);
|
|
4819
|
+
for (const formant of formants) formant.connect(mix);
|
|
4820
|
+
breathLevel.connect(mix);
|
|
4821
|
+
const envelope2 = context.createGain();
|
|
4822
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
4823
|
+
mix.connect(envelope2);
|
|
4824
|
+
envelope2.connect(outputStage(plan, layer2, target));
|
|
4825
|
+
const stop = end2(layer2, time);
|
|
4826
|
+
oscillator.start(time);
|
|
4827
|
+
vibrato.start(time);
|
|
4828
|
+
breath.start(time, source.breath.offset);
|
|
4829
|
+
oscillator.stop(stop);
|
|
4830
|
+
vibrato.stop(stop);
|
|
4831
|
+
breath.stop(stop);
|
|
4832
|
+
return [oscillator, vibrato, breath];
|
|
4833
|
+
}
|
|
4834
|
+
function realizeLayered(plan, target) {
|
|
4835
|
+
const sources = plan.layers.flatMap((layer2) => {
|
|
4836
|
+
if (layer2.source.kind === "noise") return realizeNoiseLayer(plan, layer2, target);
|
|
4837
|
+
if (layer2.source.kind === "osc") return realizeToneLayer(plan, layer2, target);
|
|
4838
|
+
return realizeVoiceLayer(plan, layer2, target);
|
|
4839
|
+
});
|
|
4840
|
+
return voiceOf(sources);
|
|
4841
|
+
}
|
|
4842
|
+
function realizeSiren(plan, target) {
|
|
4843
|
+
const { context } = target;
|
|
4844
|
+
const layer2 = plan.layers[0];
|
|
4845
|
+
const time = target.time + layer2.at;
|
|
4846
|
+
const oscillator = context.createOscillator();
|
|
4847
|
+
if (layer2.source.kind === "osc") {
|
|
4848
|
+
oscillator.type = layer2.source.wave;
|
|
4849
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
4850
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
4851
|
+
}
|
|
4852
|
+
const depths = layer2.modulators.map((modulator) => {
|
|
4853
|
+
const lfo = context.createOscillator();
|
|
4854
|
+
if (modulator.wave) lfo.type = modulator.wave;
|
|
4855
|
+
lfo.frequency.value = modulator.rate;
|
|
4856
|
+
const depth = gainNode(context, modulator.depth);
|
|
4857
|
+
lfo.connect(depth);
|
|
4858
|
+
if (modulator.target === "frequency") depth.connect(oscillator.frequency);
|
|
4859
|
+
return { lfo, depth, target: modulator.target };
|
|
4860
|
+
});
|
|
4861
|
+
const lfos = depths.map(({ lfo }) => lfo);
|
|
4862
|
+
const envelope2 = context.createGain();
|
|
4863
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
4864
|
+
for (const { depth, target: modulated } of depths) if (modulated === "gain") depth.connect(envelope2.gain);
|
|
4865
|
+
const filters = (layer2.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
|
|
4866
|
+
let tail = oscillator;
|
|
4867
|
+
for (const filter of filters) tail = tail.connect(filter);
|
|
4868
|
+
tail.connect(envelope2);
|
|
4869
|
+
envelope2.connect(target.output);
|
|
4870
|
+
if (layer2.wet && target.wet) {
|
|
4871
|
+
const send = gainNode(context, layer2.wet);
|
|
4872
|
+
envelope2.connect(send);
|
|
4873
|
+
send.connect(target.wet);
|
|
4874
|
+
}
|
|
4875
|
+
const stop = end2(layer2, time);
|
|
4876
|
+
oscillator.start(time);
|
|
4877
|
+
for (const lfo of lfos) lfo.start(time);
|
|
4878
|
+
oscillator.stop(stop);
|
|
4879
|
+
for (const lfo of lfos) lfo.stop(stop);
|
|
4880
|
+
return voiceOf([oscillator, ...lfos]);
|
|
4881
|
+
}
|
|
4882
|
+
function noiseLayer(layer2, target, time) {
|
|
4883
|
+
const { context } = target;
|
|
4884
|
+
const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
|
|
4885
|
+
const filters = (layer2.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
|
|
4886
|
+
const envelope2 = context.createGain();
|
|
4887
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
4888
|
+
let tail = source;
|
|
4889
|
+
for (const filter of filters) tail = tail.connect(filter);
|
|
4890
|
+
tail.connect(envelope2).connect(target.output);
|
|
4891
|
+
source.start(time, layer2.source.kind === "noise" ? layer2.source.offset : 0);
|
|
4892
|
+
source.stop(time + layer2.duration + layer2.tail);
|
|
4893
|
+
return source;
|
|
4894
|
+
}
|
|
4895
|
+
function toneLayer(layer2, target, time) {
|
|
4896
|
+
const { context } = target;
|
|
4897
|
+
const oscillator = context.createOscillator();
|
|
4898
|
+
if (layer2.source.kind === "osc") {
|
|
4899
|
+
oscillator.type = layer2.source.wave;
|
|
4900
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
4901
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
4902
|
+
}
|
|
4903
|
+
const envelope2 = context.createGain();
|
|
4904
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
4905
|
+
oscillator.connect(envelope2).connect(target.output);
|
|
4906
|
+
oscillator.start(time);
|
|
4907
|
+
oscillator.stop(time + layer2.duration + layer2.tail);
|
|
4908
|
+
return oscillator;
|
|
4909
|
+
}
|
|
4910
|
+
function realizeWeapon(plan, target) {
|
|
4911
|
+
const sources = plan.layers.map((layer2) => {
|
|
4912
|
+
const time = target.time + layer2.at;
|
|
4913
|
+
return layer2.source.kind === "noise" ? noiseLayer(layer2, target, time) : toneLayer(layer2, target, time);
|
|
4914
|
+
});
|
|
4915
|
+
return voiceOf(sources);
|
|
4916
|
+
}
|
|
4917
|
+
var BUILDERS = {
|
|
4918
|
+
layered: realizeLayered,
|
|
4919
|
+
burst: realizeBurst,
|
|
4920
|
+
scream: realizeScream,
|
|
4921
|
+
groan: realizeGroan,
|
|
4922
|
+
boom: realizeBoom,
|
|
4923
|
+
static: realizeStatic,
|
|
4924
|
+
chord: realizeChord,
|
|
4925
|
+
calls: realizeCalls,
|
|
4926
|
+
siren: realizeSiren,
|
|
4927
|
+
weapon: realizeWeapon
|
|
4928
|
+
};
|
|
4929
|
+
function realize(plan, target) {
|
|
4930
|
+
return BUILDERS[plan.order](plan, target);
|
|
4931
|
+
}
|
|
4932
|
+
var usesNoise = (recipe) => recipe.layers.some((layer2) => layer2.source.kind !== "osc");
|
|
4933
|
+
var planUsesNoise = (plan) => plan.layers.some((layer2) => layer2.source.kind !== "osc");
|
|
4934
|
+
function createAudioEngine(options = {}) {
|
|
4935
|
+
const seed = options.seed ?? 1;
|
|
4936
|
+
const registry = createSoundRegistry(options.banks);
|
|
4937
|
+
const voices = createVoiceManager({ ...options.groups ? { groups: options.groups } : {}, ...options.maxVoices === void 0 ? {} : { maxVoices: options.maxVoices } });
|
|
4938
|
+
const engineRandom = createRng2(seed);
|
|
4939
|
+
const shared = createNoiseStore(engineRandom);
|
|
4940
|
+
const busSettings = /* @__PURE__ */ new Map();
|
|
4941
|
+
const busNodes = /* @__PURE__ */ new Map();
|
|
4942
|
+
const meters = /* @__PURE__ */ new Map();
|
|
4943
|
+
const active = /* @__PURE__ */ new Map();
|
|
4944
|
+
const loops = /* @__PURE__ */ new Set();
|
|
4945
|
+
const named = /* @__PURE__ */ new Map();
|
|
4946
|
+
let mix = resolveMix(options.mix ?? DEFAULT_MIX, options.profiles);
|
|
4947
|
+
let context = null;
|
|
4948
|
+
let chain = null;
|
|
4949
|
+
let listener = DEFAULT_LISTENER;
|
|
4950
|
+
let lastTime = 0;
|
|
4951
|
+
const outputFor = (bus) => busNodes.get(bus) ?? chain?.master;
|
|
4952
|
+
const watched = /* @__PURE__ */ new Set();
|
|
4953
|
+
const meterOf = (bus) => {
|
|
4954
|
+
const existing = meters.get(bus);
|
|
4955
|
+
if (existing) return existing;
|
|
4956
|
+
const node = busNodes.get(bus);
|
|
4957
|
+
if (!context || !node || typeof context.createAnalyser !== "function") return void 0;
|
|
4958
|
+
const analyser = context.createAnalyser();
|
|
4959
|
+
analyser.fftSize = 1024;
|
|
4960
|
+
node.connect(analyser);
|
|
4961
|
+
const made = { analyser, data: new Float32Array(analyser.fftSize) };
|
|
4962
|
+
meters.set(bus, made);
|
|
4963
|
+
return made;
|
|
4964
|
+
};
|
|
4965
|
+
const ensureBus = (bus) => {
|
|
4966
|
+
if (!context || !chain || busNodes.has(bus)) return;
|
|
4967
|
+
const node = context.createGain();
|
|
4968
|
+
node.connect(chain.master);
|
|
4969
|
+
busNodes.set(bus, node);
|
|
4970
|
+
if (watched.has(bus)) meterOf(bus);
|
|
4971
|
+
};
|
|
4972
|
+
const ready = () => {
|
|
4973
|
+
if (context) return context;
|
|
4974
|
+
const created = (options.createContext ?? globalContext)();
|
|
4975
|
+
if (!created) return null;
|
|
4976
|
+
context = created;
|
|
4977
|
+
chain = buildChain(created, mix, engineRandom);
|
|
4978
|
+
if (mix.noise.create === "eager") noiseBuffer(shared, created, mix.noise.seconds);
|
|
4979
|
+
for (const [bus, setting] of Object.entries(mix.buses ?? {})) {
|
|
4980
|
+
busSettings.set(bus, { ...setting, ...busSettings.get(bus) });
|
|
4981
|
+
}
|
|
4982
|
+
for (const [bus, setting] of busSettings) {
|
|
4983
|
+
ensureBus(bus);
|
|
4984
|
+
busNodes.get(bus).gain.value = busGain(setting);
|
|
4985
|
+
}
|
|
4986
|
+
lastTime = created.currentTime;
|
|
4987
|
+
return created;
|
|
4988
|
+
};
|
|
4989
|
+
const advance = () => {
|
|
4990
|
+
if (!context) return;
|
|
4991
|
+
const elapsed = context.currentTime - lastTime;
|
|
4992
|
+
lastTime = context.currentTime;
|
|
4993
|
+
if (elapsed > 0) for (const id of voices.advance(elapsed)) active.delete(id);
|
|
4994
|
+
};
|
|
4995
|
+
const inputFor = (recipe, playOptions, random) => ({
|
|
4996
|
+
random,
|
|
4997
|
+
listener,
|
|
4998
|
+
noiseSeconds: mix.noise.seconds,
|
|
4999
|
+
...options.distance ? { distanceMode: options.distance } : {},
|
|
5000
|
+
...playOptions.volume === void 0 ? {} : { volume: playOptions.volume },
|
|
5001
|
+
...playOptions.distance === void 0 ? {} : { distance: playOptions.distance },
|
|
5002
|
+
...playOptions.position ? { position: playOptions.position } : {},
|
|
5003
|
+
...playOptions.params ? { params: playOptions.params } : {},
|
|
5004
|
+
...options.occlusion && playOptions.position && recipe.falloff?.model !== "none" ? { occlusion: options.occlusion(playOptions.position) } : {}
|
|
5005
|
+
});
|
|
5006
|
+
const start = (plan, store, voiceId, delay = 0) => {
|
|
5007
|
+
const audio = context;
|
|
5008
|
+
const realized = realize(plan, {
|
|
5009
|
+
context: audio,
|
|
5010
|
+
output: outputFor(plan.bus),
|
|
5011
|
+
...chain?.wet ? { wet: chain.wet } : {},
|
|
5012
|
+
noise: () => noiseBuffer(store, audio, mix.noise.seconds),
|
|
5013
|
+
time: audio.currentTime + delay
|
|
5014
|
+
});
|
|
5015
|
+
if (voiceId === void 0) return null;
|
|
5016
|
+
voices.setLength(voiceId, plan.length + delay);
|
|
5017
|
+
active.set(voiceId, realized);
|
|
5018
|
+
return {
|
|
5019
|
+
id: voiceId,
|
|
5020
|
+
plan,
|
|
5021
|
+
stop() {
|
|
5022
|
+
realized.stop();
|
|
5023
|
+
voices.release(voiceId);
|
|
5024
|
+
active.delete(voiceId);
|
|
5025
|
+
}
|
|
5026
|
+
};
|
|
5027
|
+
};
|
|
5028
|
+
const admit = (recipe, gain, distance4) => {
|
|
5029
|
+
const decision = voices.request({ ...recipe.group ? { group: recipe.group } : {}, priority: recipe.priority ?? busPriority(recipe.bus), gain, distance: distance4, length: 0 });
|
|
5030
|
+
if (!decision.accepted) return null;
|
|
5031
|
+
for (const id of decision.evicted) {
|
|
5032
|
+
active.get(id)?.stop();
|
|
5033
|
+
active.delete(id);
|
|
5034
|
+
}
|
|
5035
|
+
return decision.id;
|
|
5036
|
+
};
|
|
5037
|
+
const playWith = (id, playOptions, random, store) => {
|
|
5038
|
+
const recipe = registry.sound(id);
|
|
5039
|
+
if (!recipe || busSettings.get(recipe.bus)?.muted) return null;
|
|
5040
|
+
const distance4 = playOptions.distance ?? (playOptions.position ? listenerDistance(listener, playOptions.position, options.distance) : 0);
|
|
5041
|
+
const level = falloffGain(recipe.falloff, playOptions.volume ?? 1, distance4);
|
|
5042
|
+
const silent = isSilent(recipe.falloff, level);
|
|
5043
|
+
if (silent && !recipe.keepSilent) return null;
|
|
5044
|
+
const audio = ready();
|
|
5045
|
+
if (!audio) return null;
|
|
5046
|
+
unlockContext(audio);
|
|
5047
|
+
advance();
|
|
5048
|
+
const voiceId = silent ? void 0 : admit(recipe, level, distance4);
|
|
5049
|
+
if (voiceId === null) return null;
|
|
5050
|
+
if (!silent && usesNoise(recipe)) prepareNoise(store, audio, mix.noise.seconds);
|
|
5051
|
+
const plan = planSound(recipe, inputFor(recipe, playOptions, playOptions.seed === void 0 ? random : createRng2(playOptions.seed)));
|
|
5052
|
+
return plan ? start(plan, store, voiceId, playOptions.delay) : null;
|
|
5053
|
+
};
|
|
5054
|
+
const randomFor = (playOptions) => {
|
|
5055
|
+
if (playOptions.channel === void 0) return engineRandom;
|
|
5056
|
+
const existing = named.get(playOptions.channel);
|
|
5057
|
+
if (existing) return existing;
|
|
5058
|
+
const stream = streamFor(seed, playOptions.channel);
|
|
5059
|
+
named.set(playOptions.channel, stream);
|
|
5060
|
+
return stream;
|
|
5061
|
+
};
|
|
5062
|
+
const planWith = (id, playOptions, random) => {
|
|
5063
|
+
const recipe = registry.sound(id);
|
|
5064
|
+
return recipe ? planSound(recipe, inputFor(recipe, playOptions, random)) : null;
|
|
5065
|
+
};
|
|
5066
|
+
const loopHost = {
|
|
5067
|
+
ready,
|
|
5068
|
+
output: outputFor,
|
|
5069
|
+
prepareNoise: () => context && prepareNoise(shared, context, mix.noise.seconds),
|
|
5070
|
+
noise: () => noiseBuffer(shared, context, mix.noise.seconds),
|
|
5071
|
+
register: (loop) => void loops.add(loop),
|
|
5072
|
+
unregister: (loop) => void loops.delete(loop)
|
|
5073
|
+
};
|
|
5074
|
+
return {
|
|
5075
|
+
registry,
|
|
5076
|
+
register: (bank) => registry.register(bank),
|
|
5077
|
+
plan: (id, playOptions = {}) => planWith(id, playOptions, createRng2(playOptions.seed ?? seed)),
|
|
5078
|
+
play: (id, playOptions = {}) => playWith(id, playOptions, randomFor(playOptions), shared),
|
|
5079
|
+
playPlan(plan, playOptions = {}) {
|
|
5080
|
+
const audio = ready();
|
|
5081
|
+
if (!audio) return null;
|
|
5082
|
+
advance();
|
|
5083
|
+
const recipe = { id: plan.id, bus: plan.bus, layers: [], sources: [], priority: plan.priority, ...plan.group ? { group: plan.group } : {} };
|
|
5084
|
+
const voiceId = plan.silent ? void 0 : admit(recipe, plan.level, plan.distance);
|
|
5085
|
+
if (voiceId === null) return null;
|
|
5086
|
+
if (planUsesNoise(plan)) prepareNoise(shared, audio, mix.noise.seconds);
|
|
5087
|
+
return start(plan, shared, voiceId, playOptions.delay);
|
|
5088
|
+
},
|
|
5089
|
+
loop: (id, loopOptions = {}) => createLoopHandle(registry.loop(id), loopHost, loopOptions.volume),
|
|
5090
|
+
channel(channelOptions) {
|
|
5091
|
+
const random = channelOptions.random ?? (channelOptions.seed === void 0 ? engineRandom : createRng2(channelOptions.seed));
|
|
5092
|
+
const store = channelOptions.ownNoise ? createNoiseStore(random) : shared;
|
|
5093
|
+
return {
|
|
5094
|
+
random,
|
|
5095
|
+
play: (id, playOptions = {}) => playWith(id, playOptions, random, store),
|
|
5096
|
+
plan: (id, playOptions = {}) => planWith(id, playOptions, createRng2(playOptions.seed ?? seed))
|
|
5097
|
+
};
|
|
5098
|
+
},
|
|
5099
|
+
setListener(next) {
|
|
5100
|
+
listener = next;
|
|
5101
|
+
},
|
|
5102
|
+
setBus(id, setting) {
|
|
5103
|
+
busSettings.set(id, { ...busSettings.get(id), ...setting });
|
|
5104
|
+
if (!context) return;
|
|
5105
|
+
ensureBus(id);
|
|
5106
|
+
busNodes.get(id).gain.value = busGain(busSettings.get(id));
|
|
5107
|
+
},
|
|
5108
|
+
setMix(next) {
|
|
5109
|
+
const previous = mix;
|
|
5110
|
+
mix = resolveMix(next, options.profiles);
|
|
5111
|
+
if (!context || !chain) return;
|
|
5112
|
+
const old = chain;
|
|
5113
|
+
chain = buildChain(context, mix, engineRandom);
|
|
5114
|
+
if (mix.noise.seconds !== previous.noise.seconds) shared.reset();
|
|
5115
|
+
for (const node of busNodes.values()) {
|
|
5116
|
+
node.disconnect();
|
|
5117
|
+
node.connect(chain.master);
|
|
5118
|
+
}
|
|
5119
|
+
for (const loop of loops) loop.rewire();
|
|
5120
|
+
old.disconnect();
|
|
5121
|
+
},
|
|
5122
|
+
setMaster(gain) {
|
|
5123
|
+
mix = { ...mix, master: gain };
|
|
5124
|
+
if (chain) chain.master.gain.value = gain;
|
|
5125
|
+
},
|
|
5126
|
+
unlock() {
|
|
5127
|
+
const audio = ready();
|
|
5128
|
+
if (audio) unlockContext(audio);
|
|
5129
|
+
},
|
|
5130
|
+
level(bus) {
|
|
5131
|
+
watched.add(bus);
|
|
5132
|
+
ensureBus(bus);
|
|
5133
|
+
const meter = meterOf(bus);
|
|
5134
|
+
if (!meter) return 0;
|
|
5135
|
+
meter.analyser.getFloatTimeDomainData(meter.data);
|
|
5136
|
+
let sum2 = 0;
|
|
5137
|
+
for (const sample of meter.data) sum2 += sample * sample;
|
|
5138
|
+
return Math.sqrt(sum2 / meter.data.length);
|
|
5139
|
+
},
|
|
5140
|
+
stats() {
|
|
5141
|
+
advance();
|
|
5142
|
+
const voiceStats = voices.stats();
|
|
5143
|
+
return {
|
|
5144
|
+
context: context ? context.state : "none",
|
|
5145
|
+
voices: voiceStats.active,
|
|
5146
|
+
peak: voiceStats.peak,
|
|
5147
|
+
dropped: voiceStats.dropped,
|
|
5148
|
+
maxVoices: voiceStats.maxVoices,
|
|
5149
|
+
loops: [...loops].filter((loop) => loop.created).length
|
|
5150
|
+
};
|
|
5151
|
+
},
|
|
5152
|
+
dispose() {
|
|
5153
|
+
for (const id of active.keys()) voices.release(id);
|
|
5154
|
+
for (const loop of [...loops]) loop.stop();
|
|
5155
|
+
active.clear();
|
|
5156
|
+
void context?.close();
|
|
5157
|
+
context = null;
|
|
5158
|
+
chain = null;
|
|
5159
|
+
busNodes.clear();
|
|
5160
|
+
meters.clear();
|
|
5161
|
+
shared.reset();
|
|
5162
|
+
}
|
|
5163
|
+
};
|
|
5164
|
+
}
|
|
5165
|
+
function createAudioSink(engine) {
|
|
5166
|
+
const playing = /* @__PURE__ */ new Map();
|
|
5167
|
+
return {
|
|
5168
|
+
engine,
|
|
5169
|
+
play(request) {
|
|
5170
|
+
const handle = engine.play(request.sound, {
|
|
5171
|
+
...request.position ? { position: request.position } : {},
|
|
5172
|
+
...request.volume === void 0 ? {} : { volume: request.volume },
|
|
5173
|
+
...request.params ? { params: request.params } : {},
|
|
5174
|
+
...request.seed === void 0 ? {} : { seed: request.seed }
|
|
5175
|
+
});
|
|
5176
|
+
if (!handle) return null;
|
|
5177
|
+
playing.set(handle.id, handle);
|
|
5178
|
+
return { id: handle.id };
|
|
5179
|
+
},
|
|
5180
|
+
stop(handle) {
|
|
5181
|
+
playing.get(handle.id)?.stop();
|
|
5182
|
+
playing.delete(handle.id);
|
|
5183
|
+
},
|
|
5184
|
+
loop(sound) {
|
|
5185
|
+
const loop = engine.loop(sound);
|
|
5186
|
+
return { set: (state) => loop.set({ ...state.volume === void 0 ? {} : { volume: state.volume }, ...state.frequency === void 0 ? {} : { frequency: state.frequency } }), stop: () => loop.stop() };
|
|
5187
|
+
},
|
|
5188
|
+
setListener(listener) {
|
|
5189
|
+
engine.setListener(listener);
|
|
5190
|
+
},
|
|
5191
|
+
register(bank) {
|
|
5192
|
+
engine.register(bank);
|
|
5193
|
+
}
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
var BENCH_BANK = {
|
|
5197
|
+
id: "audio_bench",
|
|
5198
|
+
sounds: [
|
|
5199
|
+
{
|
|
5200
|
+
id: "bench_ping",
|
|
5201
|
+
bus: "ui",
|
|
5202
|
+
pan: true,
|
|
5203
|
+
duration: 0.25,
|
|
5204
|
+
layers: [
|
|
5205
|
+
{
|
|
5206
|
+
gain: 0.5,
|
|
5207
|
+
wet: 0.3,
|
|
5208
|
+
source: { kind: "osc", wave: "sine", frequency: { from: 880 } },
|
|
5209
|
+
envelope: [
|
|
5210
|
+
[0, 1e-4, "set", "fixed"],
|
|
5211
|
+
[2e-3, 1, "exp"],
|
|
5212
|
+
["1d", 1e-4, "exp", "fixed"]
|
|
5213
|
+
]
|
|
5214
|
+
}
|
|
5215
|
+
],
|
|
5216
|
+
falloff: DISTANCE_CURVES.art_scene,
|
|
5217
|
+
sources: ["sdk: bench probe, a 880 Hz ping through the art scene output stage"]
|
|
5218
|
+
},
|
|
5219
|
+
{
|
|
5220
|
+
id: "bench_hiss",
|
|
5221
|
+
bus: "ui",
|
|
5222
|
+
pan: true,
|
|
5223
|
+
duration: [0.3, 0.9],
|
|
5224
|
+
layers: [
|
|
5225
|
+
{
|
|
5226
|
+
gain: 0.4,
|
|
5227
|
+
source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
|
|
5228
|
+
filters: { mode: "serial", bank: [{ type: "highpass", frequency: { set: 3e3 }, q: 0.7 }] },
|
|
5229
|
+
envelope: [
|
|
5230
|
+
[0, 1e-4, "set", "fixed"],
|
|
5231
|
+
[2e-3, 1, "exp"],
|
|
5232
|
+
["1d", 1e-4, "exp", "fixed"]
|
|
5233
|
+
]
|
|
5234
|
+
}
|
|
5235
|
+
],
|
|
5236
|
+
falloff: DISTANCE_CURVES.art_scene,
|
|
5237
|
+
sources: ["sdk: bench probe, a highpass hiss of random length"]
|
|
5238
|
+
}
|
|
5239
|
+
]
|
|
5240
|
+
};
|
|
5241
|
+
|
|
3770
5242
|
// ../game-vfx/dist/index.js
|
|
3771
5243
|
var __defProp2 = Object.defineProperty;
|
|
3772
5244
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -6974,7 +8446,7 @@ var GameTestRoom = (() => {
|
|
|
6974
8446
|
mixed ^= mixed >>> 16;
|
|
6975
8447
|
return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
|
|
6976
8448
|
}
|
|
6977
|
-
function
|
|
8449
|
+
function streamFor2(eventSeed) {
|
|
6978
8450
|
return lehmer(mixEventSeed(eventSeed));
|
|
6979
8451
|
}
|
|
6980
8452
|
var TABLE = {
|
|
@@ -7070,10 +8542,10 @@ var GameTestRoom = (() => {
|
|
|
7070
8542
|
if (!best) return;
|
|
7071
8543
|
const offsetX = clamp22(x - best.x, -0.2, 0.2);
|
|
7072
8544
|
const offsetY = clamp22(y - best.y, -0.17, 0.17);
|
|
7073
|
-
for (let
|
|
8545
|
+
for (let draw2 = 0; draw2 < OBJECT_ID_DRAWS2; draw2++) random.next();
|
|
7074
8546
|
const chipTurn = random.between(0, 6);
|
|
7075
8547
|
const chipScale = random.between(0.8, 1.3) * scale2;
|
|
7076
|
-
for (let
|
|
8548
|
+
for (let draw2 = 0; draw2 < OBJECT_ID_DRAWS2; draw2++) random.next();
|
|
7077
8549
|
best.holes.push({ x: offsetX, y: offsetY, scale: scale2, chipTurn, chipScale });
|
|
7078
8550
|
impact(host.engine, "concrete", [ORIGIN2[0] + x, y, ORIGIN2[2] + 0.13], [0, 0, 1], 0.8);
|
|
7079
8551
|
} else if (action === "collapse") {
|
|
@@ -7972,7 +9444,7 @@ var GameTestRoom = (() => {
|
|
|
7972
9444
|
}
|
|
7973
9445
|
cue(step) {
|
|
7974
9446
|
const args = this.value(step.args);
|
|
7975
|
-
for (let
|
|
9447
|
+
for (let draw2 = 0; draw2 < (step.draws ?? 0); draw2++) this.deps.engine.random.next();
|
|
7976
9448
|
this.deps.onCue?.({ cue: step.cue, args, time: this.time });
|
|
7977
9449
|
}
|
|
7978
9450
|
scheduleSteps(step) {
|
|
@@ -9023,9 +10495,9 @@ var GameTestRoom = (() => {
|
|
|
9023
10495
|
this.color[i3] = lerp4(this.rgbStart[i3], this.rgbEnd[i3], t);
|
|
9024
10496
|
this.color[i3 + 1] = lerp4(this.rgbStart[i3 + 1], this.rgbEnd[i3 + 1], t);
|
|
9025
10497
|
this.color[i3 + 2] = lerp4(this.rgbStart[i3 + 2], this.rgbEnd[i3 + 2], t);
|
|
9026
|
-
let
|
|
9027
|
-
if (def.flicker)
|
|
9028
|
-
this.alpha[i] = clamp4(
|
|
10498
|
+
let envelope2 = t < def.fadeIn ? t / def.fadeIn : 1 - (t - def.fadeIn) / (1 - def.fadeIn);
|
|
10499
|
+
if (def.flicker) envelope2 *= 1 - def.flicker * (0.5 + 0.5 * Math.sin(age * 38 + this.seed[i] * 50));
|
|
10500
|
+
this.alpha[i] = clamp4(envelope2, 0, 1) * def.alpha * this.alphaMul[i];
|
|
9029
10501
|
top = i + 1;
|
|
9030
10502
|
}
|
|
9031
10503
|
this.high = top;
|
|
@@ -9730,10 +11202,10 @@ var GameTestRoom = (() => {
|
|
|
9730
11202
|
this.velocity[offset + 1] = this.direction.y * speed + (options.v ? options.v[1] : 0);
|
|
9731
11203
|
this.velocity[offset + 2] = this.direction.z * speed + (options.v ? options.v[2] : 0);
|
|
9732
11204
|
const start = hexToRgb(options.c0 ?? 16777215);
|
|
9733
|
-
const
|
|
11205
|
+
const end3 = options.c1 != null ? hexToRgb(options.c1) : start;
|
|
9734
11206
|
for (let channel = 0; channel < FLOATS_PER_VECTOR; channel++) {
|
|
9735
11207
|
this.colorStart[offset + channel] = start[channel];
|
|
9736
|
-
this.colorEnd[offset + channel] =
|
|
11208
|
+
this.colorEnd[offset + channel] = end3[channel];
|
|
9737
11209
|
}
|
|
9738
11210
|
this.life[index] = this.maxLife[index] = drawRange(random, options.life || 1);
|
|
9739
11211
|
this.startSize[index] = drawRange(random, options.size || 0.1);
|
|
@@ -9868,10 +11340,10 @@ var GameTestRoom = (() => {
|
|
|
9868
11340
|
this.velocity[offset + 1] = this.direction.y * speed + (options.v ? options.v[1] : 0);
|
|
9869
11341
|
this.velocity[offset + 2] = this.direction.z * speed + (options.v ? options.v[2] : 0);
|
|
9870
11342
|
const start = hexToRgb(options.c0 ?? 16765056);
|
|
9871
|
-
const
|
|
11343
|
+
const end3 = options.c1 != null ? hexToRgb(options.c1) : start;
|
|
9872
11344
|
for (let channel = 0; channel < FLOATS_PER_VECTOR2; channel++) {
|
|
9873
11345
|
this.colorStart[offset + channel] = start[channel];
|
|
9874
|
-
this.colorEnd[offset + channel] =
|
|
11346
|
+
this.colorEnd[offset + channel] = end3[channel];
|
|
9875
11347
|
}
|
|
9876
11348
|
this.life[index] = this.maxLife[index] = drawRange(random, options.life || 0.4);
|
|
9877
11349
|
this.length[index] = options.len || 0.02;
|
|
@@ -10255,7 +11727,7 @@ var GameTestRoom = (() => {
|
|
|
10255
11727
|
function streamOf(seed) {
|
|
10256
11728
|
if (randomMode === "shared") return sharedRandom;
|
|
10257
11729
|
handles.events++;
|
|
10258
|
-
return
|
|
11730
|
+
return streamFor2(seed ?? Math.imul(handles.events, AUTO_SEED) + (options.seed ?? 0));
|
|
10259
11731
|
}
|
|
10260
11732
|
function detonate(def, point, call3) {
|
|
10261
11733
|
composeExplosion(core, def, { x: point[0], y: point[1], z: point[2], direction: call3.direction, random: call3.random, handle: call3.handle });
|
|
@@ -10834,12 +12306,12 @@ void main(){
|
|
|
10834
12306
|
if (typeof document === "undefined") return blankCanvas();
|
|
10835
12307
|
return document.createElement("canvas");
|
|
10836
12308
|
}
|
|
10837
|
-
function paint(createCanvas,
|
|
12309
|
+
function paint(createCanvas, draw2) {
|
|
10838
12310
|
const canvas = createCanvas();
|
|
10839
12311
|
canvas.width = TEXTURE_SIZE;
|
|
10840
12312
|
canvas.height = TEXTURE_SIZE;
|
|
10841
12313
|
const context = canvas.getContext("2d");
|
|
10842
|
-
if (context)
|
|
12314
|
+
if (context) draw2(context, TEXTURE_SIZE);
|
|
10843
12315
|
return canvas;
|
|
10844
12316
|
}
|
|
10845
12317
|
function drawScorch(context, size, random) {
|
|
@@ -11697,7 +13169,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11697
13169
|
var __defProp3 = Object.defineProperty;
|
|
11698
13170
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11699
13171
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
11700
|
-
function
|
|
13172
|
+
function createRng3(seed) {
|
|
11701
13173
|
let a = seed >>> 0;
|
|
11702
13174
|
return () => {
|
|
11703
13175
|
a = a + 1831565813 >>> 0;
|
|
@@ -11745,7 +13217,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11745
13217
|
__publicField2(this, "m");
|
|
11746
13218
|
__publicField2(this, "v");
|
|
11747
13219
|
__publicField2(this, "w");
|
|
11748
|
-
__publicField2(this, "rng",
|
|
13220
|
+
__publicField2(this, "rng", createRng3(1374496523));
|
|
11749
13221
|
__publicField2(this, "markGeometry", null);
|
|
11750
13222
|
__publicField2(this, "markMaterial", null);
|
|
11751
13223
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -11776,7 +13248,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11776
13248
|
const idx = seed % 6;
|
|
11777
13249
|
let g = this.shardGeos[idx];
|
|
11778
13250
|
if (!g) {
|
|
11779
|
-
const r =
|
|
13251
|
+
const r = createRng3(1e3 + idx);
|
|
11780
13252
|
const n = 3 + Math.floor(r() * 3);
|
|
11781
13253
|
const shape = new this.THREE.Shape();
|
|
11782
13254
|
const pts = [];
|
|
@@ -11981,9 +13453,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11981
13453
|
};
|
|
11982
13454
|
var MAX_RADIUS = 1.2;
|
|
11983
13455
|
var GroundSpots = class {
|
|
11984
|
-
constructor(THREE,
|
|
13456
|
+
constructor(THREE, scene2, groundAt, cap) {
|
|
11985
13457
|
__publicField2(this, "THREE", THREE);
|
|
11986
|
-
__publicField2(this, "scene",
|
|
13458
|
+
__publicField2(this, "scene", scene2);
|
|
11987
13459
|
__publicField2(this, "groundAt", groundAt);
|
|
11988
13460
|
__publicField2(this, "cap", cap);
|
|
11989
13461
|
__publicField2(this, "spots", []);
|
|
@@ -12381,9 +13853,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12381
13853
|
}
|
|
12382
13854
|
};
|
|
12383
13855
|
var RebarCages = class {
|
|
12384
|
-
constructor(THREE,
|
|
13856
|
+
constructor(THREE, scene2, radius = 4e-3) {
|
|
12385
13857
|
__publicField2(this, "THREE", THREE);
|
|
12386
|
-
__publicField2(this, "scene",
|
|
13858
|
+
__publicField2(this, "scene", scene2);
|
|
12387
13859
|
__publicField2(this, "cages", /* @__PURE__ */ new Map());
|
|
12388
13860
|
__publicField2(this, "geometry");
|
|
12389
13861
|
__publicField2(this, "material");
|
|
@@ -12990,7 +14462,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12990
14462
|
/** A star of jagged lines: `rays` rays up to `length` metres, seeded so the same event always draws the same star. */
|
|
12991
14463
|
addCrack(mv, worldPoint, worldNormal, rays, length3, color, seed) {
|
|
12992
14464
|
const { s, pos, quat: quat2 } = this.decalPose(mv.mesh, worldPoint, worldNormal, 2e-3);
|
|
12993
|
-
const rnd2 =
|
|
14465
|
+
const rnd2 = createRng3(seed);
|
|
12994
14466
|
const verts = [];
|
|
12995
14467
|
const n = clamp6(Math.round(rays), 3, 16);
|
|
12996
14468
|
const L = Math.max(0.02, length3) / s;
|
|
@@ -13191,10 +14663,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13191
14663
|
const sy = lerp5(1, 0.25, melt);
|
|
13192
14664
|
const sxz = 1 + 0.18 * melt;
|
|
13193
14665
|
for (let i = range2[0] * 3, v = 0; i < range2[1] * 3; i += 3, v++) {
|
|
13194
|
-
const
|
|
14666
|
+
const noise2 = (Math.imul(v + 1, 2654435761) >>> 0) % 1e3 / 1e3;
|
|
13195
14667
|
arr[i] = cx + (work[i] - cx) * sxz;
|
|
13196
14668
|
arr[i + 2] = cz + (work[i + 2] - cz) * sxz;
|
|
13197
|
-
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy -
|
|
14669
|
+
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy - noise2 * melt * h * 0.04);
|
|
13198
14670
|
}
|
|
13199
14671
|
}
|
|
13200
14672
|
/** The colour attribute of the host's current geometry (created white when there is none) with vertex colours switched on in its material. */
|
|
@@ -13517,10 +14989,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13517
14989
|
__publicField2(this, "cages");
|
|
13518
14990
|
__publicField2(this, "active", /* @__PURE__ */ new Map());
|
|
13519
14991
|
__publicField2(this, "fires", /* @__PURE__ */ new Map());
|
|
14992
|
+
__publicField2(this, "primedBurning", /* @__PURE__ */ new Set());
|
|
13520
14993
|
__publicField2(this, "leaks", []);
|
|
13521
14994
|
__publicField2(this, "homeTags", []);
|
|
13522
14995
|
__publicField2(this, "throttles", /* @__PURE__ */ new Map());
|
|
13523
|
-
__publicField2(this, "rng",
|
|
14996
|
+
__publicField2(this, "rng", createRng3(12648430));
|
|
13524
14997
|
__publicField2(this, "vfx");
|
|
13525
14998
|
__publicField2(this, "owner", `destruction-${++rendererCount}`);
|
|
13526
14999
|
__publicField2(this, "stopLanding");
|
|
@@ -13856,8 +15329,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13856
15329
|
case "primed":
|
|
13857
15330
|
if (ev.trigger !== "explosion") {
|
|
13858
15331
|
this.vfx.fire({ point: ev.point, radius: PRIMED_FIRE_RADIUS, life: ev.delay });
|
|
15332
|
+
this.primedBurning.add(`${ev.object}|${ev.part}`);
|
|
13859
15333
|
this.activate(ev.object, ev.part);
|
|
13860
15334
|
this.sound("ignite", ev.point, 0.7);
|
|
15335
|
+
this.sound("burn_loop", ev.point, 0.5);
|
|
13861
15336
|
}
|
|
13862
15337
|
break;
|
|
13863
15338
|
case "spall":
|
|
@@ -13875,16 +15350,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13875
15350
|
break;
|
|
13876
15351
|
case "extinguish": {
|
|
13877
15352
|
const f = this.fires.get(`${ev.object}|${ev.part}`);
|
|
15353
|
+
const primed = this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
13878
15354
|
if (f) {
|
|
13879
15355
|
this.stopFire(`${ev.object}|${ev.part}`);
|
|
13880
15356
|
this.vfx.burst("steam", f.point, [0, 1, 0], 14, 1.2);
|
|
13881
15357
|
}
|
|
15358
|
+
if (f || primed) this.sound("burn_end", f?.point ?? this.rootPoint(ev.object), 0.5);
|
|
13882
15359
|
this.activate(ev.object, ev.part);
|
|
13883
15360
|
this.sound("extinguish", f?.point ?? this.rootPoint(ev.object), 0.6);
|
|
13884
15361
|
break;
|
|
13885
15362
|
}
|
|
13886
15363
|
case "burnout": {
|
|
13887
15364
|
this.stopFire(`${ev.object}|${ev.part}`);
|
|
15365
|
+
this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
15366
|
+
this.sound("burn_end", ev.point, 0.5);
|
|
13888
15367
|
this.vfx.burst("ash", ev.point, [0, 1, 0], 16, 1);
|
|
13889
15368
|
this.vfx.burst("smoke", ev.point, [0, 1, 0], 6, 0.8, { color: SMOKE_TONES.grey });
|
|
13890
15369
|
const pv = this.partVis(ev.object, ev.part);
|
|
@@ -13926,6 +15405,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13926
15405
|
break;
|
|
13927
15406
|
}
|
|
13928
15407
|
case "explode":
|
|
15408
|
+
this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
13929
15409
|
this.onExplode(ev);
|
|
13930
15410
|
break;
|
|
13931
15411
|
case "destroyed":
|
|
@@ -14009,7 +15489,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14009
15489
|
if (!b || !pv) return;
|
|
14010
15490
|
const def = this.world.materials.get(pv.material);
|
|
14011
15491
|
const n = this.worldDir(b, ev.hole.normal);
|
|
14012
|
-
const
|
|
15492
|
+
const draw2 = (point, normal) => {
|
|
14013
15493
|
const wp = new this.THREE.Vector3(point[0], point[1], point[2]);
|
|
14014
15494
|
const mv = this.nearestMesh(pv, wp);
|
|
14015
15495
|
if (!mv) return;
|
|
@@ -14020,8 +15500,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14020
15500
|
this.visuals.addHole(mv, wp, normal, ev.hole.diameter);
|
|
14021
15501
|
}
|
|
14022
15502
|
};
|
|
14023
|
-
|
|
14024
|
-
if (ev.hole.through && ev.exitPoint)
|
|
15503
|
+
draw2(ev.worldPoint, n);
|
|
15504
|
+
if (ev.hole.through && ev.exitPoint) draw2(ev.exitPoint, n.clone().negate());
|
|
14025
15505
|
if (def?.tags?.includes("metal")) this.vfx.burst("sparks", ev.worldPoint, [n.x, n.y, n.z], 6, 3.5);
|
|
14026
15506
|
this.sound(impactKey(def), ev.worldPoint, 0.6);
|
|
14027
15507
|
}
|
|
@@ -14416,6 +15896,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14416
15896
|
this.fragments.clear();
|
|
14417
15897
|
this.ground.clear();
|
|
14418
15898
|
this.cages.clear();
|
|
15899
|
+
this.primedBurning.clear();
|
|
14419
15900
|
this.vfx.sprites().clear();
|
|
14420
15901
|
this.ownVfx?.world.clear();
|
|
14421
15902
|
}
|
|
@@ -14444,12 +15925,197 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14444
15925
|
this.disposed = true;
|
|
14445
15926
|
}
|
|
14446
15927
|
};
|
|
15928
|
+
var ATTACK = 2e-3;
|
|
15929
|
+
var lengthOf = (d, attack) => {
|
|
15930
|
+
if (typeof d === "number") return attack + d;
|
|
15931
|
+
if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
|
|
15932
|
+
return { ...d, add: (d.add ?? 0) + attack };
|
|
15933
|
+
};
|
|
15934
|
+
var envelope = (attack) => [
|
|
15935
|
+
[0, 1e-4, "set", "fixed"],
|
|
15936
|
+
[attack, 1, "exp"],
|
|
15937
|
+
["1d", 1e-4, "exp", "fixed"]
|
|
15938
|
+
];
|
|
15939
|
+
function noise(o) {
|
|
15940
|
+
const a = o.a ?? ATTACK;
|
|
15941
|
+
const over = typeof o.d === "number" ? o.d : 0;
|
|
15942
|
+
return {
|
|
15943
|
+
filters: {
|
|
15944
|
+
mode: "serial",
|
|
15945
|
+
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 }]
|
|
15946
|
+
},
|
|
15947
|
+
duration: lengthOf(o.d, a),
|
|
15948
|
+
gain: o.vol,
|
|
15949
|
+
...o.delay === void 0 ? {} : { at: o.delay },
|
|
15950
|
+
...o.wet === void 0 ? {} : { wet: o.wet },
|
|
15951
|
+
source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
|
|
15952
|
+
envelope: envelope(a)
|
|
15953
|
+
};
|
|
15954
|
+
}
|
|
15955
|
+
function tone(o) {
|
|
15956
|
+
return {
|
|
15957
|
+
source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
|
|
15958
|
+
duration: lengthOf(o.d, ATTACK),
|
|
15959
|
+
gain: o.vol,
|
|
15960
|
+
...o.delay === void 0 ? {} : { at: o.delay },
|
|
15961
|
+
...o.wet === void 0 ? {} : { wet: o.wet },
|
|
15962
|
+
envelope: envelope(ATTACK)
|
|
15963
|
+
};
|
|
15964
|
+
}
|
|
15965
|
+
var many = (count, make) => Array.from({ length: count }, make);
|
|
15966
|
+
var scene = (id, layers, line, vars) => ({
|
|
15967
|
+
id,
|
|
15968
|
+
bus: "world",
|
|
15969
|
+
pan: true,
|
|
15970
|
+
layers,
|
|
15971
|
+
falloff: DISTANCE_CURVES.art_scene,
|
|
15972
|
+
...vars ? { vars } : {},
|
|
15973
|
+
sources: [`cena:${line}`]
|
|
15974
|
+
});
|
|
15975
|
+
var boomLayers = (big) => [
|
|
15976
|
+
noise({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
|
|
15977
|
+
tone({ f: 65, f2: 24, d: 1, vol: 1.8 }),
|
|
15978
|
+
noise({ type: "highpass", f: 1500, d: 0.2, vol: 0.8 }),
|
|
15979
|
+
...big ? [noise({ f: 500, f2: 90, a: 0.2, d: 2.4, vol: 1.2, wet: 1, delay: 0.15 })] : [],
|
|
15980
|
+
...many(8, () => tone({ f: [2600, 5600], d: 0.05, vol: 0.08, delay: [0.4, 1.6] }))
|
|
15981
|
+
];
|
|
15982
|
+
var SCENE_SOUNDS = [
|
|
15983
|
+
scene(
|
|
15984
|
+
"glass_shatter",
|
|
15985
|
+
[noise({ type: "highpass", f: 2800, d: 0.45, vol: 0.9, wet: 0.5 }), ...many(12, () => tone({ f: [3e3, 7500], d: [0.06, 0.14], vol: 0.18, delay: [0, 0.55] }))],
|
|
15986
|
+
"L1359 glass"
|
|
15987
|
+
),
|
|
15988
|
+
scene(
|
|
15989
|
+
"wood_hit",
|
|
15990
|
+
[noise({ type: "bandpass", f: 850, q: 1, d: 0.25, vol: 0.9, wet: 0.3 }), tone({ f: 210, f2: 90, d: 0.1, vol: 0.6 }), noise({ type: "highpass", f: 2e3, d: 0.04, vol: 0.5 })],
|
|
15991
|
+
"L1360 wood"
|
|
15992
|
+
),
|
|
15993
|
+
scene("thunk", [noise({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk"),
|
|
15994
|
+
scene("ping", [tone({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
|
|
15995
|
+
scene("clang", [tone({ f: [520, 720], f2: 480, d: 0.5, vol: 0.35, wet: 0.4 }), tone({ f: 1370, f2: 1300, d: 0.3, vol: 0.15 }), noise({ type: "highpass", f: 2e3, d: 0.05, vol: 0.5 })], "L1363 clang"),
|
|
15996
|
+
scene("chip", [noise({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
|
|
15997
|
+
scene(
|
|
15998
|
+
"crumble",
|
|
15999
|
+
[noise({ f: 900, f2: 200, d: 0.6, vol: 0.9, wet: 0.3 }), ...many(5, () => noise({ type: "bandpass", f: [500, 1400], q: 2, d: 0.05, vol: 0.4, delay: [0.1, 0.5] }))],
|
|
16000
|
+
"L1365 crumble"
|
|
16001
|
+
),
|
|
16002
|
+
// `thud(x, z, v)`: `v` is the `scale` param (default 1)
|
|
16003
|
+
scene("thud", [tone({ f: 75, f2: 38, d: 0.22, vol: { param: "scale", scale: 0.6 } }), noise({ f: 420, d: 0.16, vol: { param: "scale", scale: 0.45 } })], "L1358 thud", { scale: 1 }),
|
|
16004
|
+
// `hiss(x, z, d)`: `seconds` is the param `d` (default 1.2)
|
|
16005
|
+
scene("hiss", [noise({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }),
|
|
16006
|
+
scene("boom", boomLayers(false), "L1367 boom"),
|
|
16007
|
+
scene("boom_big", boomLayers(true), "L1367 boom (big)"),
|
|
16008
|
+
scene("burst", [noise({ f: 700, f2: 120, d: 0.7, vol: 1.4, wet: 0.5 }), noise({ type: "highpass", f: 3e3, a: 0.05, d: 1.2, vol: 0.4, delay: 0.1 })], "L1357 burst"),
|
|
16009
|
+
scene("crack", [noise({ type: "highpass", f: 1800, d: 0.05, vol: 0.8 }), tone({ f: 420, f2: 180, d: 0.06, vol: 0.4 })], "L1354 crack")
|
|
16010
|
+
];
|
|
16011
|
+
var EXPLOSION_BOOM = {
|
|
16012
|
+
id: "explosion_boom",
|
|
16013
|
+
bus: "world",
|
|
16014
|
+
order: "boom",
|
|
16015
|
+
duration: 1.6,
|
|
16016
|
+
tail: 0.1,
|
|
16017
|
+
layers: [
|
|
16018
|
+
{
|
|
16019
|
+
source: { kind: "osc", wave: "sine", frequency: { from: 70, to: 28, curve: "exp", over: 1.2 } },
|
|
16020
|
+
envelope: [
|
|
16021
|
+
[0, 1, "set"],
|
|
16022
|
+
["1d", 1e-3, "exp", "fixed"]
|
|
16023
|
+
]
|
|
16024
|
+
}
|
|
16025
|
+
],
|
|
16026
|
+
falloff: DISTANCE_CURVES.game_boom,
|
|
16027
|
+
sources: ["jogo:L68278-68285 boom"]
|
|
16028
|
+
};
|
|
16029
|
+
var BURN_LOOP = {
|
|
16030
|
+
id: "burn_loop",
|
|
16031
|
+
bus: "world",
|
|
16032
|
+
order: "chain_loop",
|
|
16033
|
+
source: { kind: "noise", loop: true },
|
|
16034
|
+
filters: { mode: "serial", bank: [{ type: "bandpass", frequency: { set: 1800 }, q: 0.6 }] },
|
|
16035
|
+
volume: 0,
|
|
16036
|
+
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
16037
|
+
};
|
|
16038
|
+
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, EXPLOSION_BOOM], loops: [BURN_LOOP] };
|
|
16039
|
+
var DESTRUCTION_SOUND_KEYS = {
|
|
16040
|
+
art_scene: {
|
|
16041
|
+
impact_metal: { sound: "clang" },
|
|
16042
|
+
impact_wood: { sound: "wood_hit" },
|
|
16043
|
+
impact_glass: { sound: "ping" },
|
|
16044
|
+
impact_masonry: { sound: "chip" },
|
|
16045
|
+
impact_soft: { sound: "thud" },
|
|
16046
|
+
shatter_glass: { sound: "glass_shatter" },
|
|
16047
|
+
crumble: { sound: "crumble" },
|
|
16048
|
+
splinter: { sound: "crack" },
|
|
16049
|
+
ricochet: { sound: "ping" },
|
|
16050
|
+
unscrew: { sound: "chip" },
|
|
16051
|
+
detach: { sound: "thunk" },
|
|
16052
|
+
ignite: { sound: "hiss", params: { seconds: 0.5 } },
|
|
16053
|
+
extinguish: { sound: "hiss" },
|
|
16054
|
+
melt_sizzle: { sound: "hiss", params: { seconds: 0.6 } },
|
|
16055
|
+
explode: { sound: "boom", above: { volume: 0.9, sound: "boom_big" } }
|
|
16056
|
+
},
|
|
16057
|
+
game: {
|
|
16058
|
+
impact_metal: { sound: "sfx_impact" },
|
|
16059
|
+
impact_wood: { sound: "sfx_wood" },
|
|
16060
|
+
impact_glass: { sound: "sfx_glass" },
|
|
16061
|
+
impact_masonry: { sound: "sfx_impact" },
|
|
16062
|
+
impact_soft: { sound: "sfx_flesh" },
|
|
16063
|
+
shatter_glass: { sound: "sfx_glass" },
|
|
16064
|
+
crumble: { sound: "sfx_impact" },
|
|
16065
|
+
splinter: { sound: "sfx_wood" },
|
|
16066
|
+
explode: { sound: "explosion_boom" }
|
|
16067
|
+
}
|
|
16068
|
+
};
|
|
16069
|
+
var DEFAULT_FIRE = { perFire: 0.05, max: 0.2 };
|
|
16070
|
+
function createDestructionSound(engine, options = {}) {
|
|
16071
|
+
engine.register(DESTRUCTION_SOUND_BANK);
|
|
16072
|
+
engine.register(GAME_SFX_BANK);
|
|
16073
|
+
const fire = { ...DEFAULT_FIRE, ...options.fire };
|
|
16074
|
+
const rules = { ...DESTRUCTION_SOUND_KEYS.art_scene, ...options.style === "game" ? DESTRUCTION_SOUND_KEYS.game : {}, ...options.keys };
|
|
16075
|
+
let loop;
|
|
16076
|
+
let burning = 0;
|
|
16077
|
+
const setFire = () => {
|
|
16078
|
+
if (options.burnLoop === false) return;
|
|
16079
|
+
if (burning <= 0) {
|
|
16080
|
+
loop?.stop();
|
|
16081
|
+
loop = void 0;
|
|
16082
|
+
return;
|
|
16083
|
+
}
|
|
16084
|
+
loop ?? (loop = engine.loop("burn_loop"));
|
|
16085
|
+
loop.set({ volume: Math.min(fire.max, fire.perFire * burning) });
|
|
16086
|
+
};
|
|
16087
|
+
return {
|
|
16088
|
+
onSound(key, point, volume) {
|
|
16089
|
+
if (key === "burn_loop") {
|
|
16090
|
+
burning += 1;
|
|
16091
|
+
setFire();
|
|
16092
|
+
return;
|
|
16093
|
+
}
|
|
16094
|
+
if (key === "burn_end") {
|
|
16095
|
+
if (burning > 0) burning -= 1;
|
|
16096
|
+
setFire();
|
|
16097
|
+
return;
|
|
16098
|
+
}
|
|
16099
|
+
const rule = rules[key];
|
|
16100
|
+
if (!rule) return;
|
|
16101
|
+
const sound = rule.above && volume >= rule.above.volume ? rule.above.sound : rule.sound;
|
|
16102
|
+
engine.play(sound, { position: point, volume: volume * (rule.volumeScale ?? 1), ...rule.params ? { params: rule.params } : {} });
|
|
16103
|
+
},
|
|
16104
|
+
rule: (key) => rules[key] ?? void 0,
|
|
16105
|
+
burning: () => burning,
|
|
16106
|
+
dispose() {
|
|
16107
|
+
loop?.stop();
|
|
16108
|
+
loop = void 0;
|
|
16109
|
+
burning = 0;
|
|
16110
|
+
}
|
|
16111
|
+
};
|
|
16112
|
+
}
|
|
14447
16113
|
|
|
14448
16114
|
// ../game-weapons/dist/index.js
|
|
14449
16115
|
var __defProp4 = Object.defineProperty;
|
|
14450
16116
|
var __defNormalProp3 = (obj, key, value) => key in obj ? __defProp4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14451
16117
|
var __publicField3 = (obj, key, value) => __defNormalProp3(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
14452
|
-
var
|
|
16118
|
+
var createRng4 = (seed) => {
|
|
14453
16119
|
let a = seed >>> 0;
|
|
14454
16120
|
return () => {
|
|
14455
16121
|
a = a + 1831565813 >>> 0;
|
|
@@ -26598,9 +28264,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
26598
28264
|
this.caliber = init.caliber;
|
|
26599
28265
|
this.action = init.def.action;
|
|
26600
28266
|
this.rules = init.rules;
|
|
26601
|
-
this.rng =
|
|
26602
|
-
this.faultRng =
|
|
26603
|
-
this.fuseRng =
|
|
28267
|
+
this.rng = createRng4(init.seed);
|
|
28268
|
+
this.faultRng = createRng4((init.seed ^ FAULT_RNG_SALT) >>> 0);
|
|
28269
|
+
this.fuseRng = createRng4((init.seed ^ FUSE_RNG_SALT) >>> 0);
|
|
26604
28270
|
this.explosive = init.explosive ?? null;
|
|
26605
28271
|
this._safety = init.rules.startSafe;
|
|
26606
28272
|
this._condition = findById(init.rules.conditions, init.condition ?? init.rules.defaultCondition, "condition");
|
|
@@ -26862,7 +28528,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
26862
28528
|
this.isCompatible = init.isCompatible;
|
|
26863
28529
|
this.makeUnderbarrel = init.makeUnderbarrel;
|
|
26864
28530
|
this.seed = init.seed;
|
|
26865
|
-
this.breakRng =
|
|
28531
|
+
this.breakRng = createRng4((init.seed ^ BREAK_RNG_SALT) >>> 0);
|
|
26866
28532
|
this.fitted = (init.snapshot?.attachments ?? init.def.fitted ?? []).map(copyOf);
|
|
26867
28533
|
if (init.snapshot) this.stockState = init.snapshot.stock;
|
|
26868
28534
|
this.fitted = this.fitted.map((part) => part.shotsLeft === void 0 ? this.withDurability(part) : part);
|
|
@@ -27121,10 +28787,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27121
28787
|
__publicField3(this, "lastRoundAt", Number.NEGATIVE_INFINITY);
|
|
27122
28788
|
}
|
|
27123
28789
|
/** When the barrels of a rotary gun are at speed: `upSeconds` after the pull began, and at once while they still spin from the last round. */
|
|
27124
|
-
spunUpAt(spin, start,
|
|
28790
|
+
spunUpAt(spin, start, end3) {
|
|
27125
28791
|
if (!spin || start - this.lastRoundAt <= spin.holdSeconds + EPS3) return 0;
|
|
27126
28792
|
if (this.spinSince === void 0 || start - this.spinFrameEnd > SPIN_PULL_GAP) this.spinSince = start;
|
|
27127
|
-
this.spinFrameEnd =
|
|
28793
|
+
this.spinFrameEnd = end3;
|
|
27128
28794
|
return this.spinSince + spin.upSeconds;
|
|
27129
28795
|
}
|
|
27130
28796
|
oncePerPull(held) {
|
|
@@ -27133,7 +28799,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27133
28799
|
this.latched = true;
|
|
27134
28800
|
return true;
|
|
27135
28801
|
}
|
|
27136
|
-
fireFirearm(a, origin, dir, start,
|
|
28802
|
+
fireFirearm(a, origin, dir, start, end3, held, source) {
|
|
27137
28803
|
if (this._reloading) {
|
|
27138
28804
|
if (this.loadStep !== null && this._ammo > 0) this.stopReload(start);
|
|
27139
28805
|
else return result("reloading");
|
|
@@ -27143,10 +28809,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27143
28809
|
this.emit({ type: "empty", at: start });
|
|
27144
28810
|
return result("empty");
|
|
27145
28811
|
}
|
|
27146
|
-
const t0 = Math.max(start, this.readyAt, this.spunUpAt(a.spin, start,
|
|
27147
|
-
if (t0 >
|
|
28812
|
+
const t0 = Math.max(start, this.readyAt, this.spunUpAt(a.spin, start, end3));
|
|
28813
|
+
if (t0 > end3 + EPS3) return result("cooldown");
|
|
27148
28814
|
const res = result("ok");
|
|
27149
|
-
const times = planRounds(a, this.mode, this._ammo, t0,
|
|
28815
|
+
const times = planRounds(a, this.mode, this._ammo, t0, end3);
|
|
27150
28816
|
let lastAt = t0;
|
|
27151
28817
|
for (const t of times) {
|
|
27152
28818
|
const fault = rollFault({ def: this.def, caliber: this.caliber, condition: this._condition, ammoQuality: this._ammoQuality, heat: this._heat, rules: this.faultRules(), random: this.faultRng });
|
|
@@ -27262,7 +28928,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27262
28928
|
this.pend = { src: "trigger", amt: d };
|
|
27263
28929
|
}
|
|
27264
28930
|
const start = this.clock - overlap;
|
|
27265
|
-
const
|
|
28931
|
+
const end3 = start + d;
|
|
27266
28932
|
this.settle(start);
|
|
27267
28933
|
const dir = normalize2(direction);
|
|
27268
28934
|
let res;
|
|
@@ -27273,10 +28939,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27273
28939
|
else if (launcher) res = launcher.trigger(origin, dir, d, held, source);
|
|
27274
28940
|
else switch (a.kind) {
|
|
27275
28941
|
case "firearm":
|
|
27276
|
-
res = this.fireFirearm(a, origin, dir, start,
|
|
28942
|
+
res = this.fireFirearm(a, origin, dir, start, end3, held, source);
|
|
27277
28943
|
break;
|
|
27278
28944
|
case "melee":
|
|
27279
|
-
res = this.fireMelee(a, origin, dir, start,
|
|
28945
|
+
res = this.fireMelee(a, origin, dir, start, end3);
|
|
27280
28946
|
break;
|
|
27281
28947
|
case "flame":
|
|
27282
28948
|
case "heat":
|
|
@@ -27286,7 +28952,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27286
28952
|
res = this.fireExtinguisher(a, origin, dir, d);
|
|
27287
28953
|
break;
|
|
27288
28954
|
case "throwable":
|
|
27289
|
-
res = this.fireThrowable(a, origin, dir, held, start,
|
|
28955
|
+
res = this.fireThrowable(a, origin, dir, held, start, end3);
|
|
27290
28956
|
break;
|
|
27291
28957
|
case "launcher":
|
|
27292
28958
|
res = this.fireLauncher(a, origin, dir, start, held);
|
|
@@ -27298,7 +28964,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27298
28964
|
res = this.oncePerPull(held) ? result("ok", { uses: [{ kind: "hand", reach: a.reach, origin, direction: dir }] }) : result("cooldown");
|
|
27299
28965
|
break;
|
|
27300
28966
|
}
|
|
27301
|
-
this.advance(Math.max(0,
|
|
28967
|
+
this.advance(Math.max(0, end3 - this.clock));
|
|
27302
28968
|
this.settle(this.clock);
|
|
27303
28969
|
return res;
|
|
27304
28970
|
}
|
|
@@ -27330,9 +28996,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27330
28996
|
this.emit({ type: "shot", shots: 0, rounds: 1, mode: this.mode, at: t });
|
|
27331
28997
|
return result("ok", { uses: launcherUses(a, this.explosive, origin, dir), recoil: [a.recoil[0], a.recoil[1]], noise: a.noise ?? 0, rounds: 1, roundTimes: [t - start] });
|
|
27332
28998
|
}
|
|
27333
|
-
fireMelee(a, origin, dir, start,
|
|
28999
|
+
fireMelee(a, origin, dir, start, end3) {
|
|
27334
29000
|
const t0 = Math.max(start, this.readyAt);
|
|
27335
|
-
if (t0 >
|
|
29001
|
+
if (t0 > end3 + EPS3) return result("cooldown");
|
|
27336
29002
|
this.readyAt = t0 + 60 / a.rpm;
|
|
27337
29003
|
const use = { kind: "melee", damage: a.damage, energy: a.energy, radius: a.radius, reach: a.reach, origin, direction: dir };
|
|
27338
29004
|
return result("ok", { uses: [use], noise: a.noise });
|
|
@@ -27366,8 +29032,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27366
29032
|
return result("ok", { uses: [throwableUse({ action: a, explosive: this.explosive, origin, direction: dir, fuse, rules: this.rules.explosives, ...extra })], noise: a.noise });
|
|
27367
29033
|
}
|
|
27368
29034
|
/** A timer throwable is cooked from the pull; impact, remote and wire ones (and a long fuse that sets down) act at once. */
|
|
27369
|
-
fireThrowable(a, origin, dir, held, start,
|
|
27370
|
-
if (this.cook) return this.cookStep(a, origin, dir,
|
|
29035
|
+
fireThrowable(a, origin, dir, held, start, end3) {
|
|
29036
|
+
if (this.cook) return this.cookStep(a, origin, dir, end3);
|
|
27371
29037
|
if (!this.oncePerPull(held)) return result("cooldown");
|
|
27372
29038
|
if (this._ammo <= 0) return result("empty");
|
|
27373
29039
|
const fuse = this.sampleThrowFuse(a);
|
|
@@ -27377,7 +29043,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27377
29043
|
}
|
|
27378
29044
|
this.cook = { start, fuse, origin, direction: dir };
|
|
27379
29045
|
this.emit({ type: "cook", phase: "start", fuse, at: start });
|
|
27380
|
-
return this.cookStep(a, origin, dir,
|
|
29046
|
+
return this.cookStep(a, origin, dir, end3);
|
|
27381
29047
|
}
|
|
27382
29048
|
/** The trigger stays down on a cooked throwable: nothing happens until the fuse runs out in the hand. */
|
|
27383
29049
|
cookStep(a, origin, dir, now) {
|
|
@@ -29734,17 +31400,17 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
29734
31400
|
}
|
|
29735
31401
|
}
|
|
29736
31402
|
const start = time;
|
|
29737
|
-
const
|
|
31403
|
+
const end3 = Math.min(def.duration, time + Math.max(0, dt));
|
|
29738
31404
|
for (let i = 0; i < keys.length && !done; i++) {
|
|
29739
31405
|
const key = keys[i];
|
|
29740
31406
|
const spanStart = key.t;
|
|
29741
31407
|
const spanEnd = i + 1 < keys.length ? keys[i + 1].t : def.duration;
|
|
29742
|
-
if ((key.action === "open" || key.action === "close") && !fired.has(i) &&
|
|
31408
|
+
if ((key.action === "open" || key.action === "close") && !fired.has(i) && end3 >= spanStart) {
|
|
29743
31409
|
fired.add(i);
|
|
29744
31410
|
if (key.action === "open") world.open(containerId);
|
|
29745
31411
|
else world.close(containerId);
|
|
29746
31412
|
}
|
|
29747
|
-
const overlap = Math.min(
|
|
31413
|
+
const overlap = Math.min(end3, spanEnd) - Math.max(start, spanStart);
|
|
29748
31414
|
if (overlap <= 0) continue;
|
|
29749
31415
|
if (key.action === "drink") {
|
|
29750
31416
|
const ev = world.drink(containerId, (key.rate ?? DEFAULT_DRINK_RATE) * overlap);
|
|
@@ -29758,7 +31424,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
29758
31424
|
}
|
|
29759
31425
|
}
|
|
29760
31426
|
if (!done) {
|
|
29761
|
-
time =
|
|
31427
|
+
time = end3;
|
|
29762
31428
|
done = time >= def.duration;
|
|
29763
31429
|
}
|
|
29764
31430
|
follow(dt);
|
|
@@ -30360,8 +32026,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
30360
32026
|
bedHeight = roadY * 1e3 + z * 14 - (x - params.roadSlopeAnchorX) * 5;
|
|
30361
32027
|
roughness = 0.55;
|
|
30362
32028
|
}
|
|
30363
|
-
const
|
|
30364
|
-
bed[index] = bedHeight + (
|
|
32029
|
+
const noise2 = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
|
|
32030
|
+
bed[index] = bedHeight + (noise2 - 0.5) * roughness * 2;
|
|
30365
32031
|
vertical[index] = region === 0 || region === 2 ? 0.95 : 1;
|
|
30366
32032
|
pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, row * 0.21) * 0.5 + hashCell(column, row) * 0.5) * (region === 3 ? 1.25 : 1);
|
|
30367
32033
|
}
|
|
@@ -30518,8 +32184,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
30518
32184
|
for (let column = 0; column < columns; column++) {
|
|
30519
32185
|
const index = (region.firstRow + row) * columns + column;
|
|
30520
32186
|
const centre = add2(add2(region.origin, scale(columnAxis, (column + 0.5) * cell)), scale(region.rowAxis, (row + 0.5) * cell));
|
|
30521
|
-
const
|
|
30522
|
-
bed[index] = centre[1] * 1e3 + (
|
|
32187
|
+
const noise2 = valueNoise(column * 0.35, (region.firstRow + row) * 0.35) * 0.6 + valueNoise(column * 1.3, (region.firstRow + row) * 1.3) * 0.4;
|
|
32188
|
+
bed[index] = centre[1] * 1e3 + (noise2 - 0.5) * strip.roughness * 2;
|
|
30523
32189
|
vertical[index] = region.isVertical ? 0.95 : 1;
|
|
30524
32190
|
pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, (region.firstRow + row) * 0.21) * 0.5 + valueNoise(column * 0.9, row * 0.9) * 0.5);
|
|
30525
32191
|
}
|
|
@@ -31432,8 +33098,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
31432
33098
|
const b2x = ty * b1z - tz * b1y;
|
|
31433
33099
|
const b2y = tz * b1x - tx * b1z;
|
|
31434
33100
|
const b2z = tx * b1y - ty * b1x;
|
|
31435
|
-
const
|
|
31436
|
-
const r = this.radiusAt(state, gravity, t, r0, speed0) *
|
|
33101
|
+
const end3 = Math.min(1, 0.35 + Math.min(k, 1 - k) * 8);
|
|
33102
|
+
const r = this.radiusAt(state, gravity, t, r0, speed0) * end3;
|
|
31437
33103
|
for (let j = 0; j < sides; j++) {
|
|
31438
33104
|
const a = j / sides * Math.PI * 2;
|
|
31439
33105
|
const c = Math.cos(a);
|
|
@@ -31467,15 +33133,15 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
31467
33133
|
};
|
|
31468
33134
|
var rendererCount2 = 0;
|
|
31469
33135
|
function createFluidRenderer(THREE, options) {
|
|
31470
|
-
const { scene, fluids } = options;
|
|
33136
|
+
const { scene: scene2, fluids } = options;
|
|
31471
33137
|
const quality = options.quality ?? "medium";
|
|
31472
33138
|
const profile = profileOf(quality);
|
|
31473
33139
|
const materials = new MaterialCache(THREE, options.envMap ?? null);
|
|
31474
33140
|
const root = new THREE.Group();
|
|
31475
33141
|
root.name = "fluid_renderer";
|
|
31476
|
-
|
|
33142
|
+
scene2.add(root);
|
|
31477
33143
|
const vfx = options.vfx ?? createVfx({ profile: quality, gravity: fluids.gravity, sprites: { capacity: profile.maxParticles } });
|
|
31478
|
-
const ownVfx = options.vfx ? void 0 : createVfxRenderer(THREE, { scene, camera: options.camera ?? new THREE.PerspectiveCamera(), vfx, pointScale: options.pointScale ?? 600 });
|
|
33144
|
+
const ownVfx = options.vfx ? void 0 : createVfxRenderer(THREE, { scene: scene2, camera: options.camera ?? new THREE.PerspectiveCamera(), vfx, pointScale: options.pointScale ?? 600 });
|
|
31479
33145
|
const particles = new FluidSprites(vfx, `fluids-${++rendererCount2}`);
|
|
31480
33146
|
const dropGeometry = new THREE.SphereGeometry(1, 8, 6);
|
|
31481
33147
|
const bodies = /* @__PURE__ */ new Map();
|
|
@@ -31612,7 +33278,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
31612
33278
|
ownVfx?.dispose();
|
|
31613
33279
|
dropGeometry.dispose();
|
|
31614
33280
|
materials.dispose();
|
|
31615
|
-
|
|
33281
|
+
scene2.remove(root);
|
|
31616
33282
|
root.clear();
|
|
31617
33283
|
},
|
|
31618
33284
|
stats() {
|
|
@@ -33454,10 +35120,10 @@ void main(){
|
|
|
33454
35120
|
}
|
|
33455
35121
|
return { name: "shelf", parts, joints, tags: ["furniture"] };
|
|
33456
35122
|
};
|
|
33457
|
-
function createArena(THREE, size,
|
|
35123
|
+
function createArena(THREE, size, scene2) {
|
|
33458
35124
|
const group = new THREE.Group();
|
|
33459
35125
|
group.name = "test-room-arena";
|
|
33460
|
-
|
|
35126
|
+
scene2.background = new THREE.Color(10466504);
|
|
33461
35127
|
group.add(new THREE.HemisphereLight(13624319, 7037527, 0.75));
|
|
33462
35128
|
const sun = new THREE.DirectionalLight(16777215, 2.1);
|
|
33463
35129
|
sun.position.set(7, 13, 8);
|
|
@@ -33732,7 +35398,7 @@ void main(){
|
|
|
33732
35398
|
var CONE_BLAST_RADIUS = 0.5;
|
|
33733
35399
|
var toArray = (vector) => [vector.x, vector.y, vector.z];
|
|
33734
35400
|
function createExplosives(context) {
|
|
33735
|
-
const { THREE, scene, camera: camera2, world, renderer, arsenal, translate, format, action } = context;
|
|
35401
|
+
const { THREE, scene: scene2, camera: camera2, world, renderer, arsenal, translate, format, action } = context;
|
|
33736
35402
|
const projectiles = [];
|
|
33737
35403
|
const meshes = [];
|
|
33738
35404
|
const up = new THREE.Vector3(0, 1, 0);
|
|
@@ -33745,7 +35411,7 @@ void main(){
|
|
|
33745
35411
|
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color, roughness: 0.6, metalness }));
|
|
33746
35412
|
mesh.castShadow = true;
|
|
33747
35413
|
mesh.position.set(at3[0], at3[1], at3[2]);
|
|
33748
|
-
|
|
35414
|
+
scene2.add(mesh);
|
|
33749
35415
|
meshes.push(mesh);
|
|
33750
35416
|
return mesh;
|
|
33751
35417
|
};
|
|
@@ -33800,7 +35466,7 @@ void main(){
|
|
|
33800
35466
|
flag.position.y = 0.46;
|
|
33801
35467
|
marker.add(stake, flag);
|
|
33802
35468
|
marker.position.set(at3[0] + 0.12, at3[1] - 0.03, at3[2]);
|
|
33803
|
-
|
|
35469
|
+
scene2.add(marker);
|
|
33804
35470
|
meshes.push(marker);
|
|
33805
35471
|
return marker;
|
|
33806
35472
|
};
|
|
@@ -33808,7 +35474,7 @@ void main(){
|
|
|
33808
35474
|
const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(...from), new THREE.Vector3(...to)]);
|
|
33809
35475
|
const line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 15262920 }));
|
|
33810
35476
|
line.name = "charge_wire";
|
|
33811
|
-
|
|
35477
|
+
scene2.add(line);
|
|
33812
35478
|
meshes.push(line);
|
|
33813
35479
|
return line;
|
|
33814
35480
|
};
|
|
@@ -34483,13 +36149,13 @@ void main(){
|
|
|
34483
36149
|
if (!g || g.done) break;
|
|
34484
36150
|
g.done = true;
|
|
34485
36151
|
const [a, b] = ev.parts;
|
|
34486
|
-
const
|
|
36152
|
+
const many2 = g.list.length > 1;
|
|
34487
36153
|
c.log.add({
|
|
34488
36154
|
channel: "event",
|
|
34489
36155
|
type: "detach",
|
|
34490
36156
|
objectId: ev.object,
|
|
34491
36157
|
itemId: c.itemIdOf(ev.object),
|
|
34492
|
-
text:
|
|
36158
|
+
text: many2 ? t(`ev.detach.many.${ev.by === "tool" ? "tool" : "break"}`, { obj: c.nameOf(ev.object), joints: joints(g.list.length), pieces: pieces(g.groups) }) : t("ev.detach", { obj: c.nameOf(ev.object), a, b, by: t(`by.${ev.by}`), pieces: pieces(g.groups) }),
|
|
34493
36159
|
data: { joint: ev.joint, joints: g.list.map((j) => j.joint), parts: ev.parts, by: ev.by, groups: g.groups }
|
|
34494
36160
|
});
|
|
34495
36161
|
break;
|
|
@@ -35490,23 +37156,23 @@ void main(){
|
|
|
35490
37156
|
}
|
|
35491
37157
|
if (sheet.timeline) {
|
|
35492
37158
|
const { segments, ticks, metric } = sheet.timeline;
|
|
35493
|
-
const
|
|
37159
|
+
const end3 = Math.max(1e-3, ...segments.map((segment) => segment.end), ...ticks);
|
|
35494
37160
|
const strip = mk("div", "tr-timeline");
|
|
35495
37161
|
for (const segment of segments) {
|
|
35496
37162
|
const bar2 = mk("div", "tr-segment", segment.name);
|
|
35497
|
-
bar2.style.left = `${segment.start /
|
|
35498
|
-
bar2.style.width = `${(segment.end - segment.start) /
|
|
37163
|
+
bar2.style.left = `${segment.start / end3 * 100}%`;
|
|
37164
|
+
bar2.style.width = `${(segment.end - segment.start) / end3 * 100}%`;
|
|
35499
37165
|
bar2.style.background = segment.color;
|
|
35500
37166
|
strip.appendChild(bar2);
|
|
35501
37167
|
}
|
|
35502
37168
|
for (const tick of ticks) {
|
|
35503
37169
|
const mark = mk("u", "tr-tick");
|
|
35504
|
-
mark.style.left = `${tick /
|
|
37170
|
+
mark.style.left = `${tick / end3 * 100}%`;
|
|
35505
37171
|
strip.appendChild(mark);
|
|
35506
37172
|
}
|
|
35507
37173
|
const head = mk("s", "tr-playhead");
|
|
35508
37174
|
strip.appendChild(head);
|
|
35509
|
-
playhead = { node: head, metric, end };
|
|
37175
|
+
playhead = { node: head, metric, end: end3 };
|
|
35510
37176
|
card.appendChild(strip);
|
|
35511
37177
|
}
|
|
35512
37178
|
if (sheet.note) card.appendChild(mk("p", "sub", sheet.note));
|
|
@@ -36187,9 +37853,9 @@ void main(){
|
|
|
36187
37853
|
gl.setSize(width(), height());
|
|
36188
37854
|
const dom = gl.domElement;
|
|
36189
37855
|
if (dom && !dom.hasAttribute?.("tabindex")) dom.setAttribute?.("tabindex", "0");
|
|
36190
|
-
const
|
|
37856
|
+
const scene2 = options.scene ?? new THREE.Scene();
|
|
36191
37857
|
const camera2 = options.camera ?? new THREE.PerspectiveCamera(72, width() / height(), 0.05, 140);
|
|
36192
|
-
if (!options.camera)
|
|
37858
|
+
if (!options.camera) scene2.add(camera2);
|
|
36193
37859
|
const resize = () => {
|
|
36194
37860
|
const w = width();
|
|
36195
37861
|
const h = height();
|
|
@@ -36200,8 +37866,8 @@ void main(){
|
|
|
36200
37866
|
const onResize = () => resize();
|
|
36201
37867
|
window.addEventListener("resize", onResize);
|
|
36202
37868
|
const arenaSize = options.arenaSize ?? 14;
|
|
36203
|
-
const arena = createArena(THREE, arenaSize,
|
|
36204
|
-
|
|
37869
|
+
const arena = createArena(THREE, arenaSize, scene2);
|
|
37870
|
+
scene2.add(arena.group);
|
|
36205
37871
|
const ownWorld = !options.world;
|
|
36206
37872
|
const world = options.world ?? createDestruction({ seed: 1, ...options.worldOptions, materials: [...DEMO_MATERIALS, ...options.worldOptions?.materials ?? []] });
|
|
36207
37873
|
const pristineWorld = ownWorld ? world.serialize() : null;
|
|
@@ -36229,15 +37895,22 @@ void main(){
|
|
|
36229
37895
|
let simTime = 0;
|
|
36230
37896
|
const log = createLog({ exportCancelled: () => t("log.export_cancelled") });
|
|
36231
37897
|
log.setClock(() => simTime);
|
|
37898
|
+
const ownAudio = options.audio === void 0;
|
|
37899
|
+
const audio = options.audio === false ? null : options.audio ?? createAudioEngine();
|
|
37900
|
+
const destructionSound = audio ? createDestructionSound(audio) : null;
|
|
37901
|
+
const playSound = (key, point, volume) => {
|
|
37902
|
+
destructionSound?.onSound(key, point, volume);
|
|
37903
|
+
options.onSound?.(key, point, volume);
|
|
37904
|
+
};
|
|
36232
37905
|
const craterField = options.craters ? createGroundField() : void 0;
|
|
36233
37906
|
const craterPatch = craterField ? createGroundPatch(THREE, { field: craterField, material: new THREE.MeshStandardMaterial({ color: 9342602, roughness: 0.95 }) }) : void 0;
|
|
36234
37907
|
if (craterPatch) {
|
|
36235
37908
|
const floor2 = arena.group.getObjectByName("floor");
|
|
36236
37909
|
if (floor2) floor2.visible = false;
|
|
36237
|
-
|
|
37910
|
+
scene2.add(craterPatch.mesh);
|
|
36238
37911
|
}
|
|
36239
37912
|
const vfx = createVfxRenderer(THREE, {
|
|
36240
|
-
scene,
|
|
37913
|
+
scene: scene2,
|
|
36241
37914
|
camera: camera2,
|
|
36242
37915
|
overlay: container,
|
|
36243
37916
|
quality,
|
|
@@ -36249,13 +37922,13 @@ void main(){
|
|
|
36249
37922
|
})
|
|
36250
37923
|
});
|
|
36251
37924
|
const renderer = createDestructionRenderer(THREE, {
|
|
36252
|
-
scene,
|
|
37925
|
+
scene: scene2,
|
|
36253
37926
|
world,
|
|
36254
37927
|
vfx: vfx.world,
|
|
36255
37928
|
quality,
|
|
36256
37929
|
groundAt,
|
|
36257
37930
|
pointScale,
|
|
36258
|
-
onSound:
|
|
37931
|
+
onSound: playSound,
|
|
36259
37932
|
onPiece: (piece) => {
|
|
36260
37933
|
rendererPieces.set(piece.objectId, piece);
|
|
36261
37934
|
loose.push(piece);
|
|
@@ -36277,10 +37950,10 @@ void main(){
|
|
|
36277
37950
|
if (best) splitOf.set(piece.objectId, best);
|
|
36278
37951
|
}
|
|
36279
37952
|
});
|
|
36280
|
-
const fluidView = createFluidRenderer(THREE, { scene, fluids, quality, pointScale, vfx: vfx.world });
|
|
37953
|
+
const fluidView = createFluidRenderer(THREE, { scene: scene2, fluids, quality, pointScale, vfx: vfx.world });
|
|
36281
37954
|
const filmRandom = createFilmLcg(1);
|
|
36282
37955
|
const film = createFilmManager(THREE, {
|
|
36283
|
-
scene,
|
|
37956
|
+
scene: scene2,
|
|
36284
37957
|
camera: camera2,
|
|
36285
37958
|
random: filmRandom,
|
|
36286
37959
|
fluids,
|
|
@@ -36477,7 +38150,7 @@ void main(){
|
|
|
36477
38150
|
for (const o of arena.objects) {
|
|
36478
38151
|
const root = buildFromSpec(THREE, o.spec, { colors: o.colors });
|
|
36479
38152
|
root.position.set(o.position[0], o.position[1], o.position[2]);
|
|
36480
|
-
|
|
38153
|
+
scene2.add(root);
|
|
36481
38154
|
root.updateMatrixWorld(true);
|
|
36482
38155
|
world.add(structuredCloneSpec(o.spec), { id: o.id, position: o.position });
|
|
36483
38156
|
registerEntry(o.id, o.id, root, o.spec, S.text[`arena.${o.id}`] ?? o.id, void 0, void 0, true, false);
|
|
@@ -36504,7 +38177,7 @@ void main(){
|
|
|
36504
38177
|
root.position.set(pos[0], pos[1], pos[2]);
|
|
36505
38178
|
root.rotation.y = rotationY ?? player.yaw;
|
|
36506
38179
|
root.name = item.id;
|
|
36507
|
-
|
|
38180
|
+
scene2.add(root);
|
|
36508
38181
|
root.updateMatrixWorld(true);
|
|
36509
38182
|
const objectId = `${item.id}_${++counter}`;
|
|
36510
38183
|
let own;
|
|
@@ -36691,7 +38364,7 @@ void main(){
|
|
|
36691
38364
|
g.setAttribute("position", new THREE.Float32BufferAttribute(6, 3));
|
|
36692
38365
|
const line = new THREE.Line(g, tracerMat);
|
|
36693
38366
|
line.frustumCulled = false;
|
|
36694
|
-
|
|
38367
|
+
scene2.add(line);
|
|
36695
38368
|
tr = { line, life: 0 };
|
|
36696
38369
|
tracers.push(tr);
|
|
36697
38370
|
}
|
|
@@ -36807,15 +38480,15 @@ void main(){
|
|
|
36807
38480
|
energy = Math.max(energy, s.energy);
|
|
36808
38481
|
const results = world.shoot(s.origin, s.direction, s.energy, s.caliber, "player");
|
|
36809
38482
|
const r0 = results.find((r) => r.part && r.point);
|
|
36810
|
-
let
|
|
38483
|
+
let end3 = [s.origin[0] + s.direction[0] * 40, s.origin[1] + s.direction[1] * 40, s.origin[2] + s.direction[2] * 40];
|
|
36811
38484
|
if (r0?.point && r0.part) {
|
|
36812
38485
|
hits++;
|
|
36813
|
-
|
|
38486
|
+
end3 = r0.point;
|
|
36814
38487
|
if (!first) first = { object: r0.object, part: r0.part, point: r0.point, normal: [0, 1, 0], distance: dist3(s.origin, r0.point) };
|
|
36815
38488
|
const speed = Math.max(80, s.speed);
|
|
36816
38489
|
pushPiece(r0.object, s.direction, 2 * s.energy / speed, r0.point);
|
|
36817
38490
|
}
|
|
36818
|
-
if (shots.length <= 3 || rnd2() < 0.34) tracer(m,
|
|
38491
|
+
if (shots.length <= 3 || rnd2() < 0.34) tracer(m, end3);
|
|
36819
38492
|
}
|
|
36820
38493
|
});
|
|
36821
38494
|
player.kick(recoil[0], recoil[1]);
|
|
@@ -36981,7 +38654,7 @@ void main(){
|
|
|
36981
38654
|
}
|
|
36982
38655
|
const explosives = createExplosives({
|
|
36983
38656
|
THREE,
|
|
36984
|
-
scene,
|
|
38657
|
+
scene: scene2,
|
|
36985
38658
|
camera: camera2,
|
|
36986
38659
|
world,
|
|
36987
38660
|
renderer,
|
|
@@ -37478,6 +39151,10 @@ void main(){
|
|
|
37478
39151
|
s.update(dt);
|
|
37479
39152
|
flushEvents(id, s);
|
|
37480
39153
|
}
|
|
39154
|
+
if (audio) {
|
|
39155
|
+
const look = camera2.getWorldDirection(listenerDirection);
|
|
39156
|
+
audio.setListener({ position: [camera2.position.x, camera2.position.y, camera2.position.z], yaw: Math.atan2(-look.x, -look.z) });
|
|
39157
|
+
}
|
|
37481
39158
|
for (const update2 of frameListeners) update2(dt);
|
|
37482
39159
|
for (const tr of tracers) {
|
|
37483
39160
|
if (tr.life > 0) {
|
|
@@ -37587,7 +39264,7 @@ void main(){
|
|
|
37587
39264
|
const ev = evs[pl.fired++];
|
|
37588
39265
|
action("animation_event", t("act.anim_event", { name: pl.entry.name, clip: pl.clip.id, type: ev.type }), { objectId: pl.entry.so.objectId, itemId: pl.entry.so.itemId, data: { clip: pl.clip.id, type: ev.type, at: ev.t, ...ev.data ?? {} } });
|
|
37589
39266
|
const key = ev.type === "sound" ? ev.data?.key : void 0;
|
|
37590
|
-
if (typeof key === "string")
|
|
39267
|
+
if (typeof key === "string") playSound(key, v3(pl.entry.so.root.getWorldPosition(tmpV2)), 1);
|
|
37591
39268
|
}
|
|
37592
39269
|
if (pl.t >= pl.clip.duration) {
|
|
37593
39270
|
playing.splice(i, 1);
|
|
@@ -37680,6 +39357,9 @@ void main(){
|
|
|
37680
39357
|
};
|
|
37681
39358
|
const hostListeners = /* @__PURE__ */ new Set();
|
|
37682
39359
|
const frameListeners = /* @__PURE__ */ new Set();
|
|
39360
|
+
const listenerDirection = new THREE.Vector3();
|
|
39361
|
+
const environment = options.environment ?? createEnvironmentBus();
|
|
39362
|
+
const audioSink = audio ? createAudioSink(audio) : void 0;
|
|
37683
39363
|
function emitHost(event) {
|
|
37684
39364
|
for (const listener of hostListeners) listener(event);
|
|
37685
39365
|
}
|
|
@@ -37726,9 +39406,11 @@ void main(){
|
|
|
37726
39406
|
}
|
|
37727
39407
|
const host = {
|
|
37728
39408
|
THREE,
|
|
37729
|
-
scene,
|
|
39409
|
+
scene: scene2,
|
|
37730
39410
|
camera: camera2,
|
|
37731
39411
|
locale,
|
|
39412
|
+
...audioSink ? { audio: audioSink } : {},
|
|
39413
|
+
environment,
|
|
37732
39414
|
groundAt,
|
|
37733
39415
|
effects: vfx,
|
|
37734
39416
|
tool: () => ({
|
|
@@ -37977,11 +39659,13 @@ void main(){
|
|
|
37977
39659
|
const room = {
|
|
37978
39660
|
world,
|
|
37979
39661
|
renderer,
|
|
37980
|
-
scene,
|
|
39662
|
+
scene: scene2,
|
|
37981
39663
|
camera: camera2,
|
|
37982
39664
|
arsenal,
|
|
37983
39665
|
fluids,
|
|
37984
39666
|
fluidRenderer: fluidView,
|
|
39667
|
+
audio,
|
|
39668
|
+
environment,
|
|
37985
39669
|
log,
|
|
37986
39670
|
get tool() {
|
|
37987
39671
|
return tool;
|
|
@@ -38075,6 +39759,8 @@ void main(){
|
|
|
38075
39759
|
return { room: roomMetrics.sample(), module: moduleMetrics() };
|
|
38076
39760
|
},
|
|
38077
39761
|
dispose() {
|
|
39762
|
+
destructionSound?.dispose();
|
|
39763
|
+
if (ownAudio) audio?.dispose();
|
|
38078
39764
|
for (const detach of detachers.values()) detach();
|
|
38079
39765
|
detachers.clear();
|
|
38080
39766
|
for (const store of benches.values()) store.dispose();
|
|
@@ -38179,7 +39865,7 @@ void main(){
|
|
|
38179
39865
|
last = now;
|
|
38180
39866
|
if (!scenarioRunning) update(dt);
|
|
38181
39867
|
const restoreCamera = vfx.applyCamera();
|
|
38182
|
-
gl.render(
|
|
39868
|
+
gl.render(scene2, camera2);
|
|
38183
39869
|
restoreCamera();
|
|
38184
39870
|
roomMetrics.rendered();
|
|
38185
39871
|
};
|