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 +663 -259
- package/dist/basePlayback.d.ts +3 -3
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +571 -498
- package/dist/index.mjs.map +1 -1
- package/dist/oscillatorMixin.d.ts +46 -0
- package/dist/playback.d.ts +44 -0
- package/dist/sound.d.ts +3 -2
- package/dist/synthPlayback.d.ts +57 -0
- package/docs/assets/search.js +1 -1
- package/docs/classes/Cacophony.html +10 -10
- package/docs/classes/Group.html +10 -10
- package/docs/classes/MicrophonePlayback.html +3 -3
- package/docs/classes/Playback.html +51 -26
- package/docs/classes/Sound.html +18 -17
- package/docs/classes/Synth.html +13 -13
- package/docs/classes/SynthGroup.html +2 -2
- package/docs/enums/SoundType.html +2 -2
- package/docs/index.html +213 -55
- package/docs/interfaces/BaseSound.html +2 -2
- package/docs/types/FadeType.html +1 -1
- package/docs/types/LoopCount.html +1 -1
- package/docs/types/Orientation.html +1 -1
- package/docs/types/PanType.html +1 -1
- package/docs/types/Position.html +1 -1
- package/package.json +4 -3
- package/dist/processors/ola.d.ts +0 -26
- package/dist/processors/phase-vocoder.d.ts +0 -30
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
|
-
##
|
|
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
|
-
|
|
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
|
|
70
|
-
const
|
|
71
|
-
|
|
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
|
|
77
|
-
|
|
78
|
-
sound.addFilter(
|
|
79
|
-
sound.play();
|
|
206
|
+
// Apply filters (they are chained in order)
|
|
207
|
+
sound.addFilter(lowpass);
|
|
208
|
+
sound.addFilter(highshelf);
|
|
80
209
|
|
|
81
|
-
// Apply
|
|
210
|
+
// Apply to Synth
|
|
82
211
|
const synth = cacophony.createOscillator({ frequency: 440, type: 'sawtooth' });
|
|
83
|
-
synth.addFilter(
|
|
84
|
-
|
|
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();
|
|
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
|
-
|
|
278
|
-
const synth = cacophony.createOscillator({ frequency: 440, type: 'sine' });
|
|
778
|
+
## Caching
|
|
279
779
|
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
302
|
-
const
|
|
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
|
-
|
|
792
|
+
// Second load - instant from memory cache
|
|
793
|
+
const sound2 = await cacophony.createSound('audio.mp3');
|
|
327
794
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
337
|
-
|
|
801
|
+
cacophony.on('cacheMiss', (event) => {
|
|
802
|
+
console.log(`Cache miss: ${event.url} - ${event.reason}`);
|
|
803
|
+
// reason: 'not-found' | 'expired' | 'invalid'
|
|
804
|
+
});
|
|
338
805
|
|
|
339
|
-
//
|
|
340
|
-
|
|
806
|
+
// Clear memory cache (browser cache persists)
|
|
807
|
+
cacophony.clearMemoryCache();
|
|
341
808
|
|
|
342
|
-
//
|
|
343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
358
|
-
synthGroup.removeSynth(synth2);
|
|
359
|
-
```
|
|
818
|
+
Cancel audio loading operations:
|
|
360
819
|
|
|
361
|
-
|
|
820
|
+
```typescript
|
|
821
|
+
const controller = new AbortController();
|
|
362
822
|
|
|
363
|
-
|
|
823
|
+
const soundPromise = cacophony.createSound(
|
|
824
|
+
'large-file.mp3',
|
|
825
|
+
SoundType.Buffer,
|
|
826
|
+
'HRTF',
|
|
827
|
+
controller.signal
|
|
828
|
+
);
|
|
364
829
|
|
|
365
|
-
|
|
366
|
-
|
|
830
|
+
// Cancel loading
|
|
831
|
+
router.on('navigate', () => controller.abort());
|
|
367
832
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
851
|
+
## Resource Management
|
|
403
852
|
|
|
404
|
-
|
|
853
|
+
Call `cleanup()` when done with sounds to free resources:
|
|
405
854
|
|
|
406
855
|
```typescript
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
860
|
+
// Clear memory cache
|
|
861
|
+
cacophony.clearMemoryCache();
|
|
448
862
|
```
|
|
449
863
|
|
|
450
|
-
|
|
864
|
+
Cacophony uses `FinalizationRegistry` for automatic cleanup when objects are garbage collected, but explicit cleanup is recommended for large applications.
|
|
451
865
|
|
|
452
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|