lively 0.12.0 → 0.13.1
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/lively/application.rb +38 -0
- data/lib/lively/assets.rb +66 -14
- data/lib/lively/environment/application.rb +17 -2
- data/lib/lively/hello_world.rb +16 -0
- data/lib/lively/pages/index.rb +19 -1
- data/lib/lively/pages/index.xrb +2 -4
- data/lib/lively/version.rb +3 -2
- data/public/_components/@socketry/live/Live.js +42 -48
- data/public/_components/@socketry/live/package.json +4 -1
- data/public/_components/@socketry/live/readme.md +147 -31
- data/public/_components/@socketry/live-audio/Live/Audio/Controller.js +168 -0
- data/public/_components/@socketry/live-audio/Live/Audio/Library.js +748 -0
- data/public/_components/@socketry/live-audio/Live/Audio/Output.js +87 -0
- data/public/_components/@socketry/live-audio/Live/Audio/Sound.js +34 -0
- data/public/_components/@socketry/live-audio/Live/Audio/Visualizer.js +265 -0
- data/public/_components/@socketry/live-audio/Live/Audio.js +24 -0
- data/public/_components/@socketry/live-audio/package.json +35 -0
- data/public/_components/@socketry/live-audio/readme.md +250 -0
- data/public/application.js +4 -0
- data.tar.gz.sig +0 -0
- metadata +12 -4
- metadata.gz.sig +0 -0
- data/public/_components/@socketry/live/test/Live.js +0 -357
@@ -0,0 +1,748 @@
|
|
1
|
+
// Live Audio Sound Library
|
2
|
+
// Collection of pre-built synthesized sound effects
|
3
|
+
|
4
|
+
import { Sound } from '../Audio.js';
|
5
|
+
|
6
|
+
// Individual Sound Classes
|
7
|
+
export class JumpSound extends Sound {
|
8
|
+
start(output) {
|
9
|
+
const audioContext = output.audioContext;
|
10
|
+
const inputNode = output.input;
|
11
|
+
|
12
|
+
const oscillator = audioContext.createOscillator();
|
13
|
+
const gainNode = audioContext.createGain();
|
14
|
+
const antiClipGain = audioContext.createGain();
|
15
|
+
|
16
|
+
oscillator.type = 'square';
|
17
|
+
oscillator.frequency.setValueAtTime(200, audioContext.currentTime);
|
18
|
+
oscillator.frequency.exponentialRampToValueAtTime(600, audioContext.currentTime + 0.3);
|
19
|
+
|
20
|
+
antiClipGain.gain.value = 0.6;
|
21
|
+
this.createEnvelope(audioContext, gainNode, 0.01, 0.08, 0.3, 0.2, 0.3);
|
22
|
+
|
23
|
+
oscillator.connect(antiClipGain);
|
24
|
+
antiClipGain.connect(gainNode);
|
25
|
+
gainNode.connect(inputNode);
|
26
|
+
|
27
|
+
oscillator.start();
|
28
|
+
oscillator.stop(audioContext.currentTime + 0.3);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
export class CoinSound extends Sound {
|
33
|
+
start(output) {
|
34
|
+
const audioContext = output.audioContext;
|
35
|
+
const inputNode = output.input;
|
36
|
+
|
37
|
+
const osc1 = audioContext.createOscillator();
|
38
|
+
const osc2 = audioContext.createOscillator();
|
39
|
+
const gainNode = audioContext.createGain();
|
40
|
+
const gain1 = audioContext.createGain();
|
41
|
+
const gain2 = audioContext.createGain();
|
42
|
+
const antiClipGain = audioContext.createGain();
|
43
|
+
|
44
|
+
osc1.type = 'sine';
|
45
|
+
osc2.type = 'sine';
|
46
|
+
osc1.frequency.value = 523;
|
47
|
+
osc2.frequency.value = 659;
|
48
|
+
|
49
|
+
gain1.gain.value = 0.35;
|
50
|
+
gain2.gain.value = 0.35;
|
51
|
+
antiClipGain.gain.value = 1.0;
|
52
|
+
|
53
|
+
this.createEnvelope(audioContext, gainNode, 0.01, 0.05, 0.4, 0.15, 0.15);
|
54
|
+
|
55
|
+
osc1.connect(gain1);
|
56
|
+
osc2.connect(gain2);
|
57
|
+
gain1.connect(antiClipGain);
|
58
|
+
gain2.connect(antiClipGain);
|
59
|
+
antiClipGain.connect(gainNode);
|
60
|
+
gainNode.connect(inputNode);
|
61
|
+
|
62
|
+
osc1.start();
|
63
|
+
osc2.start();
|
64
|
+
osc1.stop(audioContext.currentTime + 0.15);
|
65
|
+
osc2.stop(audioContext.currentTime + 0.15);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
export class PowerUpSound extends Sound {
|
70
|
+
start(output) {
|
71
|
+
const audioContext = output.audioContext;
|
72
|
+
const inputNode = output.input;
|
73
|
+
|
74
|
+
const notes = [261.63, 329.63, 392.00, 523.25];
|
75
|
+
const noteDuration = 0.08;
|
76
|
+
|
77
|
+
notes.forEach((freq, index) => {
|
78
|
+
const oscillator = audioContext.createOscillator();
|
79
|
+
const gainNode = audioContext.createGain();
|
80
|
+
const antiClipGain = audioContext.createGain();
|
81
|
+
|
82
|
+
oscillator.type = 'triangle';
|
83
|
+
oscillator.frequency.value = freq;
|
84
|
+
|
85
|
+
antiClipGain.gain.value = 0.6;
|
86
|
+
|
87
|
+
const startTime = index * noteDuration;
|
88
|
+
const noteStartTime = audioContext.currentTime + startTime;
|
89
|
+
const noteEndTime = noteStartTime + noteDuration + 0.01;
|
90
|
+
|
91
|
+
gainNode.gain.setValueAtTime(0, noteStartTime);
|
92
|
+
gainNode.gain.linearRampToValueAtTime(1, noteStartTime + 0.005);
|
93
|
+
gainNode.gain.setValueAtTime(1, noteEndTime - 0.01);
|
94
|
+
gainNode.gain.linearRampToValueAtTime(0, noteEndTime);
|
95
|
+
|
96
|
+
oscillator.connect(antiClipGain);
|
97
|
+
antiClipGain.connect(gainNode);
|
98
|
+
gainNode.connect(inputNode);
|
99
|
+
|
100
|
+
oscillator.start(noteStartTime);
|
101
|
+
oscillator.stop(noteEndTime);
|
102
|
+
});
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
export class DeathSound extends Sound {
|
107
|
+
start(output) {
|
108
|
+
const audioContext = output.audioContext;
|
109
|
+
const inputNode = output.input;
|
110
|
+
|
111
|
+
const oscillator = audioContext.createOscillator();
|
112
|
+
const gainNode = audioContext.createGain();
|
113
|
+
const antiClipGain = audioContext.createGain();
|
114
|
+
|
115
|
+
oscillator.type = 'sawtooth';
|
116
|
+
oscillator.frequency.setValueAtTime(400, audioContext.currentTime);
|
117
|
+
oscillator.frequency.exponentialRampToValueAtTime(100, audioContext.currentTime + 1.2);
|
118
|
+
|
119
|
+
antiClipGain.gain.value = 0.47;
|
120
|
+
this.createEnvelope(audioContext, gainNode, 0.1, 0.3, 0.7, 0.8, 1.2);
|
121
|
+
|
122
|
+
oscillator.connect(antiClipGain);
|
123
|
+
antiClipGain.connect(gainNode);
|
124
|
+
gainNode.connect(inputNode);
|
125
|
+
|
126
|
+
oscillator.start();
|
127
|
+
oscillator.stop(audioContext.currentTime + 1.2);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
export class ExplosionSound extends Sound {
|
132
|
+
start(output) {
|
133
|
+
const audioContext = output.audioContext;
|
134
|
+
const inputNode = output.input;
|
135
|
+
|
136
|
+
const duration = 0.8;
|
137
|
+
const bufferSize = audioContext.sampleRate * duration;
|
138
|
+
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
|
139
|
+
const data = buffer.getChannelData(0);
|
140
|
+
|
141
|
+
// Generate white noise with exponential decay
|
142
|
+
for (let i = 0; i < bufferSize; i++) {
|
143
|
+
data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 2);
|
144
|
+
}
|
145
|
+
|
146
|
+
const noiseSource = audioContext.createBufferSource();
|
147
|
+
const deepRumble = audioContext.createOscillator();
|
148
|
+
const midRumble = audioContext.createOscillator();
|
149
|
+
const lowRumble = audioContext.createOscillator();
|
150
|
+
const filter = audioContext.createBiquadFilter();
|
151
|
+
const deepGain = audioContext.createGain();
|
152
|
+
const midGain = audioContext.createGain();
|
153
|
+
const lowGain = audioContext.createGain();
|
154
|
+
const noiseGain = audioContext.createGain();
|
155
|
+
const gainNode = audioContext.createGain();
|
156
|
+
const antiClipGain = audioContext.createGain();
|
157
|
+
|
158
|
+
noiseSource.buffer = buffer;
|
159
|
+
|
160
|
+
deepRumble.type = 'sine';
|
161
|
+
deepRumble.frequency.setValueAtTime(25, audioContext.currentTime);
|
162
|
+
deepRumble.frequency.exponentialRampToValueAtTime(15, audioContext.currentTime + duration);
|
163
|
+
deepGain.gain.value = 0.5;
|
164
|
+
|
165
|
+
midRumble.type = 'square';
|
166
|
+
midRumble.frequency.setValueAtTime(50, audioContext.currentTime);
|
167
|
+
midRumble.frequency.exponentialRampToValueAtTime(30, audioContext.currentTime + duration);
|
168
|
+
midGain.gain.value = 0.25;
|
169
|
+
|
170
|
+
lowRumble.type = 'square';
|
171
|
+
lowRumble.frequency.setValueAtTime(80, audioContext.currentTime);
|
172
|
+
lowRumble.frequency.exponentialRampToValueAtTime(45, audioContext.currentTime + duration);
|
173
|
+
lowGain.gain.value = 0.2;
|
174
|
+
|
175
|
+
noiseGain.gain.value = 0.4;
|
176
|
+
|
177
|
+
filter.type = 'lowpass';
|
178
|
+
filter.frequency.setValueAtTime(1200, audioContext.currentTime);
|
179
|
+
filter.frequency.exponentialRampToValueAtTime(120, audioContext.currentTime + duration);
|
180
|
+
|
181
|
+
antiClipGain.gain.value = 0.7;
|
182
|
+
this.createEnvelope(audioContext, gainNode, 0.01, 0.2, 0.4, 0.6, duration);
|
183
|
+
|
184
|
+
deepRumble.connect(deepGain);
|
185
|
+
midRumble.connect(midGain);
|
186
|
+
lowRumble.connect(lowGain);
|
187
|
+
noiseSource.connect(noiseGain);
|
188
|
+
deepGain.connect(filter);
|
189
|
+
midGain.connect(filter);
|
190
|
+
lowGain.connect(filter);
|
191
|
+
noiseGain.connect(filter);
|
192
|
+
filter.connect(antiClipGain);
|
193
|
+
antiClipGain.connect(gainNode);
|
194
|
+
gainNode.connect(inputNode);
|
195
|
+
|
196
|
+
deepRumble.start();
|
197
|
+
midRumble.start();
|
198
|
+
lowRumble.start();
|
199
|
+
noiseSource.start();
|
200
|
+
deepRumble.stop(audioContext.currentTime + duration);
|
201
|
+
midRumble.stop(audioContext.currentTime + duration);
|
202
|
+
lowRumble.stop(audioContext.currentTime + duration);
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
export class LaserSound extends Sound {
|
207
|
+
start(output) {
|
208
|
+
const audioContext = output.audioContext;
|
209
|
+
const inputNode = output.input;
|
210
|
+
|
211
|
+
const oscillator = audioContext.createOscillator();
|
212
|
+
const gainNode = audioContext.createGain();
|
213
|
+
const antiClipGain = audioContext.createGain();
|
214
|
+
|
215
|
+
oscillator.type = 'sawtooth';
|
216
|
+
oscillator.frequency.setValueAtTime(1200, audioContext.currentTime);
|
217
|
+
oscillator.frequency.exponentialRampToValueAtTime(300, audioContext.currentTime + 0.25);
|
218
|
+
|
219
|
+
antiClipGain.gain.value = 0.5;
|
220
|
+
this.createEnvelope(audioContext, gainNode, 0.005, 0.02, 0.8, 0.225, 0.25);
|
221
|
+
|
222
|
+
oscillator.connect(antiClipGain);
|
223
|
+
antiClipGain.connect(gainNode);
|
224
|
+
gainNode.connect(inputNode);
|
225
|
+
|
226
|
+
oscillator.start();
|
227
|
+
oscillator.stop(audioContext.currentTime + 0.25);
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
export class BeepSound extends Sound {
|
232
|
+
start(output) {
|
233
|
+
const audioContext = output.audioContext;
|
234
|
+
const inputNode = output.input;
|
235
|
+
|
236
|
+
const oscillator = audioContext.createOscillator();
|
237
|
+
const gainNode = audioContext.createGain();
|
238
|
+
const antiClipGain = audioContext.createGain();
|
239
|
+
|
240
|
+
oscillator.type = 'sine';
|
241
|
+
oscillator.frequency.value = 800;
|
242
|
+
|
243
|
+
antiClipGain.gain.value = 0.7;
|
244
|
+
this.createEnvelope(audioContext, gainNode, 0.01, 0.02, 0.9, 0.07, 0.1);
|
245
|
+
|
246
|
+
oscillator.connect(antiClipGain);
|
247
|
+
antiClipGain.connect(gainNode);
|
248
|
+
gainNode.connect(inputNode);
|
249
|
+
|
250
|
+
oscillator.start();
|
251
|
+
oscillator.stop(audioContext.currentTime + 0.1);
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
export class BlipSound extends Sound {
|
256
|
+
start(output) {
|
257
|
+
const audioContext = output.audioContext;
|
258
|
+
const inputNode = output.input;
|
259
|
+
|
260
|
+
const oscillator = audioContext.createOscillator();
|
261
|
+
const gainNode = audioContext.createGain();
|
262
|
+
const antiClipGain = audioContext.createGain();
|
263
|
+
|
264
|
+
oscillator.type = 'square';
|
265
|
+
oscillator.frequency.value = 1000;
|
266
|
+
|
267
|
+
antiClipGain.gain.value = 0.6;
|
268
|
+
this.createEnvelope(audioContext, gainNode, 0.005, 0.01, 0.7, 0.035, 0.05);
|
269
|
+
|
270
|
+
oscillator.connect(antiClipGain);
|
271
|
+
antiClipGain.connect(gainNode);
|
272
|
+
gainNode.connect(inputNode);
|
273
|
+
|
274
|
+
oscillator.start();
|
275
|
+
oscillator.stop(audioContext.currentTime + 0.05);
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
export class MeowSound extends Sound {
|
280
|
+
start(output) {
|
281
|
+
const audioContext = output.audioContext;
|
282
|
+
const inputNode = output.input;
|
283
|
+
|
284
|
+
const duration = 0.6;
|
285
|
+
const osc1 = audioContext.createOscillator();
|
286
|
+
const osc2 = audioContext.createOscillator();
|
287
|
+
const filter = audioContext.createBiquadFilter();
|
288
|
+
const gainNode = audioContext.createGain();
|
289
|
+
const gain1 = audioContext.createGain();
|
290
|
+
const gain2 = audioContext.createGain();
|
291
|
+
const antiClipGain = audioContext.createGain();
|
292
|
+
|
293
|
+
osc1.type = 'sawtooth';
|
294
|
+
osc2.type = 'triangle';
|
295
|
+
|
296
|
+
gain1.gain.value = 0.3;
|
297
|
+
gain2.gain.value = 0.2;
|
298
|
+
|
299
|
+
osc1.frequency.setValueAtTime(300, audioContext.currentTime);
|
300
|
+
osc1.frequency.linearRampToValueAtTime(800, audioContext.currentTime + 0.1);
|
301
|
+
osc1.frequency.linearRampToValueAtTime(400, audioContext.currentTime + duration);
|
302
|
+
|
303
|
+
osc2.frequency.setValueAtTime(600, audioContext.currentTime);
|
304
|
+
osc2.frequency.linearRampToValueAtTime(1200, audioContext.currentTime + 0.1);
|
305
|
+
osc2.frequency.linearRampToValueAtTime(600, audioContext.currentTime + duration);
|
306
|
+
|
307
|
+
filter.type = 'bandpass';
|
308
|
+
filter.frequency.setValueAtTime(1500, audioContext.currentTime);
|
309
|
+
filter.frequency.linearRampToValueAtTime(2500, audioContext.currentTime + 0.1);
|
310
|
+
filter.frequency.linearRampToValueAtTime(1000, audioContext.currentTime + duration);
|
311
|
+
filter.Q.value = 3;
|
312
|
+
|
313
|
+
antiClipGain.gain.value = 4.8;
|
314
|
+
this.createEnvelope(audioContext, gainNode, 0.05, 0.1, 0.7, 0.45, duration);
|
315
|
+
|
316
|
+
osc1.connect(gain1);
|
317
|
+
osc2.connect(gain2);
|
318
|
+
gain1.connect(filter);
|
319
|
+
gain2.connect(filter);
|
320
|
+
filter.connect(antiClipGain);
|
321
|
+
antiClipGain.connect(gainNode);
|
322
|
+
gainNode.connect(inputNode);
|
323
|
+
|
324
|
+
osc1.start();
|
325
|
+
osc2.start();
|
326
|
+
osc1.stop(audioContext.currentTime + duration);
|
327
|
+
osc2.stop(audioContext.currentTime + duration);
|
328
|
+
}
|
329
|
+
}
|
330
|
+
|
331
|
+
export class BarkSound extends Sound {
|
332
|
+
start(output) {
|
333
|
+
const audioContext = output.audioContext;
|
334
|
+
const inputNode = output.input;
|
335
|
+
|
336
|
+
const duration = 0.12;
|
337
|
+
const oscillator = audioContext.createOscillator();
|
338
|
+
const filter1 = audioContext.createBiquadFilter();
|
339
|
+
const filter2 = audioContext.createBiquadFilter();
|
340
|
+
const gainNode = audioContext.createGain();
|
341
|
+
const boostGain = audioContext.createGain();
|
342
|
+
|
343
|
+
oscillator.type = 'sawtooth';
|
344
|
+
oscillator.frequency.setValueAtTime(140, audioContext.currentTime);
|
345
|
+
oscillator.frequency.exponentialRampToValueAtTime(90, audioContext.currentTime + duration);
|
346
|
+
|
347
|
+
filter1.type = 'bandpass';
|
348
|
+
filter1.frequency.value = 900;
|
349
|
+
filter1.Q.value = 4;
|
350
|
+
|
351
|
+
filter2.type = 'bandpass';
|
352
|
+
filter2.frequency.value = 1400;
|
353
|
+
filter2.Q.value = 2;
|
354
|
+
|
355
|
+
boostGain.gain.value = 4.8;
|
356
|
+
|
357
|
+
this.createEnvelope(audioContext, gainNode, 0.005, 0.04, 0.4, 0.1, duration);
|
358
|
+
|
359
|
+
oscillator.connect(filter1);
|
360
|
+
filter1.connect(filter2);
|
361
|
+
filter2.connect(boostGain);
|
362
|
+
boostGain.connect(gainNode);
|
363
|
+
gainNode.connect(inputNode);
|
364
|
+
|
365
|
+
oscillator.start();
|
366
|
+
oscillator.stop(audioContext.currentTime + duration);
|
367
|
+
}
|
368
|
+
}
|
369
|
+
|
370
|
+
export class DuckSound extends Sound {
|
371
|
+
start(output) {
|
372
|
+
const audioContext = output.audioContext;
|
373
|
+
const inputNode = output.input;
|
374
|
+
|
375
|
+
const duration = 0.3;
|
376
|
+
const oscillator = audioContext.createOscillator();
|
377
|
+
const modulator = audioContext.createOscillator();
|
378
|
+
const modulatorGain = audioContext.createGain();
|
379
|
+
const filter = audioContext.createBiquadFilter();
|
380
|
+
const gainNode = audioContext.createGain();
|
381
|
+
const antiClipGain = audioContext.createGain();
|
382
|
+
|
383
|
+
oscillator.type = 'square';
|
384
|
+
oscillator.frequency.value = 200;
|
385
|
+
|
386
|
+
modulator.type = 'sine';
|
387
|
+
modulator.frequency.value = 15;
|
388
|
+
modulatorGain.gain.value = 30;
|
389
|
+
modulator.connect(modulatorGain);
|
390
|
+
modulatorGain.connect(oscillator.frequency);
|
391
|
+
|
392
|
+
filter.type = 'lowpass';
|
393
|
+
filter.frequency.value = 800;
|
394
|
+
filter.Q.value = 2;
|
395
|
+
|
396
|
+
antiClipGain.gain.value = 0.62;
|
397
|
+
|
398
|
+
this.createEnvelope(audioContext, gainNode, 0.02, 0.1, 0.3, 0.18, duration);
|
399
|
+
|
400
|
+
oscillator.connect(filter);
|
401
|
+
filter.connect(antiClipGain);
|
402
|
+
antiClipGain.connect(gainNode);
|
403
|
+
gainNode.connect(inputNode);
|
404
|
+
|
405
|
+
oscillator.start();
|
406
|
+
modulator.start();
|
407
|
+
oscillator.stop(audioContext.currentTime + duration);
|
408
|
+
modulator.stop(audioContext.currentTime + duration);
|
409
|
+
}
|
410
|
+
}
|
411
|
+
|
412
|
+
export class AlienSound extends Sound {
|
413
|
+
start(output) {
|
414
|
+
const audioContext = output.audioContext;
|
415
|
+
const inputNode = output.input;
|
416
|
+
|
417
|
+
const duration = 0.8;
|
418
|
+
const carrier = audioContext.createOscillator();
|
419
|
+
const modulator = audioContext.createOscillator();
|
420
|
+
const modulatorGain = audioContext.createGain();
|
421
|
+
const ringMod = audioContext.createGain();
|
422
|
+
const filter = audioContext.createBiquadFilter();
|
423
|
+
const gainNode = audioContext.createGain();
|
424
|
+
const volumeControl = audioContext.createGain();
|
425
|
+
|
426
|
+
carrier.type = 'sine';
|
427
|
+
carrier.frequency.setValueAtTime(150, audioContext.currentTime);
|
428
|
+
carrier.frequency.exponentialRampToValueAtTime(800, audioContext.currentTime + 0.4);
|
429
|
+
carrier.frequency.exponentialRampToValueAtTime(100, audioContext.currentTime + duration);
|
430
|
+
|
431
|
+
modulator.type = 'sine';
|
432
|
+
modulator.frequency.setValueAtTime(30, audioContext.currentTime);
|
433
|
+
modulator.frequency.linearRampToValueAtTime(80, audioContext.currentTime + duration);
|
434
|
+
|
435
|
+
modulatorGain.gain.value = 0.5;
|
436
|
+
|
437
|
+
filter.type = 'highpass';
|
438
|
+
filter.frequency.value = 100;
|
439
|
+
|
440
|
+
volumeControl.gain.value = 0.48;
|
441
|
+
|
442
|
+
modulator.connect(modulatorGain);
|
443
|
+
modulatorGain.connect(ringMod.gain);
|
444
|
+
carrier.connect(ringMod);
|
445
|
+
|
446
|
+
this.createEnvelope(audioContext, gainNode, 0.1, 0.2, 0.6, 0.5, duration);
|
447
|
+
|
448
|
+
ringMod.connect(filter);
|
449
|
+
filter.connect(volumeControl);
|
450
|
+
volumeControl.connect(gainNode);
|
451
|
+
gainNode.connect(inputNode);
|
452
|
+
|
453
|
+
carrier.start();
|
454
|
+
modulator.start();
|
455
|
+
carrier.stop(audioContext.currentTime + duration);
|
456
|
+
modulator.stop(audioContext.currentTime + duration);
|
457
|
+
}
|
458
|
+
}
|
459
|
+
|
460
|
+
export class RoarSound extends Sound {
|
461
|
+
start(output) {
|
462
|
+
const audioContext = output.audioContext;
|
463
|
+
const inputNode = output.input;
|
464
|
+
|
465
|
+
const duration = 1.0;
|
466
|
+
const lowOsc = audioContext.createOscillator();
|
467
|
+
const highOsc = audioContext.createOscillator();
|
468
|
+
const noiseBuffer = audioContext.createBuffer(1, audioContext.sampleRate * duration, audioContext.sampleRate);
|
469
|
+
const noiseData = noiseBuffer.getChannelData(0);
|
470
|
+
|
471
|
+
// Generate filtered noise for texture
|
472
|
+
for (let i = 0; i < noiseData.length; i++) {
|
473
|
+
const t = i / audioContext.sampleRate;
|
474
|
+
const envelope = Math.sin(Math.PI * t / duration);
|
475
|
+
noiseData[i] = (Math.random() * 2 - 1) * 0.3 * envelope;
|
476
|
+
}
|
477
|
+
|
478
|
+
const noiseSource = audioContext.createBufferSource();
|
479
|
+
noiseSource.buffer = noiseBuffer;
|
480
|
+
|
481
|
+
const lowGain = audioContext.createGain();
|
482
|
+
const highGain = audioContext.createGain();
|
483
|
+
const noiseGain = audioContext.createGain();
|
484
|
+
const filter = audioContext.createBiquadFilter();
|
485
|
+
const gainNode = audioContext.createGain();
|
486
|
+
const antiClipGain = audioContext.createGain();
|
487
|
+
|
488
|
+
// Bass heavy square wave with frequency sweep
|
489
|
+
lowOsc.type = 'square';
|
490
|
+
lowOsc.frequency.setValueAtTime(60, audioContext.currentTime);
|
491
|
+
lowOsc.frequency.linearRampToValueAtTime(90, audioContext.currentTime + 0.3);
|
492
|
+
lowOsc.frequency.linearRampToValueAtTime(45, audioContext.currentTime + duration);
|
493
|
+
lowGain.gain.value = 0.7; // Strong bass presence
|
494
|
+
|
495
|
+
// Higher pitch square wave for harmonic richness
|
496
|
+
highOsc.type = 'square';
|
497
|
+
highOsc.frequency.setValueAtTime(180, audioContext.currentTime);
|
498
|
+
highOsc.frequency.linearRampToValueAtTime(270, audioContext.currentTime + 0.3);
|
499
|
+
highOsc.frequency.linearRampToValueAtTime(135, audioContext.currentTime + duration);
|
500
|
+
highGain.gain.value = 0.4; // Supporting harmonics
|
501
|
+
|
502
|
+
noiseGain.gain.value = 0.3; // Texture layer
|
503
|
+
|
504
|
+
// Light lowpass filtering to tame square wave harshness while keeping bass
|
505
|
+
filter.type = 'lowpass';
|
506
|
+
filter.frequency.setValueAtTime(1200, audioContext.currentTime);
|
507
|
+
filter.frequency.linearRampToValueAtTime(800, audioContext.currentTime + duration);
|
508
|
+
filter.Q.value = 1;
|
509
|
+
|
510
|
+
// Reduce gain to prevent clipping (was hitting 100%)
|
511
|
+
antiClipGain.gain.value = 0.5; // Reduced from 1.0 to prevent clipping
|
512
|
+
this.createEnvelope(audioContext, gainNode, 0.1, 0.2, 0.8, 0.7, duration);
|
513
|
+
|
514
|
+
lowOsc.connect(lowGain);
|
515
|
+
highOsc.connect(highGain);
|
516
|
+
noiseSource.connect(noiseGain);
|
517
|
+
lowGain.connect(filter);
|
518
|
+
highGain.connect(filter);
|
519
|
+
noiseGain.connect(filter);
|
520
|
+
filter.connect(antiClipGain);
|
521
|
+
antiClipGain.connect(gainNode);
|
522
|
+
gainNode.connect(inputNode);
|
523
|
+
|
524
|
+
lowOsc.start();
|
525
|
+
highOsc.start();
|
526
|
+
noiseSource.start();
|
527
|
+
lowOsc.stop(audioContext.currentTime + duration);
|
528
|
+
highOsc.stop(audioContext.currentTime + duration);
|
529
|
+
}
|
530
|
+
}
|
531
|
+
|
532
|
+
export class ChirpSound extends Sound {
|
533
|
+
start(output) {
|
534
|
+
const audioContext = output.audioContext;
|
535
|
+
const inputNode = output.input;
|
536
|
+
|
537
|
+
const duration = 0.15;
|
538
|
+
const oscillator = audioContext.createOscillator();
|
539
|
+
const gainNode = audioContext.createGain();
|
540
|
+
const antiClipGain = audioContext.createGain();
|
541
|
+
|
542
|
+
oscillator.type = 'sine';
|
543
|
+
oscillator.frequency.setValueAtTime(2000, audioContext.currentTime);
|
544
|
+
oscillator.frequency.exponentialRampToValueAtTime(4000, audioContext.currentTime + 0.05);
|
545
|
+
oscillator.frequency.exponentialRampToValueAtTime(3000, audioContext.currentTime + duration);
|
546
|
+
|
547
|
+
antiClipGain.gain.value = 0.5;
|
548
|
+
this.createEnvelope(audioContext, gainNode, 0.01, 0.03, 0.5, 0.11, duration);
|
549
|
+
|
550
|
+
oscillator.connect(antiClipGain);
|
551
|
+
antiClipGain.connect(gainNode);
|
552
|
+
gainNode.connect(inputNode);
|
553
|
+
|
554
|
+
oscillator.start();
|
555
|
+
oscillator.stop(audioContext.currentTime + duration);
|
556
|
+
}
|
557
|
+
}
|
558
|
+
|
559
|
+
export class HowlSound extends Sound {
|
560
|
+
start(output) {
|
561
|
+
const audioContext = output.audioContext;
|
562
|
+
const inputNode = output.input;
|
563
|
+
|
564
|
+
const duration = 2.0;
|
565
|
+
const osc1 = audioContext.createOscillator();
|
566
|
+
const osc2 = audioContext.createOscillator();
|
567
|
+
const filter = audioContext.createBiquadFilter();
|
568
|
+
const gainNode = audioContext.createGain();
|
569
|
+
const gain1 = audioContext.createGain();
|
570
|
+
const gain2 = audioContext.createGain();
|
571
|
+
const antiClipGain = audioContext.createGain();
|
572
|
+
|
573
|
+
osc1.type = 'sine';
|
574
|
+
osc2.type = 'triangle';
|
575
|
+
|
576
|
+
gain1.gain.value = 0.4;
|
577
|
+
gain2.gain.value = 0.3;
|
578
|
+
|
579
|
+
const baseFreq = 330; // Increased from 220 for higher pitch
|
580
|
+
osc1.frequency.setValueAtTime(baseFreq, audioContext.currentTime);
|
581
|
+
osc1.frequency.linearRampToValueAtTime(baseFreq * 1.5, audioContext.currentTime + 0.5);
|
582
|
+
osc1.frequency.linearRampToValueAtTime(baseFreq * 0.8, audioContext.currentTime + duration);
|
583
|
+
|
584
|
+
osc2.frequency.setValueAtTime(baseFreq * 1.5, audioContext.currentTime);
|
585
|
+
osc2.frequency.linearRampToValueAtTime(baseFreq * 2, audioContext.currentTime + 0.5);
|
586
|
+
osc2.frequency.linearRampToValueAtTime(baseFreq, audioContext.currentTime + duration);
|
587
|
+
|
588
|
+
filter.type = 'bandpass';
|
589
|
+
filter.frequency.setValueAtTime(800, audioContext.currentTime);
|
590
|
+
filter.frequency.linearRampToValueAtTime(1200, audioContext.currentTime + 0.5);
|
591
|
+
filter.frequency.linearRampToValueAtTime(600, audioContext.currentTime + duration);
|
592
|
+
filter.Q.value = 2;
|
593
|
+
|
594
|
+
antiClipGain.gain.value = 4.0; // Increased from 0.7 to boost from 7% to ~80%
|
595
|
+
this.createEnvelope(audioContext, gainNode, 0.2, 0.3, 0.8, 1.5, duration);
|
596
|
+
|
597
|
+
osc1.connect(gain1);
|
598
|
+
osc2.connect(gain2);
|
599
|
+
gain1.connect(filter);
|
600
|
+
gain2.connect(filter);
|
601
|
+
filter.connect(antiClipGain);
|
602
|
+
antiClipGain.connect(gainNode);
|
603
|
+
gainNode.connect(inputNode);
|
604
|
+
|
605
|
+
osc1.start();
|
606
|
+
osc2.start();
|
607
|
+
osc1.stop(audioContext.currentTime + duration);
|
608
|
+
osc2.stop(audioContext.currentTime + duration);
|
609
|
+
}
|
610
|
+
}
|
611
|
+
|
612
|
+
// Sample Sound class - loads and plays audio files (one-shot by default)
|
613
|
+
export class SampleSound extends Sound {
|
614
|
+
constructor(url, volume = 0.8) {
|
615
|
+
super();
|
616
|
+
this.url = url;
|
617
|
+
this.volume = volume;
|
618
|
+
this.source = null;
|
619
|
+
this.gainNode = null;
|
620
|
+
this.audioBuffer = null;
|
621
|
+
this.isPlaying = false;
|
622
|
+
}
|
623
|
+
|
624
|
+
async start(output) {
|
625
|
+
if (this.isPlaying) {
|
626
|
+
console.log('Sample is already playing');
|
627
|
+
return;
|
628
|
+
}
|
629
|
+
|
630
|
+
const audioContext = output.audioContext;
|
631
|
+
const inputNode = output.input;
|
632
|
+
|
633
|
+
try {
|
634
|
+
this.gainNode = audioContext.createGain();
|
635
|
+
this.gainNode.gain.value = this.volume;
|
636
|
+
this.gainNode.connect(inputNode);
|
637
|
+
|
638
|
+
if (!this.audioBuffer) {
|
639
|
+
await this.loadAudioBuffer(audioContext);
|
640
|
+
}
|
641
|
+
|
642
|
+
this.playAudioBuffer(audioContext);
|
643
|
+
console.log('Sample started:', this.url);
|
644
|
+
} catch (error) {
|
645
|
+
console.error('Failed to start sample:', error);
|
646
|
+
}
|
647
|
+
}
|
648
|
+
|
649
|
+
async loadAudioBuffer(audioContext) {
|
650
|
+
console.log('Loading sample from:', this.url);
|
651
|
+
|
652
|
+
try {
|
653
|
+
// Add a timeout to prevent hanging
|
654
|
+
const controller = new AbortController();
|
655
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
|
656
|
+
|
657
|
+
const response = await fetch(this.url, {
|
658
|
+
signal: controller.signal
|
659
|
+
});
|
660
|
+
clearTimeout(timeoutId);
|
661
|
+
|
662
|
+
if (!response.ok) {
|
663
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
664
|
+
}
|
665
|
+
|
666
|
+
const arrayBuffer = await response.arrayBuffer();
|
667
|
+
this.audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
668
|
+
|
669
|
+
console.log(`Sample loaded: ${this.audioBuffer.duration.toFixed(2)}s`);
|
670
|
+
} catch (error) {
|
671
|
+
console.warn('Failed to load sample:', error.message);
|
672
|
+
// Create a dummy silent buffer so the sound doesn't fail completely
|
673
|
+
this.audioBuffer = audioContext.createBuffer(1, audioContext.sampleRate * 0.1, audioContext.sampleRate);
|
674
|
+
}
|
675
|
+
}
|
676
|
+
|
677
|
+
playAudioBuffer(audioContext) {
|
678
|
+
this.source = audioContext.createBufferSource();
|
679
|
+
this.source.buffer = this.audioBuffer;
|
680
|
+
|
681
|
+
// Configure looping (overridden in subclasses)
|
682
|
+
this.configurePlayback(this.source);
|
683
|
+
|
684
|
+
this.source.connect(this.gainNode);
|
685
|
+
|
686
|
+
this.source.onended = () => {
|
687
|
+
this.isPlaying = false;
|
688
|
+
this.source = null;
|
689
|
+
};
|
690
|
+
|
691
|
+
this.source.start(0);
|
692
|
+
this.isPlaying = true;
|
693
|
+
}
|
694
|
+
|
695
|
+
// Override this method in subclasses to configure looping behavior
|
696
|
+
configurePlayback(source) {
|
697
|
+
// Default: no looping (one-shot)
|
698
|
+
source.loop = false;
|
699
|
+
}
|
700
|
+
|
701
|
+
stop() {
|
702
|
+
if (this.source && this.isPlaying) {
|
703
|
+
this.source.stop();
|
704
|
+
this.source.disconnect();
|
705
|
+
this.source = null;
|
706
|
+
|
707
|
+
if (this.gainNode) {
|
708
|
+
this.gainNode.disconnect();
|
709
|
+
this.gainNode = null;
|
710
|
+
}
|
711
|
+
|
712
|
+
this.isPlaying = false;
|
713
|
+
}
|
714
|
+
}
|
715
|
+
|
716
|
+
setVolume(volume) {
|
717
|
+
this.volume = volume;
|
718
|
+
if (this.gainNode) {
|
719
|
+
this.gainNode.gain.value = volume;
|
720
|
+
}
|
721
|
+
}
|
722
|
+
}
|
723
|
+
|
724
|
+
// Background Music class extending SampleSound with looping functionality
|
725
|
+
export class BackgroundMusicSound extends SampleSound {
|
726
|
+
constructor(url, loopStart = 0, loopEnd = 0) {
|
727
|
+
super(url, 0.8); // Default volume for background music
|
728
|
+
this.loopStart = loopStart;
|
729
|
+
this.loopEnd = loopEnd;
|
730
|
+
}
|
731
|
+
|
732
|
+
// Override to configure looping with specific loop points
|
733
|
+
configurePlayback(source) {
|
734
|
+
source.loop = true;
|
735
|
+
source.loopStart = this.loopStart;
|
736
|
+
source.loopEnd = this.loopEnd;
|
737
|
+
}
|
738
|
+
|
739
|
+
async start(output) {
|
740
|
+
if (this.isPlaying) {
|
741
|
+
console.log('Background music is already playing');
|
742
|
+
return;
|
743
|
+
}
|
744
|
+
|
745
|
+
await super.start(output);
|
746
|
+
console.log('Background music started with loop points:', this.loopStart, 'to', this.loopEnd);
|
747
|
+
}
|
748
|
+
}
|