cacophony 0.15.2 → 0.16.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.
package/README.md CHANGED
@@ -5,16 +5,16 @@ Cacophony is a powerful and intuitive audio library designed for modern web appl
5
5
 
6
6
  ## Key Features
7
7
 
8
- - **Versatile Audio Source Handling**: Manage audio from various sources including `AudioBuffer`, URL strings, synthesizers, and live microphone input.
9
- - **Comprehensive Playback Control**: Play, stop, pause, resume, loop, and seek within audio with ease.
10
- - **3D Audio Positioning**: Create immersive soundscapes with precise spatial audio control.
11
- - **Advanced Audio Processing**: Apply and manage a variety of audio filters for enhanced sound manipulation.
12
- - **Dynamic Volume Control**: Adjust global and individual volume levels with support for smooth fading effects.
13
- - **Synthesizer Integration**: Create and manipulate synthesized sounds with customizable oscillator options.
14
- - **Efficient Group Management**: Organize and control multiple sounds or synthesizers as groups for streamlined audio management.
15
- - **Live Microphone Input**: Capture and process real-time audio input from the user's microphone.
16
- - **Audio Streaming**: Support for streaming audio directly from URLs.
17
- - **Flexible Caching**: Implement efficient audio caching strategies for improved performance.
8
+ - **Versatile Audio Source Handling**: Manage audio from various sources including `AudioBuffer`, URL strings, synthesizers, and live microphone input
9
+ - **Comprehensive Playback Control**: Play, stop, pause, resume, loop, and seek within audio with ease
10
+ - **3D Audio Positioning**: Create immersive soundscapes with precise spatial audio control
11
+ - **Advanced Audio Processing**: Apply and manage a variety of audio filters for enhanced sound manipulation
12
+ - **Dynamic Volume Control**: Adjust global and individual volume levels with support for smooth fading effects
13
+ - **Synthesizer Integration**: Create and manipulate synthesized sounds with customizable oscillator options
14
+ - **Efficient Group Management**: Organize and control multiple sounds or synthesizers as groups for streamlined audio management
15
+ - **Live Microphone Input**: Capture and process real-time audio input from the user's microphone
16
+ - **Audio Streaming**: Support for streaming audio directly from URLs
17
+ - **Flexible Caching**: Implement efficient audio caching strategies for improved performance
18
18
 
19
19
  ## Installation
20
20
 
@@ -29,7 +29,7 @@ import { Cacophony } from 'cacophony';
29
29
 
