lively 0.11.0 → 0.13.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.
@@ -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
+ }