@aura3d/audio 3.0.1 → 4.0.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.
Files changed (67) hide show
  1. package/dist/AudioSource.d.ts +7 -0
  2. package/dist/AudioSource.d.ts.map +1 -1
  3. package/dist/AudioSource.js +21 -0
  4. package/dist/AudioSource.js.map +1 -1
  5. package/dist/PositionalEmitter.js +1 -1
  6. package/dist/PositionalEmitter.js.map +1 -1
  7. package/dist/contracts/gameSound.d.ts +94 -0
  8. package/dist/contracts/gameSound.d.ts.map +1 -0
  9. package/dist/contracts/gameSound.js +63 -0
  10. package/dist/contracts/gameSound.js.map +1 -0
  11. package/dist/game-sound/EngineLoop.d.ts +44 -0
  12. package/dist/game-sound/EngineLoop.d.ts.map +1 -0
  13. package/dist/game-sound/EngineLoop.js +114 -0
  14. package/dist/game-sound/EngineLoop.js.map +1 -0
  15. package/dist/game-sound/GameSoundEngine.d.ts +129 -0
  16. package/dist/game-sound/GameSoundEngine.d.ts.map +1 -0
  17. package/dist/game-sound/GameSoundEngine.js +563 -0
  18. package/dist/game-sound/GameSoundEngine.js.map +1 -0
  19. package/dist/game-sound/LoopHandle.d.ts +30 -0
  20. package/dist/game-sound/LoopHandle.d.ts.map +1 -0
  21. package/dist/game-sound/LoopHandle.js +78 -0
  22. package/dist/game-sound/LoopHandle.js.map +1 -0
  23. package/dist/game-sound/MasterChain.d.ts +57 -0
  24. package/dist/game-sound/MasterChain.d.ts.map +1 -0
  25. package/dist/game-sound/MasterChain.js +144 -0
  26. package/dist/game-sound/MasterChain.js.map +1 -0
  27. package/dist/game-sound/MusicController.d.ts +59 -0
  28. package/dist/game-sound/MusicController.d.ts.map +1 -0
  29. package/dist/game-sound/MusicController.js +210 -0
  30. package/dist/game-sound/MusicController.js.map +1 -0
  31. package/dist/game-sound/ReverbSend.d.ts +30 -0
  32. package/dist/game-sound/ReverbSend.d.ts.map +1 -0
  33. package/dist/game-sound/ReverbSend.js +69 -0
  34. package/dist/game-sound/ReverbSend.js.map +1 -0
  35. package/dist/game-sound/SpatialVoice.d.ts +30 -0
  36. package/dist/game-sound/SpatialVoice.d.ts.map +1 -0
  37. package/dist/game-sound/SpatialVoice.js +115 -0
  38. package/dist/game-sound/SpatialVoice.js.map +1 -0
  39. package/dist/game-sound/Voice.d.ts +139 -0
  40. package/dist/game-sound/Voice.d.ts.map +1 -0
  41. package/dist/game-sound/Voice.js +319 -0
  42. package/dist/game-sound/Voice.js.map +1 -0
  43. package/dist/game-sound/formatProbe.d.ts +19 -0
  44. package/dist/game-sound/formatProbe.d.ts.map +1 -0
  45. package/dist/game-sound/formatProbe.js +36 -0
  46. package/dist/game-sound/formatProbe.js.map +1 -0
  47. package/dist/game-sound/listenerSync.d.ts +55 -0
  48. package/dist/game-sound/listenerSync.d.ts.map +1 -0
  49. package/dist/game-sound/listenerSync.js +37 -0
  50. package/dist/game-sound/listenerSync.js.map +1 -0
  51. package/dist/game-sound/loudness.d.ts +35 -0
  52. package/dist/game-sound/loudness.d.ts.map +1 -0
  53. package/dist/game-sound/loudness.js +135 -0
  54. package/dist/game-sound/loudness.js.map +1 -0
  55. package/dist/game-sound/rng.d.ts +26 -0
  56. package/dist/game-sound/rng.d.ts.map +1 -0
  57. package/dist/game-sound/rng.js +57 -0
  58. package/dist/game-sound/rng.js.map +1 -0
  59. package/dist/game-sound/types.d.ts +96 -0
  60. package/dist/game-sound/types.d.ts.map +1 -0
  61. package/dist/game-sound/types.js +19 -0
  62. package/dist/game-sound/types.js.map +1 -0
  63. package/dist/index.d.ts +16 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +13 -0
  66. package/dist/index.js.map +1 -1
  67. package/package.json +2 -2