30
30
  async function audioDemo() {
31
31
  const cacophony = new Cacophony();
32
-
32
+
33
33
  // Create and play a sound with 3D positioning
34
34
  const sound = await cacophony.createSound('path/to/audio.mp3');
35
35
  sound.play();
@@ -55,47 +55,556 @@ async function audioDemo() {
55
55
  audioDemo();
56
56
  ```
57
57
 
58
- ## Detailed API Documentation
58
+ ## Core Concepts
59
+
60
+ ### Sound vs Playback Architecture
61
+
62
+ Cacophony uses a two-tier architecture that separates audio assets from their playback instances:
63
+
64
+ - **Sound**: Represents an audio asset (file or buffer). Acts as a container managing multiple playback instances.
65
+ - **Playback**: Represents a single playback instance of a Sound. Each call to `play()` creates a new Playback.
66
+
67
+ This design allows the same sound to be played multiple times simultaneously with different settings (volume, position, playback rate, etc.).
68
+
69
+ ```typescript
70
+ const sound = await cacophony.createSound('laser.mp3');
71
+
72
+ // Play the same sound three times with different settings
73
+ const [playback1] = sound.play();
74
+ playback1.volume = 0.3;
75
+ playback1.playbackRate = 1.0;
76
+
77
+ const [playback2] = sound.play();
78
+ playback2.volume = 0.8;
79
+ playback2.playbackRate = 1.5; // Higher pitched
80
+
81
+ const [playback3] = sound.play();
82
+ playback3.volume = 0.5;
83
+ playback3.position = [5, 0, 0]; // Positioned to the right
84
+
85
+ // Control individual playbacks
86
+ setTimeout(() => playback1.pause(), 1000);
87
+ setTimeout(() => playback2.stop(), 2000);
88
+
89
+ // Or control all playbacks through the Sound
90
+ sound.stop(); // Stops all three playbacks
91
+ ```
92
+
93
+ ## Sound Types
94
+
95
+ Cacophony supports three sound types, each optimized for different use cases:
96
+
97
+ | Type | Memory | Latency | Seeking | Multiple Instances | Best For |
98
+ |------|--------|---------|---------|-------------------|----------|
99
+ | **Buffer** (default) | High | None | Full | Yes | Sound effects, UI sounds, short music clips |
100
+ | **HTML** | Medium | Low | Full | Limited | Background music, large audio files, podcasts |
101
+ | **Streaming** | Low | Medium | Limited | No | Internet radio, live streams, very large files |
102
+
103
+ ```typescript
104
+ // Buffer - entire file loaded into memory
105
+ const sfx = await cacophony.createSound('explosion.mp3', SoundType.Buffer);
106
+
107
+ // HTML - streams from network, good for large files
108
+ const music = await cacophony.createSound('bgm.mp3', SoundType.HTML);
109
+
110
+ // Streaming - for live streams
111
+ const radio = await cacophony.createStream('https://example.com/stream.m3u8');
112
+ ```
113
+
114
+ ## Playback Control
115
+
116
+ ### Seeking
117
+
118
+ ```typescript
119
+ const sound = await cacophony.createSound('podcast.mp3');
120
+ sound.play();
121
+
122
+ // Seek to 30 seconds
123
+ sound.seek(30);
124
+
125
+ // Seek on individual playback
126
+ const [playback] = sound.play();
127
+ playback.seek(45);
128
+
129
+ // Get current time
130
+ console.log(playback.currentTime); // Current position in seconds
131
+ console.log(playback.duration); // Total duration
132
+ ```
133
+
134
+ ### Playback Rate
135
+
136
+ Control the speed of playback (affects pitch):
137
+
138
+ ```typescript
139
+ const sound = await cacophony.createSound('audio.mp3');
140
+
141
+ sound.playbackRate = 1.0; // Normal speed
142
+ sound.playbackRate = 2.0; // Double speed (higher pitch)
143
+ sound.playbackRate = 0.5; // Half speed (lower pitch)
144
+
145
+ // Apply to individual playback
146
+ const [playback] = sound.play();
147
+ playback.playbackRate = 1.25;
148
+ ```
149
+
150
+ ### Looping
151
+
152
+ ```typescript
153
+ const sound = await cacophony.createSound('music.mp3');
154
+
155
+ // Loop indefinitely
156
+ sound.loop('infinite');
157
+ sound.play();
158
+
159
+ // Loop exactly 3 times
160
+ sound.loop(3);
161
+ sound.play();
162
+
163
+ // No looping (default)
164
+ sound.loop(0);
165
+ ```
166
+
167
+ ## Volume Control
168
+
169
+ Cacophony provides a hierarchical volume control system:
170
+
171
+ ```typescript
172
+ const cacophony = new Cacophony();
173
+ const sound = await cacophony.createSound('audio.mp3');
174
+ const [playback] = sound.play();
175
+
176
+ // Global volume (affects all audio)
177
+ cacophony.volume = 0.5; // 50% of original volume
59
178
 
60
- For a comprehensive overview of all classes, methods, and features, please refer to our [detailed documentation](https://cacophony.js.org).
179
+ // Sound volume (affects all playbacks of this sound)
180
+ sound.volume = 0.8; // 80% of global volume
181
+
182
+ // Individual playback volume
183
+ playback.volume = 0.6; // 60% of sound volume
184
+
185
+ // Final volume = global × sound × playback = 0.5 × 0.8 × 0.6 = 0.24
186
+
187
+ // Mute/unmute
188
+ cacophony.mute();
189
+ cacophony.unmute();
190
+ ```
61
191
 
62
192
  ## Audio Filters
63
193
 
64
- Cacophony provides powerful audio filtering capabilities:
194
+ Cacophony provides powerful audio filtering capabilities using BiquadFilterNode. Filters can be applied to Sounds, Synths, Playbacks, and Groups.
195
+
196
+ Supported filter types: `lowpass`, `highpass`, `bandpass`, `lowshelf`, `highshelf`, `peaking`, `notch`, `allpass`.
65
197
 
66
198
  ```typescript
67
199
  const cacophony = new Cacophony();
200
+ const sound = await cacophony.createSound('audio.mp3');
68
201
 
69
- // Create a lowpass filter
70
- const lowpassFilter = cacophony.createBiquadFilter({
71
- type: 'lowpass',
72
- frequency: 1000,
73
- Q: 1
74
- });
202
+ // Create filters
203
+ const lowpass = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000, Q: 1 });
204
+ const highshelf = cacophony.createBiquadFilter({ type: 'highshelf', frequency: 5000, gain: -6 });
75
205
 
76
- // Apply filter to a Sound
77
- const sound = await cacophony.createSound('path/to/audio.mp3');
78
- sound.addFilter(lowpassFilter);
79
- sound.play();
206
+ // Apply filters (they are chained in order)
207
+ sound.addFilter(lowpass);
208
+ sound.addFilter(highshelf);
80
209
 
81
- // Apply filter to a Synth
210
+ // Apply to Synth
82
211
  const synth = cacophony.createOscillator({ frequency: 440, type: 'sawtooth' });
83
- synth.addFilter(lowpassFilter);
84
- synth.play();
212
+ synth.addFilter(lowpass);
213
+
214
+ // Remove filters
215
+ sound.removeFilter(lowpass);
216
+ ```
217
+
218
+ See [TypeDoc](https://cacophony.js.org) for complete filter parameters and options.
219
+
220
+ ## Custom Audio Routing
221
+
222
+ Cacophony exposes the underlying Web Audio graph through Playback instances, enabling manual routing through custom effects chains. This is the low-level foundation for building complex audio processing pipelines.
223
+
224
+ ### Accessing the Audio Graph
225
+
226
+ Every Playback instance exposes its output node and standard Web Audio connection methods:
227
+
228
+ ```typescript
229
+ const sound = await cacophony.createSound('audio.mp3');
230
+ const [playback] = sound.play();
231
+
232
+ // Access the output node (final node in the internal chain)
233
+ const outputNode = playback.outputNode; // Returns GainNode
234
+
235
+ // Connect to custom destination
236
+ const customEffect = cacophony.context.createDelay(0.5);
237
+ playback.connect(customEffect);
238
+ customEffect.connect(cacophony.context.destination);
239
+
240
+ // Disconnect from default routing
241
+ playback.disconnect(); // Disconnect from all
242
+ playback.disconnect(specificNode); // Disconnect from specific node
243
+ ```
244
+
245
+ ### Building Effect Chains
246
+
247
+ Route playbacks through multiple effects:
248
+
249
+ ```typescript
250
+ const sound = await cacophony.createSound('guitar.mp3');
251
+ const [playback] = sound.play();
252
+
253
+ // Create effect nodes
254
+ const distortion = cacophony.context.createWaveShaper();
255
+ const delay = cacophony.context.createDelay(1.0);
256
+ const reverb = cacophony.context.createConvolver();
257
+
258
+ // Chain: playback → distortion → delay → reverb → destination
259
+ playback.disconnect(); // Disconnect from default
260
+ playback.connect(distortion)
261
+ .connect(delay)
262
+ .connect(reverb)
263
+ .connect(cacophony.context.destination);
264
+ ```
265
+
266
+ ### Parallel Effects (Send/Return)
267
+
268
+ Create parallel effect sends like a mixing console:
269
+
270
+ ```typescript
271
+ const sound = await cacophony.createSound('vocals.mp3');
272
+ const [playback] = sound.play();
273
+
274
+ // Create send effects
275
+ const reverb = cacophony.context.createConvolver();
276
+ const reverbReturn = cacophony.context.createGain();
277
+ reverbReturn.gain.value = 0.3; // 30% wet
278
+
279
+ // Dry signal to destination
280
+ playback.connect(cacophony.context.destination);
281
+
282
+ // Parallel wet signal: playback → reverb → reverbReturn → destination
283
+ playback.connect(reverb)
284
+ .connect(reverbReturn)
285
+ .connect(cacophony.context.destination);
286
+ ```
287
+
288
+ ### Dynamic Routing
289
+
290
+ Change routing in real-time:
291
+
292
+ ```typescript
293
+ const sound = await cacophony.createSound('audio.mp3');
294
+ const [playback] = sound.play();
295
+
296
+ const cleanPath = cacophony.context.destination;
297
+ const fxPath = cacophony.context.createConvolver();
298
+ fxPath.connect(cacophony.context.destination);
299
+
300
+ // Toggle between clean and effects
301
+ let useFx = false;
302
+ button.addEventListener('click', () => {
303
+ playback.disconnect();
304
+
305
+ if (useFx) {
306
+ playback.connect(fxPath);
307
+ } else {
308
+ playback.connect(cleanPath);
309
+ }
310
+
311
+ useFx = !useFx;
312
+ });
313
+ ```
314
+
315
+ ### Integrating with Web Audio Nodes
316
+
317
+ Cacophony works seamlessly with any Web Audio API nodes:
318
+
319
+ ```typescript
320
+ const [playback] = sound.play();
321
+
322
+ // Built-in nodes
323
+ const analyser = cacophony.context.createAnalyser();
324
+ const compressor = cacophony.context.createDynamicsCompressor();
325
+ const panner = cacophony.context.createStereoPanner();
326
+
327
+ // AudioWorklet processors
328
+ const customProcessor = new AudioWorkletNode(cacophony.context, 'my-processor');
329
+
330
+ // Chain them together
331
+ playback.disconnect();
332
+ playback.connect(compressor)
333
+ .connect(analyser)
334
+ .connect(panner)
335
+ .connect(customProcessor)
336
+ .connect(cacophony.context.destination);
337
+
338
+ // Visualize with analyser
339
+ const dataArray = new Uint8Array(analyser.frequencyBinCount);
340
+ function draw() {
341
+ analyser.getByteFrequencyData(dataArray);
342
+ // ... draw visualization
343
+ requestAnimationFrame(draw);
344
+ }
345
+ ```
346
+
347
+ ### Architecture Notes
348
+
349
+ - **Internal chain**: `source → panner → [filters] → gainNode`
350
+ - **outputNode** exposes the final `gainNode` in this chain
351
+ - Default routing: `outputNode → globalGainNode → destination`
352
+ - Calling `disconnect()` breaks the default routing, allowing full manual control
353
+
354
+ ## Cloning
355
+
356
+ Clone sounds to create variations without reloading files. Clones share the same AudioBuffer but have independent settings.
357
+
358
+ ```typescript
359
+ const footstep = await cacophony.createSound('footstep.mp3');
360
+
361
+ // Create variations for different enemy sizes
362
+ const largeEnemy = footstep.clone({
363
+ position: [10, 0, -5],
364
+ playbackRate: 0.8, // Slower/lower pitch
365
+ volume: 0.9
366
+ });
367
+
368
+ const smallEnemy = footstep.clone({
369
+ position: [-5, 0, -10],
370
+ playbackRate: 1.25, // Faster/higher pitch
371
+ volume: 0.4
372
+ });
373
+
374
+ largeEnemy.play();
375
+ smallEnemy.play();
376
+
377
+ // Weapon sound variants
378
+ const gunshotBase = await cacophony.createSound('gunshot.mp3');
379
+
380
+ const weapons = {
381
+ pistol: gunshotBase.clone({ volume: 0.6, playbackRate: 1.2 }),
382
+ rifle: gunshotBase.clone({ volume: 1.0, playbackRate: 1.0 }),
383
+ shotgun: gunshotBase.clone({
384
+ volume: 1.2,
385
+ playbackRate: 0.8,
386
+ filters: [cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1200 })]
387
+ })
388
+ };
389
+
390
+ // Musical instrument samples by pitch-shifting
391
+ const pianoC4 = await cacophony.createSound('piano_c4.mp3');
392
+ const keyboard = {
393
+ C4: pianoC4,
394
+ D4: pianoC4.clone({ playbackRate: 1.122 }), // +2 semitones
395
+ E4: pianoC4.clone({ playbackRate: 1.260 }), // +4 semitones
396
+ F4: pianoC4.clone({ playbackRate: 1.335 }), // +5 semitones
397
+ G4: pianoC4.clone({ playbackRate: 1.498 }), // +7 semitones
398
+ };
399
+ ```
400
+
401
+ ## Group Functionality
402
+
403
+ Groups allow controlling multiple sounds or synthesizers as a single unit.
404
+
405
+ ```typescript
406
+ const cacophony = new Cacophony();
407
+
408
+ // Create from URLs
409
+ const soundGroup = await cacophony.createGroupFromUrls(['drum.mp3', 'bass.mp3', 'synth.mp3']);
410
+
411
+ // Or create from existing Sound instances
412
+ const sound1 = await cacophony.createSound('drum.mp3');
413
+ const sound2 = await cacophony.createSound('bass.mp3');
414
+ const sound3 = await cacophony.createSound('synth.mp3');
415
+ const soundGroup2 = await cacophony.createGroup([sound1, sound2, sound3]);
416
+
417
+ // Play all sounds simultaneously
418
+ soundGroup.play();
419
+
420
+ // Control all sounds at once
421
+ soundGroup.volume = 0.7;
422
+ soundGroup.playbackRate = 1.2;
423
+ soundGroup.position = [5, 0, 0];
424
+ soundGroup.loop('infinite');
425
+
426
+ // Apply filters to entire group
427
+ const lowpass = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 });
428
+ soundGroup.addFilter(lowpass);
429
+
430
+ // Play random sound from group
431
+ const footsteps = await cacophony.createGroupFromUrls([
432
+ 'footstep1.mp3',
433
+ 'footstep2.mp3',
434
+ 'footstep3.mp3',
435
+ 'footstep4.mp3'
436
+ ]);
437
+
438
+ footsteps.playRandom(); // Picks one at random
439
+
440
+ // Play sounds in sequence
441
+ const dialog = await cacophony.createGroupFromUrls(['line1.mp3', 'line2.mp3', 'line3.mp3']);
442
+ dialog.playOrdered(true); // Plays: 1, 2, 3, 1, 2, 3...
443
+
444
+ // SynthGroup for synthesizers
445
+ const synthGroup = new SynthGroup();
446
+ const synth1 = cacophony.createOscillator({ frequency: 440, type: 'sine' });
447
+ const synth2 = cacophony.createOscillator({ frequency: 660, type: 'square' });
448
+ synthGroup.addSynth(synth1);
449
+ synthGroup.addSynth(synth2);
450
+ synthGroup.play();
451
+ synthGroup.setVolume(0.5);
452
+
453
+ // Remove a synth from the group
454
+ synthGroup.removeSynth(synth1);
455
+ ```
456
+
457
+ ## Synthesizer Functionality
458
+
459
+ Create oscillator-based sounds with four waveform types: `sine`, `square`, `sawtooth`, `triangle`.
460
+
461
+ ```typescript
462
+ const cacophony = new Cacophony();
463
+
464
+ // Create oscillators
465
+ const sineOsc = cacophony.createOscillator({ frequency: 440, type: 'sine' });
466
+ sineOsc.play();
467
+
468
+ // Change parameters in real-time
469
+ sineOsc.frequency = 880; // Change frequency
470
+ sineOsc.type = 'sawtooth'; // Change waveform
471
+ sineOsc.detune = 10; // Detune in cents (1/100th of a semitone)
472
+
473
+ // Layer multiple oscillators
474
+ const bass = cacophony.createOscillator({ frequency: 110, type: 'sine' });
475
+ const lead = cacophony.createOscillator({ frequency: 440, type: 'sawtooth' });
476
+ bass.volume = 0.7;
477
+ lead.volume = 0.5;
478
+ bass.addFilter(cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 }));
479
+ bass.play();
480
+ lead.play();
481
+
482
+ // Modulate frequency over time
483
+ let time = 0;
484
+ setInterval(() => {
485
+ const frequency = 440 + Math.sin(time) * 100;
486
+ sineOsc.frequency = frequency;
487
+ time += 0.1;
488
+ }, 50);
489
+ ```
490
+
491
+ ## 3D Audio Positioning
492
+
493
+ Create immersive soundscapes with precise spatial audio control using HRTF (Head-Related Transfer Function) or stereo panning.
494
+
495
+ ```typescript
496
+ const cacophony = new Cacophony();
497
+
498
+ // Create sounds with HRTF panning
499
+ const ambience = await cacophony.createSound('forest_ambience.mp3', SoundType.Buffer, 'HRTF');
500
+ const birdSound = await cacophony.createSound('bird_chirp.mp3', SoundType.Buffer, 'HRTF');
501
+ const footsteps = await cacophony.createSound('footsteps.mp3', SoundType.Buffer, 'HRTF');
502
+
503
+ // Position sounds in 3D space
504
+ // Coordinate system: X (left- to right+), Y (down- to up+), Z (front+ to back-)
505
+ ambience.position = [0, 0, -5]; // Slightly behind the listener
506
+ birdSound.position = [10, 5, 0]; // To the right and above
507
+ footsteps.position = [-2, -1, 2]; // Slightly to the left and in front
508
+
509
+ // Configure distance attenuation
510
+ birdSound.threeDOptions = {
511
+ distanceModel: 'inverse', // 'linear', 'inverse', or 'exponential'
512
+ refDistance: 1,
513
+ rolloffFactor: 1
514
+ };
515
+
516
+ // Play the sounds
517
+ ambience.play();
518
+ birdSound.play();
519
+ footsteps.play();
520
+
521
+ // Set listener position and orientation
522
+ cacophony.listenerPosition = [0, 0, 0];
523
+ cacophony.listenerOrientation = {
524
+ forward: [0, 0, -1], // Looking towards negative Z
525
+ up: [0, 1, 0]
526
+ };
527
+
528
+ // Animate bird sound position
529
+ let time = 0;
530
+ setInterval(() => {
531
+ const x = Math.sin(time) * 10;
532
+ birdSound.position = [x, 5, 0];
533
+ time += 0.05;
534
+ }, 50);
535
+
536
+ // Stereo panning (simple left-right)
537
+ const stereoSound = await cacophony.createSound('audio.mp3', SoundType.Buffer, 'stereo');
538
+ stereoSound.stereoPan = 0.5; // -1 (left) to 1 (right)
539
+ stereoSound.play();
540
+ ```
541
+
542
+ See [TypeDoc](https://cacophony.js.org) for distance models, cone effects, and advanced 3D audio options.
543
+
544
+ ## Microphone Input
545
+
546
+ Capture, process, and manipulate live audio input:
547
+
548
+ ```typescript
549
+ const cacophony = new Cacophony();
550
+
551
+ try {
552
+ const micStream = await cacophony.getMicrophoneStream();
553
+ micStream.play();
554
+
555
+ // Apply filters to microphone input
556
+ const lowPassFilter = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 });
557
+ micStream.addFilter(lowPassFilter);
558
+
559
+ // Control microphone volume
560
+ micStream.volume = 0.8;
561
+
562
+ // Pause and resume
563
+ setTimeout(() => {
564
+ micStream.pause();
565
+ setTimeout(() => micStream.resume(), 2000);
566
+ }, 5000);
567
+
568
+ } catch (error) {
569
+ console.error("Error accessing microphone:", error);
570
+ }
571
+ ```
572
+
573
+ ## Audio Streaming
574
+
575
+ Stream audio content efficiently:
576
+
577
+ ```typescript
578
+ const cacophony = new Cacophony();
579
+
580
+ try {
581
+ const streamedSound = await cacophony.createStream('https://example.com/live_radio_stream');
582
+ streamedSound.play();
583
+
584
+ // Apply real-time effects to the stream
585
+ const highPassFilter = cacophony.createBiquadFilter({ type: 'highpass', frequency: 500 });
586
+ streamedSound.addFilter(highPassFilter);
587
+
588
+ // Control streaming playback
589
+ setTimeout(() => {
590
+ streamedSound.pause();
591
+ setTimeout(() => streamedSound.play(), 5000);
592
+ }, 10000);
593
+
594
+ } catch (error) {
595
+ console.error("Error streaming audio:", error);
596
+ }
85
597
  ```
86
598
 
87
599
  ## Event System
88
600
 
89
- Cacophony provides a comprehensive event system for monitoring loading progress, cache performance, and audio playback state:
601
+ Cacophony provides a comprehensive event system for monitoring loading progress, cache performance, and audio playback state.
90
602
 
91
603
  ### Loading Progress
92
604
 
93
- Track download progress with real-time updates:
94
-
95
605
  ```typescript
