@vgai/engine 0.2.0 → 0.4.0-canary.20260715.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/README.md +3 -1
- package/package.json +24 -4
- package/schemas/engine-api.json +124 -0
- package/schemas/engine-api.md +53 -0
- package/schemas/engine-capabilities.json +124 -0
- package/schemas/inputmap.schema.json +314 -0
- package/schemas/mat.schema.json +286 -0
- package/schemas/prefab.schema.json +10148 -0
- package/schemas/scn2d.schema.json +475 -0
- package/schemas/vgai-game.schema.json +383 -0
- package/schemas/vscn.schema.json +11007 -0
- package/src/adapter/{world-kind.ts → adapter-surface.ts} +6 -6
- package/src/adapter/authoring.ts +77 -0
- package/src/adapter/first-party-systems.ts +23 -34
- package/src/adapter/game-adapter.ts +8 -8
- package/src/adapter/host-context.ts +2 -4
- package/src/adapter/index.ts +4 -4
- package/src/adapter/system-adapter.ts +88 -22
- package/src/adapter/vgai-scene-game-adapter.ts +244 -194
- package/src/animation/anim-graph-types.ts +12 -43
- package/src/animation/animation-clock.ts +479 -0
- package/src/animation/camera-ownership.ts +467 -0
- package/src/animation/cinematic-cues.ts +451 -0
- package/src/animation/clip-map.ts +41 -0
- package/src/animation/gsap-registration.ts +184 -0
- package/src/animation/theatre-clock-binding.ts +111 -0
- package/src/animation/theatre-director.ts +347 -0
- package/src/animation/theatre-object-binding.ts +661 -0
- package/src/animation/xstate-animation-binding.ts +436 -0
- package/src/animation/xstate-animation-meta.ts +319 -0
- package/src/audio/index.ts +39 -7
- package/src/audio/tone-clock-binding.ts +98 -0
- package/src/audio/tone-context.ts +129 -0
- package/src/audio/tone-offline-render.ts +167 -0
- package/src/audio/wav-encode.ts +119 -0
- package/src/character/cloth-sim.ts +533 -0
- package/src/character/spring-chain.ts +307 -0
- package/src/core/game-loop.ts +57 -2
- package/src/core/seeded-random.ts +161 -0
- package/src/core/system-runner.ts +20 -3
- package/src/core/types.ts +50 -0
- package/src/data/data-asset.ts +167 -0
- package/src/data/data-check-core.ts +242 -0
- package/src/data/data-ref.ts +145 -0
- package/src/data/vite-plugin-data.ts +290 -0
- package/src/dev/performance-profiler.ts +213 -0
- package/src/dev/webgl-gpu-timer.ts +53 -0
- package/src/ecs/component-manager.ts +45 -12
- package/src/ecs/game-component.ts +95 -11
- package/src/humanoid/bake.operation.ts +326 -0
- package/src/humanoid/body.ts +663 -0
- package/src/humanoid/clips.ts +149 -0
- package/src/humanoid/compose.ts +209 -0
- package/src/humanoid/generate.ts +189 -0
- package/src/humanoid/index.ts +36 -0
- package/src/humanoid/schema.ts +108 -0
- package/src/humanoid/skeleton.ts +345 -0
- package/src/index.ts +48 -0
- package/src/input/input-manager.ts +1886 -33
- package/src/input/input-types.ts +158 -3
- package/src/input/prompt-labels.ts +122 -0
- package/src/input/rebind-controller.ts +105 -0
- package/src/input/schema.ts +206 -52
- package/src/manifest/index.ts +5 -5
- package/src/manifest/load.ts +125 -72
- package/src/manifest/schema.ts +362 -255
- package/src/react/game-state.tsx +135 -32
- package/src/react/root-adapter.tsx +49 -0
- package/src/react/unmanaged-root-detector.ts +66 -0
- package/src/react/use-data.ts +124 -0
- package/src/react/use-selection.tsx +135 -0
- package/src/runtime/create-runtime.ts +112 -273
- package/src/runtime/debug-bridge.ts +483 -0
- package/src/runtime/debug-registry.ts +856 -0
- package/src/runtime/game.ts +342 -93
- package/src/runtime/gameplay-rng-trap.ts +134 -0
- package/src/runtime/input-router.ts +7 -7
- package/src/runtime/mount-game.ts +40 -38
- package/src/runtime/mount-manifest.ts +169 -37
- package/src/runtime/render-audio-control.ts +168 -0
- package/src/runtime/render-control.ts +522 -0
- package/src/runtime/render-seed.ts +79 -0
- package/src/runtime/state-bridge.ts +24 -10
- package/src/runtime/types.ts +110 -33
- package/src/scene/asset-loaders.ts +10 -36
- package/src/scene/asset-paths.ts +0 -2
- package/src/scene/asset-ref-check.ts +248 -0
- package/src/scene/asset-registry.ts +22 -0
- package/src/scene/component-registry.ts +14 -3
- package/src/scene/defaults.ts +1 -0
- package/src/scene/light-camera-factory.ts +11 -3
- package/src/scene/parse.ts +133 -0
- package/src/scene/scene-apply.ts +55 -4
- package/src/scene/scene-loader.ts +91 -123
- package/src/scene/scene-types.ts +0 -1
- package/src/scene/schema/animation.ts +30 -79
- package/src/scene/schema/entity.ts +20 -0
- package/src/scene/schema/index.ts +2 -46
- package/src/scene/schema/light.ts +16 -1
- package/src/scene/schema/material.ts +96 -91
- package/src/scene/schema/scene-file.ts +1 -7
- package/src/scene/user-data.ts +22 -10
- package/src/setup/setup-renderer.ts +10 -3
- package/src/tools/define-tool.ts +191 -0
- package/src/world2d/authoring-2d.ts +17 -1
- package/src/world2d/collision-2d.ts +1 -1
- package/src/world2d/pixi-game-adapter.ts +19 -17
- package/src/world2d/scene2d-loader.ts +1 -0
- package/src/world2d/types.ts +8 -2
- package/src/animation/anim-graph.ts +0 -406
- package/src/animation/anim-system.ts +0 -28
- package/src/animation/property-track.ts +0 -178
- package/src/animation/schema.ts +0 -204
- package/src/audio/ambient.ts +0 -300
- package/src/audio/impacts.ts +0 -212
- package/src/audio/movement.ts +0 -140
- package/src/audio/musical.ts +0 -200
- package/src/audio/ui-sounds.ts +0 -171
- package/src/audio/vehicle.ts +0 -235
- package/src/audio/weapons.ts +0 -152
- package/src/runtime/scene-ui-bridge.ts +0 -86
- package/src/runtime/scene-ui-data.ts +0 -119
- package/src/scene/schema/ui.ts +0 -602
package/src/audio/impacts.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Impact sounds — procedural one-shot sounds for collisions and events.
|
|
3
|
-
*
|
|
4
|
-
* All functions take a Web Audio `AudioContext` and a destination `AudioNode`
|
|
5
|
-
* (typically a bus gain from setup-audio). Returns `{ play(), dispose() }`.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
function createNoiseBuffer(ctx: AudioContext, duration = 1): AudioBuffer {
|
|
9
|
-
const length = ctx.sampleRate * duration;
|
|
10
|
-
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
|
|
11
|
-
const data = buffer.getChannelData(0);
|
|
12
|
-
for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
|
|
13
|
-
return buffer;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Short percussive thud — noise burst through a bandpass filter. */
|
|
17
|
-
export function hit(
|
|
18
|
-
ctx: AudioContext,
|
|
19
|
-
dest: AudioNode,
|
|
20
|
-
options?: { pitch?: number; volume?: number; duration?: number; brightness?: number },
|
|
21
|
-
) {
|
|
22
|
-
const pitch = options?.pitch ?? 400;
|
|
23
|
-
const volume = options?.volume ?? 0.5;
|
|
24
|
-
const duration = options?.duration ?? 0.1;
|
|
25
|
-
const brightness = options?.brightness ?? 1;
|
|
26
|
-
const noiseBuffer = createNoiseBuffer(ctx, 0.5);
|
|
27
|
-
|
|
28
|
-
const out = ctx.createGain();
|
|
29
|
-
out.gain.value = volume;
|
|
30
|
-
out.connect(dest);
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
play() {
|
|
34
|
-
const now = ctx.currentTime;
|
|
35
|
-
const src = ctx.createBufferSource();
|
|
36
|
-
src.buffer = noiseBuffer;
|
|
37
|
-
|
|
38
|
-
const filter = ctx.createBiquadFilter();
|
|
39
|
-
filter.type = 'bandpass';
|
|
40
|
-
filter.frequency.value = pitch;
|
|
41
|
-
filter.Q.value = brightness;
|
|
42
|
-
|
|
43
|
-
const env = ctx.createGain();
|
|
44
|
-
env.gain.setValueAtTime(1, now);
|
|
45
|
-
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
|
|
46
|
-
|
|
47
|
-
src.connect(filter);
|
|
48
|
-
filter.connect(env);
|
|
49
|
-
env.connect(out);
|
|
50
|
-
src.start(now);
|
|
51
|
-
src.stop(now + duration + 0.01);
|
|
52
|
-
},
|
|
53
|
-
dispose() {
|
|
54
|
-
out.disconnect();
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Ball-bounce ping — sine tone with quick pitch drop. */
|
|
60
|
-
export function bounce(
|
|
61
|
-
ctx: AudioContext,
|
|
62
|
-
dest: AudioNode,
|
|
63
|
-
options?: { pitch?: number; volume?: number; duration?: number },
|
|
64
|
-
) {
|
|
65
|
-
const pitch = options?.pitch ?? 300;
|
|
66
|
-
const volume = options?.volume ?? 0.3;
|
|
67
|
-
const duration = options?.duration ?? 0.15;
|
|
68
|
-
|
|
69
|
-
const out = ctx.createGain();
|
|
70
|
-
out.gain.value = volume;
|
|
71
|
-
out.connect(dest);
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
play() {
|
|
75
|
-
const now = ctx.currentTime;
|
|
76
|
-
const osc = ctx.createOscillator();
|
|
77
|
-
osc.type = 'sine';
|
|
78
|
-
osc.frequency.setValueAtTime(pitch, now);
|
|
79
|
-
osc.frequency.exponentialRampToValueAtTime(pitch * 0.5, now + duration);
|
|
80
|
-
|
|
81
|
-
const env = ctx.createGain();
|
|
82
|
-
env.gain.setValueAtTime(1, now);
|
|
83
|
-
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
|
|
84
|
-
|
|
85
|
-
osc.connect(env);
|
|
86
|
-
env.connect(out);
|
|
87
|
-
osc.start(now);
|
|
88
|
-
osc.stop(now + duration + 0.01);
|
|
89
|
-
},
|
|
90
|
-
dispose() {
|
|
91
|
-
out.disconnect();
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Heavy crash — layered noise burst with lowpass sweep + low sine thud. */
|
|
97
|
-
export function crash(
|
|
98
|
-
ctx: AudioContext,
|
|
99
|
-
dest: AudioNode,
|
|
100
|
-
options?: { volume?: number; duration?: number; lowFreq?: number },
|
|
101
|
-
) {
|
|
102
|
-
const volume = options?.volume ?? 0.7;
|
|
103
|
-
const duration = options?.duration ?? 0.5;
|
|
104
|
-
const lowFreq = options?.lowFreq ?? 80;
|
|
105
|
-
const noiseBuffer = createNoiseBuffer(ctx, 1);
|
|
106
|
-
|
|
107
|
-
const out = ctx.createGain();
|
|
108
|
-
out.gain.value = volume;
|
|
109
|
-
out.connect(dest);
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
play() {
|
|
113
|
-
const now = ctx.currentTime;
|
|
114
|
-
|
|
115
|
-
// Noise layer: lowpass sweep from bright to dark
|
|
116
|
-
const noiseSrc = ctx.createBufferSource();
|
|
117
|
-
noiseSrc.buffer = noiseBuffer;
|
|
118
|
-
const lp = ctx.createBiquadFilter();
|
|
119
|
-
lp.type = 'lowpass';
|
|
120
|
-
lp.frequency.setValueAtTime(4000, now);
|
|
121
|
-
lp.frequency.exponentialRampToValueAtTime(200, now + duration);
|
|
122
|
-
const noiseEnv = ctx.createGain();
|
|
123
|
-
noiseEnv.gain.setValueAtTime(0.7, now);
|
|
124
|
-
noiseEnv.gain.exponentialRampToValueAtTime(0.001, now + duration);
|
|
125
|
-
noiseSrc.connect(lp);
|
|
126
|
-
lp.connect(noiseEnv);
|
|
127
|
-
noiseEnv.connect(out);
|
|
128
|
-
noiseSrc.start(now);
|
|
129
|
-
noiseSrc.stop(now + duration + 0.01);
|
|
130
|
-
|
|
131
|
-
// Low thud layer
|
|
132
|
-
const thud = ctx.createOscillator();
|
|
133
|
-
thud.type = 'sine';
|
|
134
|
-
thud.frequency.value = lowFreq;
|
|
135
|
-
const thudEnv = ctx.createGain();
|
|
136
|
-
thudEnv.gain.setValueAtTime(0.8, now);
|
|
137
|
-
thudEnv.gain.exponentialRampToValueAtTime(0.001, now + duration * 0.7);
|
|
138
|
-
thud.connect(thudEnv);
|
|
139
|
-
thudEnv.connect(out);
|
|
140
|
-
thud.start(now);
|
|
141
|
-
thud.stop(now + duration + 0.01);
|
|
142
|
-
},
|
|
143
|
-
dispose() {
|
|
144
|
-
out.disconnect();
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** Large explosion — heavy noise burst with sub-bass tail. */
|
|
150
|
-
export function explosion(
|
|
151
|
-
ctx: AudioContext,
|
|
152
|
-
dest: AudioNode,
|
|
153
|
-
options?: { volume?: number; duration?: number; subFreq?: number },
|
|
154
|
-
) {
|
|
155
|
-
const volume = options?.volume ?? 0.8;
|
|
156
|
-
const duration = options?.duration ?? 1.0;
|
|
157
|
-
const subFreq = options?.subFreq ?? 40;
|
|
158
|
-
const noiseBuffer = createNoiseBuffer(ctx, 2);
|
|
159
|
-
|
|
160
|
-
const out = ctx.createGain();
|
|
161
|
-
out.gain.value = volume;
|
|
162
|
-
out.connect(dest);
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
play() {
|
|
166
|
-
const now = ctx.currentTime;
|
|
167
|
-
|
|
168
|
-
// Noise layer: wide lowpass sweep
|
|
169
|
-
const noiseSrc = ctx.createBufferSource();
|
|
170
|
-
noiseSrc.buffer = noiseBuffer;
|
|
171
|
-
const lp = ctx.createBiquadFilter();
|
|
172
|
-
lp.type = 'lowpass';
|
|
173
|
-
lp.frequency.setValueAtTime(6000, now);
|
|
174
|
-
lp.frequency.exponentialRampToValueAtTime(100, now + duration);
|
|
175
|
-
const noiseEnv = ctx.createGain();
|
|
176
|
-
noiseEnv.gain.setValueAtTime(1, now);
|
|
177
|
-
noiseEnv.gain.exponentialRampToValueAtTime(0.001, now + duration);
|
|
178
|
-
noiseSrc.connect(lp);
|
|
179
|
-
lp.connect(noiseEnv);
|
|
180
|
-
noiseEnv.connect(out);
|
|
181
|
-
noiseSrc.start(now);
|
|
182
|
-
noiseSrc.stop(now + duration + 0.01);
|
|
183
|
-
|
|
184
|
-
// Sub-bass layer
|
|
185
|
-
const sub = ctx.createOscillator();
|
|
186
|
-
sub.type = 'sine';
|
|
187
|
-
sub.frequency.value = subFreq;
|
|
188
|
-
const subEnv = ctx.createGain();
|
|
189
|
-
subEnv.gain.setValueAtTime(1, now);
|
|
190
|
-
subEnv.gain.exponentialRampToValueAtTime(0.001, now + duration * 0.8);
|
|
191
|
-
sub.connect(subEnv);
|
|
192
|
-
subEnv.connect(out);
|
|
193
|
-
sub.start(now);
|
|
194
|
-
sub.stop(now + duration + 0.01);
|
|
195
|
-
|
|
196
|
-
// Crunch layer: short sawtooth burst
|
|
197
|
-
const crunch = ctx.createOscillator();
|
|
198
|
-
crunch.type = 'sawtooth';
|
|
199
|
-
crunch.frequency.value = 60;
|
|
200
|
-
const crunchEnv = ctx.createGain();
|
|
201
|
-
crunchEnv.gain.setValueAtTime(0.5, now);
|
|
202
|
-
crunchEnv.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
|
|
203
|
-
crunch.connect(crunchEnv);
|
|
204
|
-
crunchEnv.connect(out);
|
|
205
|
-
crunch.start(now);
|
|
206
|
-
crunch.stop(now + 0.2);
|
|
207
|
-
},
|
|
208
|
-
dispose() {
|
|
209
|
-
out.disconnect();
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
}
|
package/src/audio/movement.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Movement sounds — footsteps, whooshes, jumps.
|
|
3
|
-
*
|
|
4
|
-
* All one-shot: call play() to trigger, dispose() to clean up.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
function createNoiseBuffer(ctx: AudioContext, duration = 1): AudioBuffer {
|
|
8
|
-
const length = ctx.sampleRate * duration;
|
|
9
|
-
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
|
|
10
|
-
const data = buffer.getChannelData(0);
|
|
11
|
-
for (let i = 0; i < length; i++) data[i] = Math.random() * 2 - 1;
|
|
12
|
-
return buffer;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Footstep — filtered noise burst with random pitch variation per play(). */
|
|
16
|
-
export function footstep(
|
|
17
|
-
ctx: AudioContext,
|
|
18
|
-
dest: AudioNode,
|
|
19
|
-
options?: { pitch?: number; volume?: number; variation?: number },
|
|
20
|
-
) {
|
|
21
|
-
const pitch = options?.pitch ?? 250;
|
|
22
|
-
const volume = options?.volume ?? 0.3;
|
|
23
|
-
const variation = options?.variation ?? 0.2;
|
|
24
|
-
const noiseBuffer = createNoiseBuffer(ctx, 0.2);
|
|
25
|
-
|
|
26
|
-
const out = ctx.createGain();
|
|
27
|
-
out.gain.value = volume;
|
|
28
|
-
out.connect(dest);
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
play() {
|
|
32
|
-
const now = ctx.currentTime;
|
|
33
|
-
const src = ctx.createBufferSource();
|
|
34
|
-
src.buffer = noiseBuffer;
|
|
35
|
-
|
|
36
|
-
const filter = ctx.createBiquadFilter();
|
|
37
|
-
filter.type = 'bandpass';
|
|
38
|
-
filter.frequency.value = pitch * (1 + (Math.random() - 0.5) * 2 * variation);
|
|
39
|
-
filter.Q.value = 2;
|
|
40
|
-
|
|
41
|
-
const env = ctx.createGain();
|
|
42
|
-
env.gain.setValueAtTime(0, now);
|
|
43
|
-
env.gain.linearRampToValueAtTime(1, now + 0.007);
|
|
44
|
-
env.gain.exponentialRampToValueAtTime(0.001, now + 0.05);
|
|
45
|
-
|
|
46
|
-
src.connect(filter);
|
|
47
|
-
filter.connect(env);
|
|
48
|
-
env.connect(out);
|
|
49
|
-
src.start(now);
|
|
50
|
-
src.stop(now + 0.06);
|
|
51
|
-
},
|
|
52
|
-
dispose() {
|
|
53
|
-
out.disconnect();
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Whoosh — bandpass noise sweep for fast-moving objects. */
|
|
59
|
-
export function whoosh(
|
|
60
|
-
ctx: AudioContext,
|
|
61
|
-
dest: AudioNode,
|
|
62
|
-
options?: { pitch?: number; volume?: number; duration?: number; speed?: number },
|
|
63
|
-
) {
|
|
64
|
-
const pitch = options?.pitch ?? 600;
|
|
65
|
-
const volume = options?.volume ?? 0.3;
|
|
66
|
-
const duration = options?.duration ?? 0.2;
|
|
67
|
-
const speed = options?.speed ?? 1.0;
|
|
68
|
-
const noiseBuffer = createNoiseBuffer(ctx, 0.5);
|
|
69
|
-
|
|
70
|
-
const out = ctx.createGain();
|
|
71
|
-
out.gain.value = volume;
|
|
72
|
-
out.connect(dest);
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
play() {
|
|
76
|
-
const now = ctx.currentTime;
|
|
77
|
-
const src = ctx.createBufferSource();
|
|
78
|
-
src.buffer = noiseBuffer;
|
|
79
|
-
|
|
80
|
-
const filter = ctx.createBiquadFilter();
|
|
81
|
-
filter.type = 'bandpass';
|
|
82
|
-
filter.Q.value = 2;
|
|
83
|
-
filter.frequency.setValueAtTime(pitch * speed * 2, now);
|
|
84
|
-
filter.frequency.exponentialRampToValueAtTime(Math.max(pitch * 0.5, 20), now + duration);
|
|
85
|
-
|
|
86
|
-
// Triangle envelope: fade in 30%, fade out 70%
|
|
87
|
-
const peak = duration * 0.3;
|
|
88
|
-
const env = ctx.createGain();
|
|
89
|
-
env.gain.setValueAtTime(0.001, now);
|
|
90
|
-
env.gain.linearRampToValueAtTime(1, now + peak);
|
|
91
|
-
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
|
|
92
|
-
|
|
93
|
-
src.connect(filter);
|
|
94
|
-
filter.connect(env);
|
|
95
|
-
env.connect(out);
|
|
96
|
-
src.start(now);
|
|
97
|
-
src.stop(now + duration + 0.01);
|
|
98
|
-
},
|
|
99
|
-
dispose() {
|
|
100
|
-
out.disconnect();
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** Jump — quick ascending triangle tone. */
|
|
106
|
-
export function jump(
|
|
107
|
-
ctx: AudioContext,
|
|
108
|
-
dest: AudioNode,
|
|
109
|
-
options?: { pitch?: number; volume?: number; duration?: number },
|
|
110
|
-
) {
|
|
111
|
-
const pitch = options?.pitch ?? 150;
|
|
112
|
-
const volume = options?.volume ?? 0.3;
|
|
113
|
-
const duration = options?.duration ?? 0.15;
|
|
114
|
-
|
|
115
|
-
const out = ctx.createGain();
|
|
116
|
-
out.gain.value = volume;
|
|
117
|
-
out.connect(dest);
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
play() {
|
|
121
|
-
const now = ctx.currentTime;
|
|
122
|
-
const osc = ctx.createOscillator();
|
|
123
|
-
osc.type = 'triangle';
|
|
124
|
-
osc.frequency.setValueAtTime(pitch, now);
|
|
125
|
-
osc.frequency.exponentialRampToValueAtTime(pitch * 2, now + duration);
|
|
126
|
-
|
|
127
|
-
const env = ctx.createGain();
|
|
128
|
-
env.gain.setValueAtTime(0.8, now);
|
|
129
|
-
env.gain.exponentialRampToValueAtTime(0.001, now + duration);
|
|
130
|
-
|
|
131
|
-
osc.connect(env);
|
|
132
|
-
env.connect(out);
|
|
133
|
-
osc.start(now);
|
|
134
|
-
osc.stop(now + duration + 0.01);
|
|
135
|
-
},
|
|
136
|
-
dispose() {
|
|
137
|
-
out.disconnect();
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
}
|
package/src/audio/musical.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Musical sounds — rhythmic and melodic elements.
|
|
3
|
-
*
|
|
4
|
-
* bassPulse and arpeggio are continuous (play/stop).
|
|
5
|
-
* stinger is one-shot.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** Bass pulse — rhythmic low sine following a beat pattern. */
|
|
9
|
-
export function bassPulse(
|
|
10
|
-
ctx: AudioContext,
|
|
11
|
-
dest: AudioNode,
|
|
12
|
-
options?: { frequency?: number; bpm?: number; volume?: number; pattern?: number[] },
|
|
13
|
-
) {
|
|
14
|
-
const frequency = options?.frequency ?? 55;
|
|
15
|
-
const bpm = options?.bpm ?? 120;
|
|
16
|
-
const volume = options?.volume ?? 0.3;
|
|
17
|
-
const pattern = options?.pattern ?? [1, 0, 1, 0];
|
|
18
|
-
const beatInterval = 60 / bpm;
|
|
19
|
-
|
|
20
|
-
const out = ctx.createGain();
|
|
21
|
-
out.gain.value = volume;
|
|
22
|
-
out.connect(dest);
|
|
23
|
-
|
|
24
|
-
let osc: OscillatorNode | null = null;
|
|
25
|
-
let pulseGain: GainNode | null = null;
|
|
26
|
-
let timer: ReturnType<typeof setInterval> | null = null;
|
|
27
|
-
let step = 0;
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
play() {
|
|
31
|
-
if (osc) return;
|
|
32
|
-
osc = ctx.createOscillator();
|
|
33
|
-
osc.type = 'sine';
|
|
34
|
-
osc.frequency.value = frequency;
|
|
35
|
-
pulseGain = ctx.createGain();
|
|
36
|
-
pulseGain.gain.value = 0;
|
|
37
|
-
osc.connect(pulseGain);
|
|
38
|
-
pulseGain.connect(out);
|
|
39
|
-
osc.start();
|
|
40
|
-
step = 0;
|
|
41
|
-
|
|
42
|
-
const tick = () => {
|
|
43
|
-
if (!pulseGain) return;
|
|
44
|
-
const beat = pattern[step % pattern.length]!;
|
|
45
|
-
const now = ctx.currentTime;
|
|
46
|
-
if (beat) {
|
|
47
|
-
pulseGain.gain.setValueAtTime(1, now);
|
|
48
|
-
pulseGain.gain.exponentialRampToValueAtTime(0.01, now + beatInterval * 0.8);
|
|
49
|
-
}
|
|
50
|
-
step++;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
tick();
|
|
54
|
-
timer = setInterval(tick, beatInterval * 1000);
|
|
55
|
-
},
|
|
56
|
-
stop() {
|
|
57
|
-
if (timer) {
|
|
58
|
-
clearInterval(timer);
|
|
59
|
-
timer = null;
|
|
60
|
-
}
|
|
61
|
-
if (!osc) return;
|
|
62
|
-
const refs = { osc, pulseGain };
|
|
63
|
-
osc = null;
|
|
64
|
-
pulseGain = null;
|
|
65
|
-
setTimeout(() => {
|
|
66
|
-
try {
|
|
67
|
-
refs.osc.stop();
|
|
68
|
-
} catch {
|
|
69
|
-
/* already stopped */
|
|
70
|
-
}
|
|
71
|
-
refs.osc.disconnect();
|
|
72
|
-
refs.pulseGain?.disconnect();
|
|
73
|
-
}, 100);
|
|
74
|
-
},
|
|
75
|
-
dispose() {
|
|
76
|
-
if (timer) {
|
|
77
|
-
clearInterval(timer);
|
|
78
|
-
timer = null;
|
|
79
|
-
}
|
|
80
|
-
if (osc) {
|
|
81
|
-
try {
|
|
82
|
-
osc.stop();
|
|
83
|
-
} catch {
|
|
84
|
-
/* already stopped */
|
|
85
|
-
}
|
|
86
|
-
osc.disconnect();
|
|
87
|
-
}
|
|
88
|
-
pulseGain?.disconnect();
|
|
89
|
-
osc = null;
|
|
90
|
-
pulseGain = null;
|
|
91
|
-
out.disconnect();
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Arpeggio — repeating note sequence. */
|
|
97
|
-
export function arpeggio(
|
|
98
|
-
ctx: AudioContext,
|
|
99
|
-
dest: AudioNode,
|
|
100
|
-
options?: {
|
|
101
|
-
notes?: number[];
|
|
102
|
-
bpm?: number;
|
|
103
|
-
waveform?: OscillatorType;
|
|
104
|
-
volume?: number;
|
|
105
|
-
},
|
|
106
|
-
) {
|
|
107
|
-
const notes = options?.notes ?? [262, 330, 392, 523];
|
|
108
|
-
const bpm = options?.bpm ?? 140;
|
|
109
|
-
const waveform = options?.waveform ?? 'triangle';
|
|
110
|
-
const volume = options?.volume ?? 0.2;
|
|
111
|
-
const noteInterval = 60 / bpm;
|
|
112
|
-
|
|
113
|
-
const out = ctx.createGain();
|
|
114
|
-
out.gain.value = volume;
|
|
115
|
-
out.connect(dest);
|
|
116
|
-
|
|
117
|
-
let timer: ReturnType<typeof setInterval> | null = null;
|
|
118
|
-
let step = 0;
|
|
119
|
-
|
|
120
|
-
function playNote(freq: number) {
|
|
121
|
-
const now = ctx.currentTime;
|
|
122
|
-
const osc = ctx.createOscillator();
|
|
123
|
-
osc.type = waveform;
|
|
124
|
-
osc.frequency.value = freq;
|
|
125
|
-
|
|
126
|
-
const env = ctx.createGain();
|
|
127
|
-
env.gain.setValueAtTime(0.8, now);
|
|
128
|
-
env.gain.exponentialRampToValueAtTime(0.001, now + noteInterval * 0.9);
|
|
129
|
-
|
|
130
|
-
osc.connect(env);
|
|
131
|
-
env.connect(out);
|
|
132
|
-
osc.start(now);
|
|
133
|
-
osc.stop(now + noteInterval);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
play() {
|
|
138
|
-
if (timer) return;
|
|
139
|
-
step = 0;
|
|
140
|
-
const tick = () => {
|
|
141
|
-
playNote(notes[step % notes.length]!);
|
|
142
|
-
step++;
|
|
143
|
-
};
|
|
144
|
-
tick();
|
|
145
|
-
timer = setInterval(tick, noteInterval * 1000);
|
|
146
|
-
},
|
|
147
|
-
stop() {
|
|
148
|
-
if (timer) {
|
|
149
|
-
clearInterval(timer);
|
|
150
|
-
timer = null;
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
dispose() {
|
|
154
|
-
if (timer) {
|
|
155
|
-
clearInterval(timer);
|
|
156
|
-
timer = null;
|
|
157
|
-
}
|
|
158
|
-
out.disconnect();
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Stinger — one-shot ascending note sequence for events. */
|
|
164
|
-
export function stinger(
|
|
165
|
-
ctx: AudioContext,
|
|
166
|
-
dest: AudioNode,
|
|
167
|
-
options?: { notes?: number[]; volume?: number; tempo?: number },
|
|
168
|
-
) {
|
|
169
|
-
const notes = options?.notes ?? [392, 494, 587, 784];
|
|
170
|
-
const volume = options?.volume ?? 0.3;
|
|
171
|
-
const tempo = options?.tempo ?? 0.12;
|
|
172
|
-
|
|
173
|
-
const out = ctx.createGain();
|
|
174
|
-
out.gain.value = volume;
|
|
175
|
-
out.connect(dest);
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
play() {
|
|
179
|
-
const now = ctx.currentTime;
|
|
180
|
-
for (let i = 0; i < notes.length; i++) {
|
|
181
|
-
const t = now + i * tempo;
|
|
182
|
-
const osc = ctx.createOscillator();
|
|
183
|
-
osc.type = 'triangle';
|
|
184
|
-
osc.frequency.value = notes[i]!;
|
|
185
|
-
|
|
186
|
-
const env = ctx.createGain();
|
|
187
|
-
env.gain.setValueAtTime(0.8, t);
|
|
188
|
-
env.gain.exponentialRampToValueAtTime(0.001, t + tempo * 1.5);
|
|
189
|
-
|
|
190
|
-
osc.connect(env);
|
|
191
|
-
env.connect(out);
|
|
192
|
-
osc.start(t);
|
|
193
|
-
osc.stop(t + tempo * 1.5 + 0.01);
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
dispose() {
|
|
197
|
-
out.disconnect();
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
}
|
package/src/audio/ui-sounds.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UI sounds — short tonal feedback for interface interactions.
|
|
3
|
-
*
|
|
4
|
-
* All one-shot: call play() to trigger, dispose() to clean up.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** Click — short sine pip. */
|
|
8
|
-
export function uiClick(
|
|
9
|
-
ctx: AudioContext,
|
|
10
|
-
dest: AudioNode,
|
|
11
|
-
options?: { pitch?: number; volume?: number },
|
|
12
|
-
) {
|
|
13
|
-
const pitch = options?.pitch ?? 800;
|
|
14
|
-
const volume = options?.volume ?? 0.2;
|
|
15
|
-
|
|
16
|
-
const out = ctx.createGain();
|
|
17
|
-
out.gain.value = volume;
|
|
18
|
-
out.connect(dest);
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
play() {
|
|
22
|
-
const now = ctx.currentTime;
|
|
23
|
-
const osc = ctx.createOscillator();
|
|
24
|
-
osc.type = 'sine';
|
|
25
|
-
osc.frequency.value = pitch;
|
|
26
|
-
|
|
27
|
-
const env = ctx.createGain();
|
|
28
|
-
env.gain.setValueAtTime(1, now);
|
|
29
|
-
env.gain.exponentialRampToValueAtTime(0.001, now + 0.04);
|
|
30
|
-
|
|
31
|
-
osc.connect(env);
|
|
32
|
-
env.connect(out);
|
|
33
|
-
osc.start(now);
|
|
34
|
-
osc.stop(now + 0.05);
|
|
35
|
-
},
|
|
36
|
-
dispose() {
|
|
37
|
-
out.disconnect();
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Hover — softer, higher sine pip. */
|
|
43
|
-
export function uiHover(
|
|
44
|
-
ctx: AudioContext,
|
|
45
|
-
dest: AudioNode,
|
|
46
|
-
options?: { pitch?: number; volume?: number },
|
|
47
|
-
) {
|
|
48
|
-
const pitch = options?.pitch ?? 1200;
|
|
49
|
-
const volume = options?.volume ?? 0.1;
|
|
50
|
-
|
|
51
|
-
const out = ctx.createGain();
|
|
52
|
-
out.gain.value = volume;
|
|
53
|
-
out.connect(dest);
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
play() {
|
|
57
|
-
const now = ctx.currentTime;
|
|
58
|
-
const osc = ctx.createOscillator();
|
|
59
|
-
osc.type = 'sine';
|
|
60
|
-
osc.frequency.value = pitch;
|
|
61
|
-
|
|
62
|
-
const env = ctx.createGain();
|
|
63
|
-
env.gain.setValueAtTime(1, now);
|
|
64
|
-
env.gain.exponentialRampToValueAtTime(0.001, now + 0.03);
|
|
65
|
-
|
|
66
|
-
osc.connect(env);
|
|
67
|
-
env.connect(out);
|
|
68
|
-
osc.start(now);
|
|
69
|
-
osc.stop(now + 0.04);
|
|
70
|
-
},
|
|
71
|
-
dispose() {
|
|
72
|
-
out.disconnect();
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Success — two ascending tones (major third interval). */
|
|
78
|
-
export function uiSuccess(
|
|
79
|
-
ctx: AudioContext,
|
|
80
|
-
dest: AudioNode,
|
|
81
|
-
options?: { pitch?: number; volume?: number },
|
|
82
|
-
) {
|
|
83
|
-
const pitch = options?.pitch ?? 500;
|
|
84
|
-
const volume = options?.volume ?? 0.3;
|
|
85
|
-
|
|
86
|
-
const out = ctx.createGain();
|
|
87
|
-
out.gain.value = volume;
|
|
88
|
-
out.connect(dest);
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
play() {
|
|
92
|
-
const now = ctx.currentTime;
|
|
93
|
-
|
|
94
|
-
// First note
|
|
95
|
-
const osc1 = ctx.createOscillator();
|
|
96
|
-
osc1.type = 'sine';
|
|
97
|
-
osc1.frequency.value = pitch;
|
|
98
|
-
const env1 = ctx.createGain();
|
|
99
|
-
env1.gain.setValueAtTime(1, now);
|
|
100
|
-
env1.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
|
|
101
|
-
osc1.connect(env1);
|
|
102
|
-
env1.connect(out);
|
|
103
|
-
osc1.start(now);
|
|
104
|
-
osc1.stop(now + 0.16);
|
|
105
|
-
|
|
106
|
-
// Second note — major third above, delayed 0.1s
|
|
107
|
-
const osc2 = ctx.createOscillator();
|
|
108
|
-
osc2.type = 'sine';
|
|
109
|
-
osc2.frequency.value = pitch * (5 / 4);
|
|
110
|
-
const env2 = ctx.createGain();
|
|
111
|
-
env2.gain.setValueAtTime(0.001, now);
|
|
112
|
-
env2.gain.setValueAtTime(1, now + 0.1);
|
|
113
|
-
env2.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
|
|
114
|
-
osc2.connect(env2);
|
|
115
|
-
env2.connect(out);
|
|
116
|
-
osc2.start(now);
|
|
117
|
-
osc2.stop(now + 0.26);
|
|
118
|
-
},
|
|
119
|
-
dispose() {
|
|
120
|
-
out.disconnect();
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Error — two descending tones (minor second). */
|
|
126
|
-
export function uiError(
|
|
127
|
-
ctx: AudioContext,
|
|
128
|
-
dest: AudioNode,
|
|
129
|
-
options?: { pitch?: number; volume?: number },
|
|
130
|
-
) {
|
|
131
|
-
const pitch = options?.pitch ?? 400;
|
|
132
|
-
const volume = options?.volume ?? 0.3;
|
|
133
|
-
|
|
134
|
-
const out = ctx.createGain();
|
|
135
|
-
out.gain.value = volume;
|
|
136
|
-
out.connect(dest);
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
play() {
|
|
140
|
-
const now = ctx.currentTime;
|
|
141
|
-
|
|
142
|
-
// First note
|
|
143
|
-
const osc1 = ctx.createOscillator();
|
|
144
|
-
osc1.type = 'sine';
|
|
145
|
-
osc1.frequency.value = pitch;
|
|
146
|
-
const env1 = ctx.createGain();
|
|
147
|
-
env1.gain.setValueAtTime(1, now);
|
|
148
|
-
env1.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
|
|
149
|
-
osc1.connect(env1);
|
|
150
|
-
env1.connect(out);
|
|
151
|
-
osc1.start(now);
|
|
152
|
-
osc1.stop(now + 0.16);
|
|
153
|
-
|
|
154
|
-
// Second note — minor second below, delayed 0.1s
|
|
155
|
-
const osc2 = ctx.createOscillator();
|
|
156
|
-
osc2.type = 'sine';
|
|
157
|
-
osc2.frequency.value = pitch * 0.9;
|
|
158
|
-
const env2 = ctx.createGain();
|
|
159
|
-
env2.gain.setValueAtTime(0.001, now);
|
|
160
|
-
env2.gain.setValueAtTime(1, now + 0.1);
|
|
161
|
-
env2.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
|
|
162
|
-
osc2.connect(env2);
|
|
163
|
-
env2.connect(out);
|
|
164
|
-
osc2.start(now);
|
|
165
|
-
osc2.stop(now + 0.26);
|
|
166
|
-
},
|
|
167
|
-
dispose() {
|
|
168
|
-
out.disconnect();
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
}
|