@@ -0,0 +1,78 @@
1
+ /**
2
+ * game-sound/LoopHandle.ts — live loop control (§6.8).
3
+ *
4
+ * `setRate`/`setGain` use `AudioParam.setTargetAtTime` (τ = rampMs/3 per
5
+ * AudioSource.setPlaybackRate convention). The handle fronts a running
6
+ * `VoiceRecord` (or several, for engine layers — see EngineLoop.ts).
7
+ */
8
+ import { dbToGain } from "./types.js";
9
+ export class LoopHandleImpl {
10
+ ctx;
11
+ voice;
12
+ spatial;
13
+ stopVoice;
14
+ stopped = false;
15
+ constructor(ctx, voice, spatial, stopVoice) {
16
+ this.ctx = ctx;
17
+ this.voice = voice;
18
+ this.spatial = spatial;
19
+ this.stopVoice = stopVoice;
20
+ }
21
+ get id() {
22
+ return this.voice.id;
23
+ }
24
+ get ended() {
25
+ return this.stopped || this.voice.ended;
26
+ }
27
+ stop(fadeMs = 0) {
28
+ if (this.stopped)
29
+ return;
30
+ this.stopped = true;
31
+ const gain = this.voice.gain;
32
+ if (fadeMs > 0 && gain.gain?.setTargetAtTime) {
33
+ gain.gain.setTargetAtTime(0, this.ctx.currentTime, fadeMs / 3000);
34
+ const voice = this.voice;
35
+ const release = () => this.stopVoice(voice);
36
+ setTimeout(release, fadeMs + 30).unref?.();
37
+ }
38
+ else {
39
+ this.stopVoice(this.voice);
40
+ }
41
+ }
42
+ setPosition(p) {
43
+ this.spatial?.setPosition(p);
44
+ }
45
+ setPositionVelocity(p, v) {
46
+ this.spatial?.setPosition(p);
47
+ if (v)
48
+ this.spatial?.setVelocity(v);
49
+ }
50
+ setOcclusion(amount01) {
51
+ this.spatial?.setOcclusion(amount01);
52
+ }
53
+ setRate(rate, rampMs = 0) {
54
+ const param = this.voice.source.playbackRate;
55
+ if (!param)
56
+ return;
57
+ const next = Math.max(0.0625, rate);
58
+ if (rampMs > 0 && param.setTargetAtTime) {
59
+ param.setTargetAtTime(next, this.ctx.currentTime, rampMs / 3000);
60
+ }
61
+ else {
62
+ param.value = next;
63
+ }
64
+ }
65
+ setGain(gainDb, rampMs = 0) {
66
+ const linear = dbToGain(gainDb);
67
+ const gain = this.voice.gain;
68
+ if (!gain.gain)
69
+ return;
70
+ if (rampMs > 0 && gain.gain.setTargetAtTime) {
71
+ gain.gain.setTargetAtTime(Math.min(4, Math.max(0, linear)), this.ctx.currentTime, rampMs / 3000);
72
+ }
73
+ else {
74
+ gain.gain.value = Math.min(4, Math.max(0, linear));
75
+ }
76
+ }
77
+ }
78
+ //# sourceMappingURL=LoopHandle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoopHandle.js","sourceRoot":"","sources":["../../../../../packages/audio/src/game-sound/LoopHandle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * game-sound/MasterChain.ts — §6.8 master bus chain.
3
+ *
4
+ * master gain (headroom) → [glue, opt-in] → limiter → makeup trim → safety clipper → destination
5
+ *
6
+ * Chrome's DynamicsCompressorNode always applies automatic makeup gain
7
+ * (≈ 0.6 × the full-scale gain reduction). The old default chain (glue −18 dB
8
+ * 3:1 + limiter −6 dB 12:1) therefore lifted quiet material by +8.3 dB and
9
+ * pushed −12 dBFS by +6.1 dB (L4 OfflineAudioContext sweep): beds, hums and
10
+ * tails came up to meet the hits — a squashed, loud, buzzing mix. The default
11
+ * is now a hard-knee −1 dBFS 20:1 limiter followed by a trim that cancels its
12
+ * makeup exactly, so the chain is level-transparent below the ceiling.
13
+ *
14
+ * The two `DynamicsCompressorNode`s are NOT brick-wall limiting (max ratio
15
+ * 20, finite attack): 32 simultaneous 0 dBFS hits still overshoot ~1.5 dB.
16
+ * The `WaveShaperNode` safety clip (1,024-point curve, linear below −6 dBFS,
17
+ * tanh knee to ±0.966 ≈ −0.3 dBFS) is the guaranteed ceiling — inaudible at
18
+ * normal levels.
19
+ */
20
+ import type { SoundGraphContext, SoundNodeLike } from "./types.js";
21
+ export interface MasterChainOptions {
22
+ /** Glue compressor stage (−18 dB, 3:1). Off by default: its makeup gain lifts quiet material. */
23
+ readonly glue?: boolean;
24
+ /** Limiter threshold, default −1 dBFS. */
25
+ readonly limiterCeilingDb?: number;
26
+ /**
27
+ * Fixed headroom gain in front of the dynamics stages (default
28
+ * MASTER_HEADROOM). Game sample libraries are normalised to ~−1 dBFS peaks;
29
+ * at unity, a few overlapping hits drive the limiter into audible pumping and
30
+ * the safety clipper into distortion. The master volume slider scales on top.
31
+ */
32
+ readonly headroom?: number;
33
+ }
34
+ /** Default master headroom: −4.4 dB (0.6 linear). */
35
+ export declare const MASTER_HEADROOM = 0.6;
36
+ export declare const LIMITER_CEILING_DB = -1;
37
+ export declare const LIMITER_RATIO = 20;
38
+ /**
39
+ * Makeup gain (dB) Chrome/WebKit's DynamicsCompressor adds for a hard-knee
40
+ * curve: 0.6 × the reduction a 0 dBFS input gets, i.e. 0.6·|T|·(1 − 1/R).
41
+ * L4 sweep: T −1, R 20 → measured +0.57 dB, formula 0.57.
42
+ */
43
+ export declare function limiterMakeupDb(thresholdDb: number, ratio: number): number;
44
+ export interface MasterChain {
45
+ readonly input: AudioNode;
46
+ readonly output: SoundNodeLike;
47
+ readonly limiterEngaged: boolean;
48
+ /** Human-readable stage list for `proof()`. */
49
+ readonly stages: readonly string[];
50
+ /** Master volume 0..1 (scaled by the fixed headroom). */
51
+ setMasterGain(volume01: number): void;
52
+ dispose(): void;
53
+ }
54
+ /** Safety-clip curve: linear to −6 dBFS, tanh knee, endpoints ±0.966. */
55
+ export declare function safetyClipCurve(size?: number): Float32Array;
56
+ export declare function createMasterChain(ctx: SoundGraphContext, destination: AudioNode, options?: MasterChainOptions): MasterChain;
57
+ //# sourceMappingURL=MasterChain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MasterChain.d.ts","sourceRoot":"","sources":["../../../../../packages/audio/src/game-sound/MasterChain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,iGAAiG;IACjG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,0CAA0C;IAC1C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,qDAAqD;AACrD,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,yDAAyD;IACzD,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,IAAI,SAAO,GAAG,YAAY,CAczD;AAeD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,iBAAiB,EACtB,WAAW,EAAE,SAAS,EACtB,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAoGb"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * game-sound/MasterChain.ts — §6.8 master bus chain.
3
+ *
4
+ * master gain (headroom) → [glue, opt-in] → limiter → makeup trim → safety clipper → destination
5
+ *
6
+ * Chrome's DynamicsCompressorNode always applies automatic makeup gain
7
+ * (≈ 0.6 × the full-scale gain reduction). The old default chain (glue −18 dB
8
+ * 3:1 + limiter −6 dB 12:1) therefore lifted quiet material by +8.3 dB and
9
+ * pushed −12 dBFS by +6.1 dB (L4 OfflineAudioContext sweep): beds, hums and
10
+ * tails came up to meet the hits — a squashed, loud, buzzing mix. The default
11
+ * is now a hard-knee −1 dBFS 20:1 limiter followed by a trim that cancels its
12
+ * makeup exactly, so the chain is level-transparent below the ceiling.
13
+ *
14
+ * The two `DynamicsCompressorNode`s are NOT brick-wall limiting (max ratio
15
+ * 20, finite attack): 32 simultaneous 0 dBFS hits still overshoot ~1.5 dB.
16
+ * The `WaveShaperNode` safety clip (1,024-point curve, linear below −6 dBFS,
17
+ * tanh knee to ±0.966 ≈ −0.3 dBFS) is the guaranteed ceiling — inaudible at
18
+ * normal levels.
19
+ */
20
+ /** Default master headroom: −4.4 dB (0.6 linear). */
21
+ export const MASTER_HEADROOM = 0.6;
22
+ export const LIMITER_CEILING_DB = -1;
23
+ export const LIMITER_RATIO = 20;
24
+ /**
25
+ * Makeup gain (dB) Chrome/WebKit's DynamicsCompressor adds for a hard-knee
26
+ * curve: 0.6 × the reduction a 0 dBFS input gets, i.e. 0.6·|T|·(1 − 1/R).
27
+ * L4 sweep: T −1, R 20 → measured +0.57 dB, formula 0.57.
28
+ */
29
+ export function limiterMakeupDb(thresholdDb, ratio) {
30
+ return 0.6 * Math.abs(thresholdDb) * (1 - 1 / ratio);
31
+ }
32
+ /** Safety-clip curve: linear to −6 dBFS, tanh knee, endpoints ±0.966. */
33
+ export function safetyClipCurve(size = 1024) {
34
+ const curve = new Float32Array(size);
35
+ const knee = 0.5; // −6 dBFS
36
+ const peak = 0.966;
37
+ const tanhKnee = Math.tanh((1 - knee) * 2);
38
+ for (let i = 0; i < size; i++) {
39
+ const x = (i / (size - 1)) * 2 - 1;
40
+ const ax = Math.abs(x);
41
+ curve[i] =
42
+ ax <= knee
43
+ ? x
44
+ : Math.sign(x) * (knee + ((ax - knee) / (1 - knee)) * tanhKnee * (peak - knee) / tanhKnee);
45
+ }
46
+ return curve;
47
+ }
48
+ export function createMasterChain(ctx, destination, options = {}) {
49
+ const ceiling = options.limiterCeilingDb ?? LIMITER_CEILING_DB;
50
+ const glueOn = options.glue ?? false;
51
+ const headroom = Math.max(0, Math.min(1, options.headroom ?? MASTER_HEADROOM));
52
+ const input = ctx.createGain();
53
+ input.gain.value = headroom;
54
+ const stages = ["masterGain"];
55
+ const chain = [];
56
+ let limiter;
57
+ let shaper;
58
+ if (ctx.createDynamicsCompressor && glueOn) {
59
+ const glue = ctx.createDynamicsCompressor();
60
+ glue.threshold.value = -18;
61
+ glue.knee.value = 6;
62
+ glue.ratio.value = 3;
63
+ glue.attack.value = 0.005;
64
+ glue.release.value = 0.25;
65
+ chain.push(glue);
66
+ stages.push("glue(-18dB)");
67
+ }
68
+ if (ctx.createDynamicsCompressor) {
69
+ limiter = ctx.createDynamicsCompressor();
70
+ // Hard-knee peak limiter; the safety clip below is the guaranteed ceiling.
71
+ limiter.threshold.value = ceiling;
72
+ limiter.knee.value = 0;
73
+ limiter.ratio.value = LIMITER_RATIO;
74
+ limiter.attack.value = 0.001;
75
+ limiter.release.value = 0.1;
76
+ chain.push(limiter);
77
+ stages.push(`limiter(${ceiling}dB)`);
78
+ // Cancel the compressor's automatic makeup gain (exact for knee 0).
79
+ const trim = ctx.createGain();
80
+ trim.gain.value = Math.pow(10, -limiterMakeupDb(ceiling, LIMITER_RATIO) / 20);
81
+ chain.push(trim);
82
+ stages.push("makeupTrim");
83
+ }
84
+ if (ctx.createWaveShaper) {
85
+ shaper = ctx.createWaveShaper();
86
+ shaper.curve = safetyClipCurve();
87
+ shaper.oversample = "none";
88
+ chain.push(shaper);
89
+ stages.push("safetyClip(±0.966)");
90
+ }
91
+ // Wire input → chain → destination.
92
+ let head = input;
93
+ for (const node of chain) {
94
+ head.connect(node);
95
+ head = node;
96
+ }
97
+ head.connect(destination);
98
+ let limiterEngaged = false;
99
+ // `reduction` is read off live compressor state in proof paths; fakes may
100
+ // not expose it, so engagement is computed from the signal instead.
101
+ const reductionProbe = limiter;
102
+ const limiterActive = () => reductionProbe?.reduction !== undefined && reductionProbe.reduction < 0;
103
+ // Engaged flag is sticky within a proof read — set when the limiter reports
104
+ // negative gain reduction at least once.
105
+ const pollLimiter = () => {
106
+ if (limiterActive())
107
+ limiterEngaged = true;
108
+ };
109
+ if (typeof setInterval === "function" && reductionProbe?.reduction !== undefined) {
110
+ const timer = setInterval(pollLimiter, 500);
111
+ if (typeof timer === "object" && timer !== null && "unref" in timer) {
112
+ timer.unref();
113
+ }
114
+ }
115
+ return {
116
+ input,
117
+ output: head,
118
+ stages,
119
+ get limiterEngaged() {
120
+ pollLimiter();
121
+ return limiterEngaged || limiterActive();
122
+ },
123
+ setMasterGain(volume01) {
124
+ input.gain.value = Math.max(0, Math.min(1, volume01)) * headroom;
125
+ },
126
+ dispose() {
127
+ for (const node of chain) {
128
+ try {
129
+ node.disconnect();
130
+ }
131
+ catch {
132
+ /* already disconnected */
133
+ }
134
+ }
135
+ try {
136
+ input.disconnect();
137
+ }
138
+ catch {
139
+ /* noop */
140
+ }
141
+ }
142
+ };
143
+ }
144
+ //# sourceMappingURL=MasterChain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MasterChain.js","sourceRoot":"","sources":["../../../../../packages/audio/src/game-sound/MasterChain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkBH,qDAAqD;AACrD,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AACnC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,CAAC;AACrC,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAEhC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,WAAmB,EAAE,KAAa;IAChE,OAAO,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;AACvD,CAAC;AAaD,yEAAyE;AACzE,MAAM,UAAU,eAAe,CAAC,IAAI,GAAG,IAAI;IACzC,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,UAAU;IAC5B,MAAM,IAAI,GAAG,KAAK,CAAC;IACnB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvB,KAAK,CAAC,CAAC,CAAC;YACN,EAAE,IAAI,IAAI;gBACR,CAAC,CAAC,CAAC;gBACH,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAeD,MAAM,UAAU,iBAAiB,CAC/B,GAAsB,EACtB,WAAsB,EACtB,UAA8B,EAAE;IAEhC,MAAM,OAAO,GAAG,OAAO,CAAC,gBAAgB,IAAI,kBAAkB,CAAC;IAC/D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC;IAErC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;IAC/B,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;IAC5B,MAAM,MAAM,GAAa,CAAC,YAAY,CAAC,CAAC;IACxC,MAAM,KAAK,GAAoB,EAAE,CAAC;IAElC,IAAI,OAAmC,CAAC;IACxC,IAAI,MAA8B,CAAC;IAEnC,IAAI,GAAG,CAAC,wBAAwB,IAAI,MAAM,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,GAAG,CAAC,wBAAwB,EAA+B,CAAC;QACzE,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QAC1B,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,GAAG,CAAC,wBAAwB,EAAE,CAAC;QACjC,OAAO,GAAG,GAAG,CAAC,wBAAwB,EAA+B,CAAC;QACtE,2EAA2E;QAC3E,OAAO,CAAC,SAAS,CAAC,KAAK,GAAG,OAAO,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC;QACpC,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QAC7B,OAAO,CAAC,OAAO,CAAC,KAAK,GAAG,GAAG,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC,CAAC;QACrC,oEAAoE;QACpE,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC;QAC9E,KAAK,CAAC,IAAI,CAAC,IAAgC,CAAC,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,CAAC,gBAAgB,EAA2B,CAAC;QACzD,MAAM,CAAC,KAAK,GAAG,eAAe,EAAE,CAAC;QACjC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACpC,CAAC;IAED,oCAAoC;IACpC,IAAI,IAAI,GAAkB,KAAiC,CAAC;IAC5D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACxB,IAA+C,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,IAAI,GAAG,IAAI,CAAC;IACd,CAAC;IACA,IAA+C,CAAC,OAAO,CAAC,WAAuC,CAAC,CAAC;IAElG,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,0EAA0E;IAC1E,oEAAoE;IACpE,MAAM,cAAc,GAAG,OAAwD,CAAC;IAChF,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,cAAc,EAAE,SAAS,KAAK,SAAS,IAAI,cAAc,CAAC,SAAS,GAAG,CAAC,CAAC;IACpG,4EAA4E;IAC5E,yCAAyC;IACzC,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,aAAa,EAAE;YAAE,cAAc,GAAG,IAAI,CAAC;IAC7C,CAAC,CAAC;IACF,IAAI,OAAO,WAAW,KAAK,UAAU,IAAI,cAAc,EAAE,SAAS,KAAK,SAAS,EAAE,CAAC;QACjF,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;YACnE,KAA2B,CAAC,KAAK,EAAE,CAAC;QACvC,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK;QACL,MAAM,EAAE,IAAI;QACZ,MAAM;QACN,IAAI,cAAc;YAChB,WAAW,EAAE,CAAC;YACd,OAAO,cAAc,IAAI,aAAa,EAAE,CAAC;QAC3C,CAAC;QACD,aAAa,CAAC,QAAgB;YAC5B,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,QAAQ,CAAC;QACnE,CAAC;QACD,OAAO;YACL,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC;oBACH,IAAI,CAAC,UAAU,EAAE,CAAC;gBACpB,CAAC;gBAAC,MAAM,CAAC;oBACP,0BAA0B;gBAC5B,CAAC;YACH,CAAC;YACD,IAAI,CAAC;gBACH,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,UAAU;YACZ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * game-sound/MusicController.ts — §6.8 music.
3
+ *
4
+ * Single-stem tracks stream through one `MediaElementAudioSourceNode` (no
5
+ * decode). Multi-stem tracks are decoded buffers started on ONE shared
6
+ * `start(when)` timestamp — separate media elements cannot stay sample-
7
+ * aligned. Crossfade default 1.5 s; `setIntensity` drives stem gains;
8
+ * `stinger` quantizes to beat/bar from `bpm` + the track's start time;
9
+ * pause duck −9 dB + lowpass 1.2 kHz.
10
+ */
11
+ import type { SoundGraphContext, SoundNodeLike } from "./types.js";
12
+ export interface MusicTrackSpec {
13
+ readonly stems: readonly {
14
+ readonly url: string;
15
+ readonly id?: string;
16
+ }[];
17
+ readonly bpm?: number;
18
+ readonly loop?: boolean;
19
+ }
20
+ export interface MusicControllerOptions {
21
+ readonly ctx: SoundGraphContext;
22
+ readonly output: SoundNodeLike;
23
+ readonly tracks: Readonly<Record<string, MusicTrackSpec>>;
24
+ readonly bufferFor: (url: string) => AudioBuffer | undefined;
25
+ /** Media-element factory (injectable for tests). */
26
+ readonly createMediaElement?: (url: string) => HTMLMediaElement;
27
+ /** Cue player injected by the engine so `stinger` can schedule cues. */
28
+ readonly playCue?: (cue: string) => void;
29
+ }
30
+ export declare class MusicControllerImpl {
31
+ private readonly o;
32
+ private active?;
33
+ private intensity;
34
+ private ducked;
35
+ private duckFilter?;
36
+ private disposed;
37
+ constructor(o: MusicControllerOptions);
38
+ play(track: string, options?: {
39
+ crossfadeMs?: number;
40
+ }): void;
41
+ setIntensity(level01: number): void;
42
+ /** Beat/bar-quantized stinger scheduling from the playing track's start. */
43
+ quantizeDelay(quantize: "beat" | "bar" | "none" | undefined): number;
44
+ stingerDelay(quantize?: "beat" | "bar" | "none"): number;
45
+ /** C-25: schedule a cue stinger quantized to the playing track's beat/bar. */
46
+ stinger(cue: string, o?: {
47
+ quantize?: "beat" | "bar" | "none";
48
+ }): void;
49
+ /** Pause duck: −9 dB + lowpass 1.2 kHz until cleared. */
50
+ setDucked(ducked: boolean): void;
51
+ stop(fadeMs?: number): void;
52
+ get playing(): string | undefined;
53
+ dispose(): void;
54
+ private startTrack;
55
+ private stemLevel;
56
+ private applyStemGains;
57
+ private stopTrack;
58
+ }
59
+ //# sourceMappingURL=MusicController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MusicController.d.ts","sourceRoot":"","sources":["../../../../../packages/audio/src/game-sound/MusicController.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAmB,iBAAiB,EAAE,aAAa,EAAmB,MAAM,SAAS,CAAC;AAElG,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1E,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;IAC7D,oDAAoD;IACpD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,gBAAgB,CAAC;IAChE,wEAAwE;IACxE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AAaD,qBAAa,mBAAmB;IAOlB,OAAO,CAAC,QAAQ,CAAC,CAAC;IAN9B,OAAO,CAAC,MAAM,CAAC,CAAc;IAC7B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAC,CAAkB;IACrC,OAAO,CAAC,QAAQ,CAAS;gBAEI,CAAC,EAAE,sBAAsB;IAEtD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,IAAI;IAajE,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKnC,4EAA4E;IAC5E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM;IAUpE,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM;IAIxD,8EAA8E;IAC9E,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;KAAO,GAAG,IAAI;IAW1E,yDAAyD;IACzD,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAShC,IAAI,CAAC,MAAM,SAAM,GAAG,IAAI;IASxB,IAAI,OAAO,IAAI,MAAM,GAAG,SAAS,CAEhC;IAED,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,UAAU;IA0DlB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,SAAS;CA0BlB"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * game-sound/MusicController.ts — §6.8 music.
3
+ *
4
+ * Single-stem tracks stream through one `MediaElementAudioSourceNode` (no
5
+ * decode). Multi-stem tracks are decoded buffers started on ONE shared
6
+ * `start(when)` timestamp — separate media elements cannot stay sample-
7
+ * aligned. Crossfade default 1.5 s; `setIntensity` drives stem gains;
8
+ * `stinger` quantizes to beat/bar from `bpm` + the track's start time;
9
+ * pause duck −9 dB + lowpass 1.2 kHz.
10
+ */
11
+ import { clamp, dbToGain } from "./types.js";
12
+ export class MusicControllerImpl {
13
+ o;
14
+ active;
15
+ intensity = 1;
16
+ ducked = false;
17
+ duckFilter;
18
+ disposed = false;
19
+ constructor(o) {
20
+ this.o = o;
21
+ }
22
+ play(track, options = {}) {
23
+ if (this.disposed)
24
+ return;
25
+ const spec = this.o.tracks[track];
26
+ if (!spec)
27
+ throw new Error(`Unknown music track "${track}"`);
28
+ const fade = options.crossfadeMs ?? 1500;
29
+ const prev = this.active;
30
+ if (prev) {
31
+ rampGain(prev.gain, this.o.ctx.currentTime, 0, fade);
32
+ setTimeout(() => this.stopTrack(prev), fade + 60).unref?.();
33
+ }
34
+ this.active = this.startTrack(track, spec, fade);
35
+ }
36
+ setIntensity(level01) {
37
+ this.intensity = clamp(level01, 0, 1);
38
+ this.applyStemGains();
39
+ }
40
+ /** Beat/bar-quantized stinger scheduling from the playing track's start. */
41
+ quantizeDelay(quantize) {
42
+ const track = this.active;
43
+ if (!track?.bpm || quantize === "none" || quantize === undefined)
44
+ return 0;
45
+ const beat = 60 / track.bpm;
46
+ const bar = beat * 4;
47
+ const elapsed = this.o.ctx.currentTime - track.startedAt;
48
+ const unit = quantize === "bar" ? bar : beat;
49
+ return (Math.ceil(elapsed / unit) * unit - elapsed) || unit;
50
+ }
51
+ stingerDelay(quantize) {
52
+ return this.quantizeDelay(quantize);
53
+ }
54
+ /** C-25: schedule a cue stinger quantized to the playing track's beat/bar. */
55
+ stinger(cue, o = {}) {
56
+ const play = this.o.playCue;
57
+ if (!play)
58
+ return;
59
+ const delayS = this.quantizeDelay(o.quantize);
60
+ if (delayS <= 0) {
61
+ play(cue);
62
+ return;
63
+ }
64
+ setTimeout(() => play(cue), delayS * 1000).unref?.();
65
+ }
66
+ /** Pause duck: −9 dB + lowpass 1.2 kHz until cleared. */
67
+ setDucked(ducked) {
68
+ this.ducked = ducked;
69
+ if (!this.active)
70
+ return;
71
+ const target = ducked ? dbToGain(-9) : 1;
72
+ rampGain(this.active.gain, this.o.ctx.currentTime, target, 120);
73
+ const f = this.duckFilter?.frequency;
74
+ if (f)
75
+ f.value = ducked ? 1200 : 20_000;
76
+ }
77
+ stop(fadeMs = 500) {
78
+ if (this.active) {
79
+ const prev = this.active;
80
+ rampGain(prev.gain, this.o.ctx.currentTime, 0, fadeMs);
81
+ setTimeout(() => this.stopTrack(prev), fadeMs + 60).unref?.();
82
+ this.active = undefined;
83
+ }
84
+ }
85
+ get playing() {
86
+ return this.active?.name;
87
+ }
88
+ dispose() {
89
+ this.disposed = true;
90
+ if (this.active)
91
+ this.stopTrack(this.active);
92
+ this.active = undefined;
93
+ }
94
+ startTrack(name, spec, fadeInMs) {
95
+ const ctx = this.o.ctx;
96
+ const gain = ctx.createGain();
97
+ const g = gain;
98
+ g.gain.value = fadeInMs > 0 ? 0.0001 : 1;
99
+ if (this.ducked)
100
+ g.gain.value = dbToGain(-9) * g.gain.value;
101
+ let duckFilter;
102
+ if (typeof ctx.createBiquadFilter === "function") {
103
+ duckFilter = ctx.createBiquadFilter();
104
+ duckFilter.type = "lowpass";
105
+ if (duckFilter.frequency)
106
+ duckFilter.frequency.value = this.ducked ? 1200 : 20_000;
107
+ gain.connect(duckFilter);
108
+ duckFilter.connect(this.o.output);
109
+ }
110
+ else {
111
+ gain.connect(this.o.output);
112
+ }
113
+ this.duckFilter = duckFilter;
114
+ if (fadeInMs > 0)
115
+ rampGain(gain, ctx.currentTime, this.ducked ? dbToGain(-9) : 1, fadeInMs);
116
+ const track = { name, gain, stems: [], mediaElement: undefined, startedAt: ctx.currentTime, bpm: spec.bpm, stopped: false };
117
+ if (spec.stems.length === 1 && typeof ctx.createMediaElementSource === "function" && this.o.createMediaElement) {
118
+ // Single stem → streamed media element.
119
+ const element = this.o.createMediaElement(spec.stems[0].url);
120
+ element.loop = spec.loop ?? true;
121
+ const source = ctx.createMediaElementSource(element);
122
+ source.connect(gain);
123
+ track.mediaElement = element;
124
+ track.stems = [source];
125
+ void element.play?.();
126
+ }
127
+ else {
128
+ // Multi-stem → decoded buffers started on one shared `when`.
129
+ const when = ctx.currentTime + 0.05;
130
+ const stemGains = [];
131
+ const stems = spec.stems.map((stem, i) => {
132
+ const source = ctx.createBufferSource();
133
+ source.buffer = this.o.bufferFor(stem.url) ?? null;
134
+ source.loop = spec.loop ?? true;
135
+ const stemGain = ctx.createGain();
136
+ const param = stemGain.gain;
137
+ // Base stem (index 0) is always full; intensity layers fade in by
138
+ // setIntensity — start at their intensity-scaled level.
139
+ param.value = i === 0 ? 1 : this.stemLevel(i, spec.stems.length);
140
+ if (i > 0)
141
+ stemGains[i - 1] = param;
142
+ source.connect(stemGain);
143
+ stemGain.connect(gain);
144
+ source.start(when);
145
+ return source;
146
+ });
147
+ track.stems = stems;
148
+ track.stemGains = stemGains;
149
+ // Beat quantization anchors at the shared start timestamp, not mount time.
150
+ track.startedAt = when;
151
+ }
152
+ return track;
153
+ }
154
+ stemLevel(stemIndex, stemCount) {
155
+ // Later stems represent higher intensity layers.
156
+ return this.intensity * Math.min(1, (stemIndex + 1) / stemCount);
157
+ }
158
+ applyStemGains() {
159
+ const track = this.active;
160
+ if (!track || track.stems.length <= 1)
161
+ return;
162
+ track.stemGains?.forEach((param, i) => {
163
+ param.value = this.stemLevel(i + 1, track.stems.length);
164
+ });
165
+ }
166
+ stopTrack(track) {
167
+ if (track.stopped)
168
+ return;
169
+ track.stopped = true;
170
+ for (const stem of track.stems) {
171
+ try {
172
+ stem.stop?.();
173
+ }
174
+ catch {
175
+ /* noop */
176
+ }
177
+ try {
178
+ stem.disconnect();
179
+ }
180
+ catch {
181
+ /* noop */
182
+ }
183
+ }
184
+ try {
185
+ track.mediaElement?.pause?.();
186
+ }
187
+ catch {
188
+ /* noop */
189
+ }
190
+ try {
191
+ track.gain.disconnect();
192
+ }
193
+ catch {
194
+ /* noop */
195
+ }
196
+ }
197
+ }
198
+ const rampGain = (gain, now, target, ms) => {
199
+ const param = gain.gain;
200
+ if (!param)
201
+ return;
202
+ if (ms <= 0 || !param.linearRampToValueAtTime) {
203
+ param.value = target;
204
+ return;
205
+ }
206
+ param.cancelScheduledValues?.(now);
207
+ param.setValueAtTime?.(Math.max(0.0001, param.value), now);
208
+ param.linearRampToValueAtTime(Math.max(0.0001, target), now + ms / 1000);
209
+ };
210
+ //# sourceMappingURL=MusicController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MusicController.js","sourceRoot":"","sources":["../../../../../packages/audio/src/game-sound/MusicController.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * game-sound/ReverbSend.ts — §6.8 reverb send.
3
+ *
4
+ * One convolver send off the bus mix, tier-gated (off on `low`; IR length ≤
5
+ * the §17 budget: medium 0.8 s, high 1.5 s, ultra 2.5 s). Presets pick a
6
+ * bundled mono 24 kHz IR via the format probe. `underwater` adds a 1.8 kHz
7
+ * lowpass on the send path.
8
+ */
9
+ import type { SoundGraphContext, SoundNodeLike } from "./types.js";
10
+ export type ReverbPreset = "none" | "small-room" | "hall" | "street" | "hangar" | "underwater";
11
+ export type QualityTier = "low" | "medium" | "high" | "ultra";
12
+ export declare const IR_BUDGET_SECONDS: Record<QualityTier, number>;
13
+ /** IR lengths shipped per preset (seconds, mono 24 kHz — from the pack manifest). */
14
+ export declare const PRESET_IR_SECONDS: Record<Exclude<ReverbPreset, "none">, number>;
15
+ export declare const presetIrUrl: (preset: ReverbPreset, extension: string) => string | undefined;
16
+ export interface ReverbSendOptions {
17
+ readonly ctx: SoundGraphContext;
18
+ readonly preset: ReverbPreset;
19
+ readonly tier: QualityTier;
20
+ /** Resolve an IR url → decoded AudioBuffer (async loading upstream). */
21
+ readonly bufferFor: (url: string) => AudioBuffer | undefined;
22
+ }
23
+ export interface ReverbSend {
24
+ readonly enabled: boolean;
25
+ readonly sendInput?: SoundNodeLike;
26
+ readonly irSeconds: number;
27
+ dispose(): void;
28
+ }
29
+ export declare function createReverbSend(options: ReverbSendOptions): ReverbSend;
30
+ //# sourceMappingURL=ReverbSend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReverbSend.d.ts","sourceRoot":"","sources":["../../../../../packages/audio/src/game-sound/ReverbSend.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAmB,iBAAiB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEjF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAC;AAC/F,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAE9D,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAKzD,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM,CAM3E,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,QAAQ,YAAY,EAAE,WAAW,MAAM,KAAG,MAAM,GAAG,SACX,CAAC;AAErE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;CAC9D;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA4CvE"}