96
606
  const cacophony = new Cacophony();
97
607
 
98
- // Show loading progress
99
608
  cacophony.on('loadingStart', (event) => {
100
609
  console.log(`Started loading: ${event.url}`);
101
610
  showSpinner();
@@ -103,7 +612,6 @@ cacophony.on('loadingStart', (event) => {
103
612
 
104
613
  cacophony.on('loadingProgress', (event) => {
105
614
  const percent = event.progress * 100;
106
- console.log(`Progress: ${percent.toFixed(1)}%`);
107
615
  updateProgressBar(event.progress);
108
616
  });
109
617
 
@@ -117,8 +625,6 @@ const sound = await cacophony.createSound('large-audio-file.mp3');
117
625
 
118
626
  ### Error Handling
119
627
 
120
- Handle loading and playback errors:
121
-
122
628
  ```typescript
123
629
  // Global error handling
124
630
  cacophony.on('loadingError', (event) => {
@@ -130,31 +636,15 @@ cacophony.on('loadingError', (event) => {
130
636
  sound.on('soundError', (event) => {
131
637
  if (event.recoverable) {
132
638
  console.log('Retrying...');
133
- sound.play(); // Retry playback
639
+ sound.play();
134
640
  } else {
135
641
  console.error('Unrecoverable error:', event.error);
136
642
  }
137
643
  });
138
644
  ```
139
645
 
140
- ### Cache Monitoring
141
-
142
- Monitor cache performance:
143
-
144
- ```typescript
145
- cacophony.on('cacheHit', (event) => {
146
- console.log(`Cache hit: ${event.url} (${event.cacheType})`);
147
- });
148
-
149
- cacophony.on('cacheMiss', (event) => {
150
- console.log(`Cache miss: ${event.url} (${event.reason})`);
151
- });
152
- ```
153
-
154
646
  ### Global Playback Events
155
647
 
156
- Track all audio activity across your application:
157
-
158
648
  ```typescript
159
649
  // Monitor all playback globally
160
650
  cacophony.on('globalPlay', (event) => {
@@ -170,20 +660,10 @@ cacophony.on('globalStop', (event) => {
170
660
  cacophony.on('globalPause', (event) => {
171
661
  console.log('Audio paused:', event.source);
172
662
  });
173
-
174
- // Use case: pause all audio when app backgrounds
175
- document.addEventListener('visibilitychange', () => {
176
- if (document.hidden) {
177
- // Global events let you track what's playing without manual state management
178
- console.log('App backgrounded - track via global events');
179
- }
180
- });
181
663
  ```
182
664
 
183
665
  ### Playback Events
184
666
 
185
- React to playback state changes:
186
-
187
667
  ```typescript
188
668
  const sound = await cacophony.createSound('audio.mp3');
189
669
 
@@ -199,14 +679,42 @@ sound.on('pause', () => {
199
679
  sound.on('volumeChange', (volume) => {
200
680
  updateVolumeSlider(volume);
201
681
  });
682
+
683
+ // Playback instance events
684
+ const [playback] = sound.play();
685
+ playback.on('play', (pb) => {
686
+ console.log('Playback started:', pb.currentTime);
687
+ });
688
+
689
+ playback.on('error', (event) => {
690
+ if (event.recoverable) {
691
+ console.log('Recoverable error, retrying...');
692
+ playback.play();
693
+ }
694
+ });
695
+ ```
696
+
697
+ ### Synth Events
698
+
699
+ ```typescript
700
+ const synth = cacophony.createOscillator({ frequency: 440, type: 'sine' });
701
+
702
+ synth.on('frequencyChange', (freq) => {
703
+ console.log('Frequency changed to:', freq, 'Hz');
704
+ });
705
+
706
+ synth.on('typeChange', (type) => {
707
+ console.log('Waveform changed to:', type);
708
+ });
709
+
710
+ synth.frequency = 880; // Triggers frequencyChange event
711
+ synth.type = 'square'; // Triggers typeChange event
202
712
  ```
203
713
 
204
714
  ### Complete Event Reference
205
715
 
206
716
  #### Cacophony Events
207
717
 
208
- The main Cacophony instance emits global events for loading, caching, and errors:
209
-
210
718
  | Event | Payload | Description |
211
719
  |-------|---------|-------------|
212
720
  | `loadingStart` | `LoadingStartEvent` | Fired when audio loading begins. Contains `url` and `timestamp`. |
@@ -219,274 +727,170 @@ The main Cacophony instance emits global events for loading, caching, and errors
219
727
  | `globalPlay` | `GlobalPlaybackEvent` | Fired when any Sound or Synth starts playing. Contains `source`, `timestamp`. |
220
728
  | `globalStop` | `GlobalPlaybackEvent` | Fired when any Sound or Synth stops. Contains `source`, `timestamp`. |
221
729
  | `globalPause` | `GlobalPlaybackEvent` | Fired when any Sound or Synth pauses. Contains `source`, `timestamp`. |
730
+ | `volumeChange` | `number` | Fired when global volume changes. Receives new volume value. |
731
+ | `mute` | `void` | Fired when audio is muted globally. |
732
+ | `unmute` | `void` | Fired when audio is unmuted globally. |
733
+ | `suspend` | `void` | Fired when audio context is suspended. |
734
+ | `resume` | `void` | Fired when audio context is resumed. |
222
735
 
223
736
  #### Sound Events
224
737
 
225
- Sound instances emit events for playback state and property changes:
226
-
227
738
  | Event | Payload | Description |
228
739
  |-------|---------|-------------|
229
740
  | `play` | `Playback` | Fired when sound starts playing. Receives the Playback instance. |
230
741
  | `stop` | `void` | Fired when sound stops. |
231
742
  | `pause` | `void` | Fired when sound pauses. |
743
+ | `resume` | `void` | Fired when sound resumes after being paused. |
744
+ | `ended` | `void` | Fired when sound playback ends naturally. |
745
+ | `loopEnd` | `void` | Fired when a loop iteration completes. |
232
746
  | `volumeChange` | `number` | Fired when volume changes. Receives new volume value. |
233
747
  | `rateChange` | `number` | Fired when playback rate changes. Receives new rate value. |
234
748
  | `soundError` | `SoundErrorEvent` | Fired on playback errors. Contains `url`, `error`, `errorType`, `timestamp`, `recoverable`. |
235
749
 
236
750
  #### Playback Events
237
751
 
238
- Individual Playback instances emit fine-grained playback events:
239
-
240
752
  | Event | Payload | Description |
241
753
  |-------|---------|-------------|
242
754
  | `play` | `BasePlayback` | Fired when playback starts. Receives the Playback instance. |
243
755
  | `stop` | `void` | Fired when playback stops. |
756
+ | `pause` | `void` | Fired when playback pauses. |
757
+ | `resume` | `void` | Fired when playback resumes after being paused. |
758
+ | `ended` | `void` | Fired when playback ends naturally. |
759
+ | `seek` | `number` | Fired when playback position changes. Receives new time in seconds. |
760
+ | `volumeChange` | `number` | Fired when playback volume changes. Receives new volume value. |
244
761
  | `error` | `PlaybackErrorEvent` | Fired on playback errors. Contains `error`, `errorType`, `timestamp`, `recoverable`. |
245
762
 
246
- ```typescript
247
- const sound = await cacophony.createSound('audio.mp3');
248
- const [playback] = sound.play();
249
-
250
- playback.on('play', (pb) => {
251
- console.log('Playback started:', pb.currentTime);
252
- });
253
-
254
- playback.on('error', (event) => {
255
- if (event.recoverable) {
256
- console.log('Recoverable error, retrying...');
257
- playback.play();
258
- }
259
- });
260
- ```
261
-
262
763
  #### Synth Events
263
764
 
264
- Synth instances emit events for synthesis parameter changes:
265
-
266
765
  | Event | Payload | Description |
267
766
  |-------|---------|-------------|
268
767
  | `play` | `SynthPlayback` | Fired when synth starts playing. Receives the SynthPlayback instance. |
269
768
  | `stop` | `void` | Fired when synth stops. |
270
769
  | `pause` | `void` | Fired when synth pauses. |
770
+ | `resume` | `void` | Fired when synth resumes after being paused. |
771
+ | `ended` | `void` | Fired when synth playback ends naturally. |
271
772
  | `frequencyChange` | `number` | Fired when frequency changes. Receives new frequency in Hz. |
272
773
  | `typeChange` | `OscillatorType` | Fired when waveform type changes. Receives new type ('sine'\|'square'\|'sawtooth'\|'triangle'). |
273
774
  | `detuneChange` | `number` | Fired when detune changes. Receives new detune value in cents. |
274
775
  | `volumeChange` | `number` | Fired when volume changes. Receives new volume value. |
275
776
  | `error` | `PlaybackErrorEvent` | Fired on playback errors. Contains `error`, `errorType`, `timestamp`, `recoverable`. |
276
777
 
277
- ```typescript
278
- const synth = cacophony.createOscillator({ frequency: 440, type: 'sine' });
778
+ ## Caching
279
779
 
280
- synth.on('frequencyChange', (freq) => {
281
- console.log('Frequency changed to:', freq, 'Hz');
282
- updateFrequencyDisplay(freq);
283
- });
780
+ Cacophony implements intelligent three-layer caching for optimal performance:
284
781
 
285
- synth.on('typeChange', (type) => {
286
- console.log('Waveform changed to:', type);
287
- updateWaveformDisplay(type);
288
- });
289
-
290
- synth.frequency = 880; // Triggers frequencyChange event
291
- synth.type = 'square'; // Triggers typeChange event
292
- ```
293
-
294
- ## Synthesizer Functionality
782
+ **Memory Cache (LRU)** **Browser Cache API** → **Network**
295
783
 
296
- Create and manipulate synthesized sounds with advanced control:
784
+ The cache system is fully automatic and HTTP-compliant, respecting standard cache headers (ETag, Last-Modified). When cache validation tokens are available, Cacophony makes lightweight conditional requests (304 responses have no body). When tokens are unavailable, it falls back to TTL-based caching (24 hours default).
297
785
 
298
786
  ```typescript
299
787
  const cacophony = new Cacophony();
300
788
 
301
- // Create a simple sine wave oscillator
302
- const sineOsc = cacophony.createOscillator({ frequency: 440, type: 'sine' });
303
- sineOsc.play();
304
-
305
- // Create a complex sound with multiple oscillators
306
- const complexSynth = cacophony.createOscillator({ frequency: 220, type: 'sawtooth' });
307
- const subOsc = cacophony.createOscillator({ frequency: 110, type: 'sine' });
308
- complexSynth.addFilter(cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 }));
309
- complexSynth.play();
310
- subOsc.play();
311
-
312
- // Modulate frequency over time
313
- let time = 0;
314
- setInterval(() => {
315
- const frequency = 440 + Math.sin(time) * 100;
316
- sineOsc.frequency = frequency;
317
- time += 0.1;
318
- }, 50);
319
-
320
- // Apply envelope to volume
321
- complexSynth.volume = 0;
322
- complexSynth.fadeIn(0.5, 'linear');
323
- setTimeout(() => complexSynth.fadeOut(1, 'exponential'), 2000);
324
- ```
789
+ // First load - fetches from network, stores in cache
790
+ const sound1 = await cacophony.createSound('audio.mp3');
325
791
 
326
- ## Group Functionality
792
+ // Second load - instant from memory cache
793
+ const sound2 = await cacophony.createSound('audio.mp3');
327
794
 
328
- Efficiently manage and control multiple sounds or synthesizers:
329
-
330
- ```typescript
331
- const cacophony = new Cacophony();
332
-
333
- // Create a group of sounds
334
- const soundGroup = await cacophony.createGroupFromUrls(['drum.mp3', 'bass.mp3', 'synth.mp3']);
795
+ // Monitor cache performance via events
796
+ cacophony.on('cacheHit', (event) => {
797
+ console.log(`${event.cacheType} cache hit: ${event.url}`);
798
+ // cacheType: 'memory' | 'browser' | 'conditional'
799
+ });
335
800
 
336
- // Play all sounds in the group
337
- soundGroup.play();
801
+ cacophony.on('cacheMiss', (event) => {
802
+ console.log(`Cache miss: ${event.url} - ${event.reason}`);
803
+ // reason: 'not-found' | 'expired' | 'invalid'
804
+ });
338
805
 
339
- // Control volume for all sounds
340
- soundGroup.volume = 0.7;
806
+ // Clear memory cache (browser cache persists)
807
+ cacophony.clearMemoryCache();
341
808
 
342
- // Apply 3D positioning to the entire group
343
- soundGroup.position = [1, 0, -1];
809
+ // Optional: configure TTL for when no validation tokens exist
810
+ import { AudioCache } from 'cacophony';
811
+ AudioCache.setCacheExpirationTime(60 * 60 * 1000); // 1 hour
812
+ ```
344
813
 
345
- // Create a group of synthesizers
346
- const synthGroup = new SynthGroup();
347
- const synth1 = cacophony.createOscillator({ frequency: 440, type: 'sine' });
348
- const synth2 = cacophony.createOscillator({ frequency: 660, type: 'square' });
349
- synthGroup.addSynth(synth1);
350
- synthGroup.addSynth(synth2);
814
+ The caching system requires no configuration in most cases. It automatically optimizes for performance while respecting HTTP standards.
351
815
 
352
- // Play and control all synths in the group
353
- synthGroup.play();
354
- synthGroup.setVolume(0.5);
355
- synthGroup.stereoPan = -0.3; // Pan slightly to the left
816
+ ## Cancellation with AbortSignal
356
817
 
357
- // Remove a synth from the group
358
- synthGroup.removeSynth(synth2);
359
- ```
818
+ Cancel audio loading operations:
360
819
 
361
- ## Microphone Input
820
+ ```typescript
821
+ const controller = new AbortController();
362
822
 
363
- Capture, process, and manipulate live audio input:
823
+ const soundPromise = cacophony.createSound(
824
+ 'large-file.mp3',
825
+ SoundType.Buffer,
826
+ 'HRTF',
827
+ controller.signal
828
+ );
364
829
 
365
- ```typescript
366
- const cacophony = new Cacophony();
830
+ // Cancel loading
831
+ router.on('navigate', () => controller.abort());
367
832
 
368
- async function setupMicrophone() {
369
- try {
370
- const micStream = await cacophony.getMicrophoneStream();
371
- micStream.play();
372
-
373
- // Apply a low-pass filter to the microphone input
374
- const lowPassFilter = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 });
375
- micStream.addFilter(lowPassFilter);
376
-
377
- // Add a delay effect
378
- const delayFilter = cacophony.createBiquadFilter({ type: 'delay', delayTime: 0.5 });
379
- micStream.addFilter(delayFilter);
380
-
381
- // Control microphone volume
382
- micStream.volume = 0.8;
383
-
384
- // Pause and resume microphone input
385
- setTimeout(() => {
386
- micStream.pause();
387
- console.log("Microphone paused");
388
- setTimeout(() => {
389
- micStream.resume();
390
- console.log("Microphone resumed");
391
- }, 2000);
392
- }, 5000);
393
-
394
- } catch (error) {
395
- console.error("Error accessing microphone:", error);
833
+ try {
834
+ const sound = await soundPromise;
835
+ sound.play();
836
+ } catch (error) {
837
+ if (error.name === 'AbortError') {
838
+ console.log('Loading was cancelled');
396
839
  }
397
840
  }
398
841
 
399
- setupMicrophone();
842
+ // Works with groups too
843
+ const group = await cacophony.createGroupFromUrls(
844
+ ['a.mp3', 'b.mp3', 'c.mp3'],
845
+ SoundType.Buffer,
846
+ 'HRTF',
847
+ controller.signal
848
+ );
400
849
  ```
401
850
 
402
- ## 3D Audio Positioning
851
+ ## Resource Management
403
852
 
404
- Create immersive soundscapes with precise spatial audio control:
853
+ Call `cleanup()` when done with sounds to free resources:
405
854
 
406
855
  ```typescript
407
- const cacophony = new Cacophony();
408
-
409
- async function create3DAudioScene() {
410
- // Create sounds with HRTF panning
411
- const ambience = await cacophony.createSound('forest_ambience.mp3', SoundType.Buffer, 'HRTF');
412
- const birdSound = await cacophony.createSound('bird_chirp.mp3', SoundType.Buffer, 'HRTF');
413
- const footsteps = await cacophony.createSound('footsteps.mp3', SoundType.Buffer, 'HRTF');
414
-
415
- // Position sounds in 3D space
416
- ambience.position = [0, 0, -5]; // Slightly behind the listener
417
- birdSound.position = [10, 5, 0]; // To the right and above
418
- footsteps.position = [-2, -1, 2]; // Slightly to the left and in front
419
-
420
- // Play the sounds
421
- ambience.play();
422
- birdSound.play();
423
- footsteps.play();
424
-
425
- // Update listener position and orientation
426
- cacophony.listenerPosition = [0, 0, 0];
427
- cacophony.listenerOrientation = {
428
- forward: [0, 0, -1],
429
- up: [0, 1, 0]
430
- };
431
-
432
- // Animate bird sound position
433
- let time = 0;
434
- setInterval(() => {
435
- const x = Math.sin(time) * 10;
436
- birdSound.position = [x, 5, 0];
437
- time += 0.05;
438
- }, 50);
439
-
440
- // Change listener position over time
441
- setTimeout(() => {
442
- cacophony.listenerPosition = [0, 0, 5];
443
- console.log("Listener moved forward");
444
- }, 5000);
445
- }
856
+ const sound = await cacophony.createSound('temp.mp3');
857
+ sound.play();
858
+ sound.cleanup(); // Disconnects nodes, removes playbacks, clears listeners
446
859
 
447
- create3DAudioScene();
860
+ // Clear memory cache
861
+ cacophony.clearMemoryCache();
448
862
  ```
449
863
 
450
- ## Audio Streaming
864
+ Cacophony uses `FinalizationRegistry` for automatic cleanup when objects are garbage collected, but explicit cleanup is recommended for large applications.
451
865
 
452
- Stream audio content efficiently:
866
+ ## Audio Context Control
867
+
868
+ Suspend and resume the entire audio context to pause all audio processing. Useful for mobile apps when entering background mode or for performance optimization:
453
869
 
454
870
  ```typescript
455
871
  const cacophony = new Cacophony();
456
872
 
457
- async function streamAudio() {
458
- try {
459
- const streamedSound = await cacophony.createStream('https://example.com/live_radio_stream');
460
- streamedSound.play();
461
-
462
- // Apply real-time effects to the stream
463
- const highPassFilter = cacophony.createBiquadFilter({ type: 'highpass', frequency: 500 });
464
- streamedSound.addFilter(highPassFilter);
465
-
466
- // Control streaming playback
467
- setTimeout(() => {
468
- streamedSound.pause();
469
- console.log("Stream paused");
470
- setTimeout(() => {
471
- streamedSound.play();
472
- console.log("Stream resumed");
473
- }, 5000);
474
- }, 10000);
475
-
476
- } catch (error) {
477
- console.error("Error streaming audio:", error);
478
- }
479
- }
873
+ // Suspend audio context (pauses ALL audio, saves battery)
874
+ cacophony.pause();
875
+
876
+ // Resume audio context
877
+ cacophony.resume();
480
878
 
481
- streamAudio();
879
+ // Example: pause when app goes to background
880
+ document.addEventListener('visibilitychange', () => {
881
+ if (document.hidden) {
882
+ cacophony.pause();
883
+ } else {
884
+ cacophony.resume();
885
+ }
886
+ });
482
887
  ```
483
888
 
484
- ## Additional Highlights
889
+ Note: This is different from `sound.pause()` which pauses individual sounds. `cacophony.pause()` suspends the entire audio engine.
890
+
891
+ ## API Documentation
485
892
 
486
- - **Efficient Caching**: Implement smart caching strategies for improved performance and reduced bandwidth usage.
487
- - **Flexible Sound Types**: Support for various sound types including buffered audio, HTML5 audio, and streaming.
488
- - **Advanced Looping Control**: Fine-grained control over audio looping, including infinite loops and specific loop counts.
489
- - **Detailed Playback Information**: Access and control various playback parameters such as current time, duration, and playback rate.
893
+ For complete API documentation including all methods, parameters, and options, see the [TypeDoc documentation](https://cacophony.js.org).
490
894
 
491
895
  ## License
492
896