cacophony 0.21.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -71
- package/dist/autoplayUnlock.d.ts +28 -0
- package/dist/bus.d.ts +117 -0
- package/dist/cache.d.ts +23 -24
- package/dist/cacophony.d.ts +553 -11
- package/dist/container.d.ts +43 -16
- package/dist/context.d.ts +15 -0
- package/dist/effects.d.ts +544 -0
- package/dist/eventEmitter.d.ts +17 -1
- package/dist/events.d.ts +35 -31
- package/dist/filters.d.ts +2 -4
- package/dist/group.d.ts +8 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +21 -6
- package/dist/index.mjs +3349 -1048
- package/dist/index.mjs.map +1 -1
- package/dist/mediaStream.d.ts +5 -6
- package/dist/meters/loudness-core.d.ts +179 -0
- package/dist/meters/loudness-core.test.d.ts +1 -0
- package/dist/meters/loudness-meter.d.ts +60 -0
- package/dist/meters/loudness-meter.test.d.ts +1 -0
- package/dist/meters/truepeak-core.d.ts +104 -0
- package/dist/meters/truepeak-core.test.d.ts +1 -0
- package/dist/microphone.d.ts +2 -4
- package/dist/pannerMixin.d.ts +34 -6
- package/dist/playback.d.ts +56 -9
- package/dist/sound.d.ts +119 -8
- package/dist/spatial/foa-encode.d.ts +37 -0
- package/dist/stream.d.ts +16 -2
- package/dist/synth.d.ts +26 -15
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/AudioCache.html +17 -11
- package/docs/classes/BiquadEffect.html +8 -0
- package/docs/classes/Bus.html +79 -0
- package/docs/classes/Cacophony.html +414 -18
- package/docs/classes/DynamicsEffect.html +11 -0
- package/docs/classes/FdnReverbEffect.html +12 -0
- package/docs/classes/FoaDecoder.html +79 -0
- package/docs/classes/Group.html +18 -14
- package/docs/classes/KWeightingFilter.html +10 -0
- package/docs/classes/LoudnessMeter.html +39 -0
- package/docs/classes/MediaStreamPlayback.html +25 -27
- package/docs/classes/MediaStreamSound.html +31 -25
- package/docs/classes/MicrophonePlayback.html +3 -4
- package/docs/classes/ModulatedDelayEffect.html +12 -0
- package/docs/classes/PhaserEffect.html +11 -0
- package/docs/classes/Playback.html +81 -50
- package/docs/classes/ReverbEffect.html +15 -0
- package/docs/classes/ShareEffect.html +10 -0
- package/docs/classes/Sound.html +120 -28
- package/docs/classes/Synth.html +43 -25
- package/docs/classes/SynthGroup.html +2 -2
- package/docs/classes/TremoloEffect.html +11 -0
- package/docs/classes/TruePeakDetector.html +29 -0
- package/docs/classes/WaveshaperEffect.html +12 -0
- package/docs/functions/encodeMonoToFoaSN3D.html +31 -0
- package/docs/functions/integratedLoudness.html +12 -0
- package/docs/functions/integratedUngatedLoudness.html +7 -0
- package/docs/functions/loudnessRange.html +7 -0
- package/docs/functions/timeStretch.html +20 -0
- package/docs/functions/timeStretchChannels.html +4 -0
- package/docs/functions/truePeakDb.html +4 -0
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +74 -6
- package/docs/interfaces/AudioBuffer.html +2 -2
- package/docs/interfaces/AudioBufferSourceNode.html +4 -4
- package/docs/interfaces/AudioEventCallbacks.html +11 -11
- package/docs/interfaces/AudioListener.html +2 -2
- package/docs/interfaces/AudioNode.html +3 -3
- package/docs/interfaces/AudioParam.html +2 -2
- package/docs/interfaces/AudioWorklet.html +2 -0
- package/docs/interfaces/AudioWorkletNode.html +9 -4
- package/docs/interfaces/BaseContext.html +4 -2
- package/docs/interfaces/BaseSound.html +2 -2
- package/docs/interfaces/BiquadCoefficients.html +8 -0
- package/docs/interfaces/BiquadFilterNode.html +4 -4
- package/docs/interfaces/CacheErrorEvent.html +2 -2
- package/docs/interfaces/CacheHitEvent.html +2 -2
- package/docs/interfaces/CacheMissEvent.html +2 -2
- package/docs/interfaces/CacophonyEffect.html +7 -0
- package/docs/interfaces/ChannelMergerNode.html +3 -3
- package/docs/interfaces/ChannelSplitterNode.html +3 -3
- package/docs/interfaces/DynamicsOptions.html +21 -0
- package/docs/interfaces/FadeStartEvent.html +2 -2
- package/docs/interfaces/FdnReverbOptions.html +19 -0
- package/docs/interfaces/FoaDecoderOptions.html +10 -0
- package/docs/interfaces/GainNode.html +3 -3
- package/docs/interfaces/GlobalPlaybackEvent.html +2 -2
- package/docs/interfaces/LoadingCompleteEvent.html +2 -2
- package/docs/interfaces/LoadingErrorEvent.html +2 -2
- package/docs/interfaces/LoadingProgressEvent.html +2 -2
- package/docs/interfaces/LoadingStartEvent.html +2 -2
- package/docs/interfaces/LoudnessChannelInput.html +4 -0
- package/docs/interfaces/LoudnessReading.html +10 -0
- package/docs/interfaces/MediaElementSourceNode.html +3 -3
- package/docs/interfaces/MediaStreamAudioSourceNode.html +3 -3
- package/docs/interfaces/MediaStreamSoundOptions.html +2 -2
- package/docs/interfaces/ModulatedDelayOptions.html +27 -0
- package/docs/interfaces/OfflineOptions.html +2 -2
- package/docs/interfaces/OscillatorNode.html +4 -4
- package/docs/interfaces/PannerNode.html +3 -3
- package/docs/interfaces/PhaserOptions.html +23 -0
- package/docs/interfaces/PlayOptions.html +2 -2
- package/docs/interfaces/PlaybackErrorEvent.html +2 -2
- package/docs/interfaces/ReverbOptions.html +31 -0
- package/docs/interfaces/RuntimeOptions.html +15 -2
- package/docs/interfaces/SoundCleanupHoldings.html +2 -2
- package/docs/interfaces/SoundErrorEvent.html +2 -2
- package/docs/interfaces/StereoPannerNode.html +3 -3
- package/docs/interfaces/TimeStretchOptions.html +16 -0
- package/docs/interfaces/TremoloOptions.html +21 -0
- package/docs/interfaces/WaveshaperOptions.html +18 -0
- package/docs/modules.html +51 -8
- package/docs/types/BaseAudioEvents.html +1 -0
- package/docs/types/BusConnectionTarget.html +4 -0
- package/docs/types/CacheEventCallback.html +2 -2
- package/docs/types/CacophonyEvents.html +6 -0
- package/docs/types/ErrorEventCallback.html +2 -2
- package/docs/types/FadeType.html +1 -1
- package/docs/types/HrtfPannerOptions.html +3 -0
- package/docs/types/LoadingEventCallback.html +2 -2
- package/docs/types/LoopCount.html +1 -1
- package/docs/types/LoudnessChannel.html +4 -0
- package/docs/types/Orientation.html +1 -1
- package/docs/types/PanCloneOverrides.html +1 -0
- package/docs/types/PanType.html +1 -1
- package/docs/types/PlaybackEvents.html +2 -0
- package/docs/types/Position.html +1 -1
- package/docs/types/SoundEvents.html +2 -0
- package/docs/types/SoundType.html +1 -0
- package/docs/types/SourceNode.html +1 -1
- package/docs/types/SynthEvents.html +2 -0
- package/docs/types/ThreeDOptions.html +14 -0
- package/docs/variables/CHANNEL_WEIGHTS.html +4 -0
- package/docs/variables/K_WEIGHTING_STAGE1_48K.html +4 -0
- package/docs/variables/K_WEIGHTING_STAGE2_48K.html +3 -0
- package/package.json +6 -2
- package/dist/basePlayback.d.ts +0 -81
- package/dist/oscillatorMixin.d.ts +0 -53
- package/dist/synthPlayback.d.ts +0 -69
- package/dist/volumeMixin.d.ts +0 -58
- package/docs/enums/SoundType.html +0 -5
- package/docs/interfaces/BaseAudioEvents.html +0 -11
- package/docs/interfaces/CacophonyEvents.html +0 -17
- package/docs/interfaces/PlaybackEvents.html +0 -13
- package/docs/interfaces/SoundEvents.html +0 -15
- package/docs/interfaces/SynthEvents.html +0 -15
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Cacophony is a powerful and intuitive audio library designed for modern web appl
|
|
|
13
13
|
- **Synthesizer Integration**: Create and manipulate synthesized sounds with customizable oscillator options
|
|
14
14
|
- **Efficient Group Management**: Organize and control multiple sounds or synthesizers as groups for streamlined audio management
|
|
15
15
|
- **Live Microphone Input**: Capture and process real-time audio input from the user's microphone
|
|
16
|
-
- **Network-Backed Playback**: Play audio directly from URLs using media-element-backed sounds
|
|
16
|
+
- **Network-Backed Playback**: Play audio directly from URLs using media-element-backed sounds
|
|
17
17
|
- **Flexible Caching**: Implement efficient audio caching strategies for improved performance
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
@@ -55,6 +55,43 @@ async function audioDemo() {
|
|
|
55
55
|
audioDemo();
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
## Mobile Autoplay Handling
|
|
59
|
+
|
|
60
|
+
Modern browsers — especially iOS Safari and Chrome on Android — refuse to
|
|
61
|
+
produce sound from an `AudioContext` that was constructed before any user
|
|
62
|
+
interaction. The context is created in `suspended` state and must be both
|
|
63
|
+
resumed AND have a node started from inside a real user-gesture call stack
|
|
64
|
+
before audio can play. Calling `context.resume()` alone is not enough on iOS.
|
|
65
|
+
|
|
66
|
+
Cacophony handles this transparently by default. When you construct a
|
|
67
|
+
`Cacophony` instance and the context is suspended, it installs one-time
|
|
68
|
+
`touchend` / `click` / `keydown` listeners on `document.body`. The first
|
|
69
|
+
user gesture resumes the context, plays a 1-sample silent primer buffer (the
|
|
70
|
+
iOS unlock primer), removes the listeners, and emits the `unlock` event.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const cacophony = new Cacophony();
|
|
74
|
+
|
|
75
|
+
if (cacophony.locked) {
|
|
76
|
+
// Mobile: waiting for the first user interaction.
|
|
77
|
+
// Show a "tap to enable audio" affordance if you want one.
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
cacophony.on('unlock', () => {
|
|
81
|
+
// Audio is now usable.
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
If you prefer to manage unlock yourself, opt out:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const cacophony = new Cacophony(undefined, undefined, { autoUnlock: false });
|
|
89
|
+
// You are responsible for calling cacophony.resume() inside a user gesture.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The auto-unlock has no effect on offline contexts or in non-browser
|
|
93
|
+
environments (`typeof document === 'undefined'`).
|
|
94
|
+
|
|
58
95
|
## Core Concepts
|
|
59
96
|
|
|
60
97
|
### Sound vs Playback Architecture
|
|
@@ -92,25 +129,59 @@ sound.stop(); // Stops all three playbacks
|
|
|
92
129
|
|
|
93
130
|
## Sound Types
|
|
94
131
|
|
|
95
|
-
Cacophony supports three sound types:
|
|
132
|
+
Cacophony supports three sound types:
|
|
96
133
|
|
|
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 | Yes | Background music, large audio files, podcasts |
|
|
101
|
-
| **Streaming** | Medium | Low | Full | Yes | Network-backed playback created via `createStream()` |
|
|
134
|
+
| Type | Memory | Latency | Seeking | Multiple Instances | Best For |
|
|
135
|
+
|------|--------|---------|---------|-------------------|----------|
|
|
136
|
+
| **Buffer** (default) | High | None | Full | Yes | Sound effects, UI sounds, short music clips |
|
|
137
|
+
| **HTML** | Medium | Low | Full | Yes | Background music, large audio files, podcasts |
|
|
138
|
+
| **Streaming** | Medium | Low | Full | Yes | Network-backed playback created via `createStream()` |
|
|
102
139
|
|
|
103
140
|
```typescript
|
|
104
141
|
// Buffer - entire file loaded into memory
|
|
105
|
-
const sfx = await cacophony.createSound('explosion.mp3',
|
|
142
|
+
const sfx = await cacophony.createSound('explosion.mp3', 'buffer');
|
|
106
143
|
|
|
107
144
|
// HTML - streams from network, good for large files
|
|
108
|
-
const music = await cacophony.createSound('bgm.mp3',
|
|
145
|
+
const music = await cacophony.createSound('bgm.mp3', 'html');
|
|
109
146
|
|
|
110
|
-
// Streaming - convenience helper for network-backed playback
|
|
111
|
-
const radio = await cacophony.createStream('https://example.com/stream.m3u8');
|
|
147
|
+
// Streaming - convenience helper for network-backed playback
|
|
148
|
+
const radio = await cacophony.createStream('https://example.com/stream.m3u8');
|
|
112
149
|
```
|
|
113
150
|
|
|
151
|
+
### Format Fallback (Howler-style)
|
|
152
|
+
|
|
153
|
+
Pass an array of URLs and Cacophony picks the first one the browser can play.
|
|
154
|
+
It queries `HTMLAudioElement.canPlayType` per extension and fetches only the
|
|
155
|
+
chosen source (cache and loading events fire only for that URL). If the
|
|
156
|
+
selected source's `canPlayType` was `'maybe'` and decoding actually fails,
|
|
157
|
+
the next playable candidate is tried.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// First playable source wins. Cacophony fetches only that one.
|
|
161
|
+
const boom = await cacophony.createSound([
|
|
162
|
+
'sfx/boom.webm', // small/modern, preferred when supported
|
|
163
|
+
'sfx/boom.mp3', // universal fallback
|
|
164
|
+
'sfx/boom.wav', // last-resort uncompressed
|
|
165
|
+
]);
|
|
166
|
+
boom.play();
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Recognised extensions: `.webm`, `.mp3`, `.ogg`, `.wav`, `.flac`, `.m4a`,
|
|
170
|
+
`.aac`, `.opus`. If no candidate is reported playable, or every playable
|
|
171
|
+
candidate fails to decode, the promise rejects with an error naming the
|
|
172
|
+
URLs that were tried and the reason each failed (codec unsupported or
|
|
173
|
+
decode failure).
|
|
174
|
+
|
|
175
|
+
Only decode failures advance to the next candidate. Fetch/network/cache
|
|
176
|
+
failures of the selected source propagate immediately so the caller sees
|
|
177
|
+
the real cause instead of a silent format swap. Decode failures are
|
|
178
|
+
detected by the Web Audio spec marker -- `DOMException` with name
|
|
179
|
+
`EncodingError`.
|
|
180
|
+
|
|
181
|
+
v1 limitation: format fallback is only available for the default `'buffer'`
|
|
182
|
+
sound type. Passing an array together with `'html'` or `'streaming'`
|
|
183
|
+
rejects with a "not yet supported" error.
|
|
184
|
+
|
|
114
185
|
## Playback Control
|
|
115
186
|
|
|
116
187
|
### Seeking
|
|
@@ -269,26 +340,98 @@ playback.connect(distortion)
|
|
|
269
340
|
|
|
270
341
|
### Parallel Effects (Send/Return)
|
|
271
342
|
|
|
272
|
-
|
|
343
|
+
For mixing-console-style routing — named buses, shared effects, and
|
|
344
|
+
per-edge send gain — use the first-class [Buses and Sends](#buses-and-sends)
|
|
345
|
+
API documented below. The Bus class supersedes the older user-built
|
|
346
|
+
`playback.connect()` send/return pattern.
|
|
347
|
+
|
|
348
|
+
## Buses and Sends
|
|
349
|
+
|
|
350
|
+
A `Bus` is a named summing node with its own filter chain and per-edge
|
|
351
|
+
gain on outgoing connections. Sounds and synths route to buses via
|
|
352
|
+
`routeTo`; buses can carry rich effects (not just BiquadFilter) by
|
|
353
|
+
adding a `CacophonyEffect` to their filter chain. The built-in
|
|
354
|
+
`cacophony.createReverb()` returns a DattorroReverb effect ready to drop
|
|
355
|
+
into a bus.
|
|
273
356
|
|
|
274
357
|
```typescript
|
|
275
|
-
|
|
276
|
-
const
|
|
358
|
+
// 1. Create a named bus
|
|
359
|
+
const reverbBus = cacophony.createBus('reverb');
|
|
277
360
|
|
|
278
|
-
//
|
|
279
|
-
const reverb = cacophony.
|
|
280
|
-
|
|
281
|
-
reverbReturn.gain.value = 0.3; // 30% wet
|
|
361
|
+
// 2. Add a DattorroReverb effect to the bus
|
|
362
|
+
const reverb = cacophony.createReverb({ wet: 0.6, dry: 0.4, decay: 0.7 });
|
|
363
|
+
await reverbBus.addFilter(reverb);
|
|
282
364
|
|
|
283
|
-
//
|
|
284
|
-
|
|
365
|
+
// 3. Lower the bus's level a bit before it hits master
|
|
366
|
+
reverbBus.gain = 0.5;
|
|
285
367
|
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
368
|
+
// 4. Route one sound's primary output to the bus
|
|
369
|
+
const vocals = await cacophony.createSound('vocals.mp3');
|
|
370
|
+
vocals.routeTo(reverbBus);
|
|
371
|
+
vocals.play();
|
|
372
|
+
|
|
373
|
+
// 5. Send another sound to the bus at 30% (primary route still goes to master)
|
|
374
|
+
const drums = await cacophony.createSound('drums.mp3');
|
|
375
|
+
drums.routeTo(reverbBus, 0.3);
|
|
376
|
+
drums.play();
|
|
290
377
|
```
|
|
291
378
|
|
|
379
|
+
### Looking up buses by name
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
const fx = cacophony.createBus('fx');
|
|
383
|
+
cacophony.getBus('fx'); // → same Bus instance
|
|
384
|
+
cacophony.listBuses(); // → ['master', 'fx']
|
|
385
|
+
sound.routeTo('fx'); // string lookup via the registry
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### The master bus
|
|
389
|
+
|
|
390
|
+
`cacophony.master` is the built-in master bus. Its `input` is literally
|
|
391
|
+
the same node as `cacophony.globalGainNode`, so the existing
|
|
392
|
+
`cacophony.volume` and `cacophony.mute` APIs continue to work
|
|
393
|
+
transparently. Routing a sound to `cacophony.master` (or never calling
|
|
394
|
+
`routeTo`) sends it through the master path.
|
|
395
|
+
|
|
396
|
+
### Bus-to-bus routing with per-edge gain
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
const groupBus = cacophony.createBus('group');
|
|
400
|
+
const sendBus = cacophony.createBus('aux');
|
|
401
|
+
groupBus.connect(sendBus, 0.2); // 20% send: groupBus.output → sendGain(0.2) → sendBus.input
|
|
402
|
+
groupBus.disconnect(sendBus); // tears down the sendGain too
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Adding custom effects to a bus
|
|
406
|
+
|
|
407
|
+
Bus filter chains accept Cacophony-built BiquadFilters and any
|
|
408
|
+
`CacophonyEffect`. Raw third-party AudioNodes are rejected unless you
|
|
409
|
+
wrap them explicitly with `cacophony.shareEffect(node)` — this surfaces
|
|
410
|
+
the shared-state intent (the same node will run on every bus that adds it).
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
const eq = cacophony.createBiquadFilter({ type: 'highshelf', frequency: 4000, gain: 3 });
|
|
414
|
+
await bus.addFilter(eq);
|
|
415
|
+
|
|
416
|
+
const sharedWorklet = new AudioWorkletNode(cacophony.context, 'my-fx');
|
|
417
|
+
await bus.addFilter(cacophony.shareEffect(sharedWorklet));
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Cleaning up
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
reverbBus.destroy(); // disconnects everything, deregisters the name
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Sounds still routed to a destroyed bus fall back to master on their
|
|
427
|
+
next playback with a `console.warn`.
|
|
428
|
+
|
|
429
|
+
### Legacy: user-built send/return
|
|
430
|
+
|
|
431
|
+
Before buses existed, send/return was a manual `playback.connect()`
|
|
432
|
+
chain (see the [Custom Audio Routing](#custom-audio-routing) section
|
|
433
|
+
for the low-level pattern). New code should use a Bus and `routeTo`.
|
|
434
|
+
|
|
292
435
|
### Dynamic Routing
|
|
293
436
|
|
|
294
437
|
Change routing in real-time:
|
|
@@ -441,28 +584,28 @@ const footsteps = await cacophony.createGroupFromUrls([
|
|
|
441
584
|
|
|
442
585
|
footsteps.playRandom(); // Picks one at random
|
|
443
586
|
|
|
444
|
-
// Advance through sounds in sequence, one call at a time
|
|
445
|
-
const dialog = await cacophony.createGroupFromUrls(['line1.mp3', 'line2.mp3', 'line3.mp3']);
|
|
446
|
-
dialog.playOrdered(true); // Plays line1, then advances internal order
|
|
447
|
-
dialog.playOrdered(true); // Plays line2
|
|
448
|
-
dialog.playOrdered(true); // Plays line3
|
|
449
|
-
dialog.playOrdered(true); // Plays line1 again because looping is enabled
|
|
450
|
-
|
|
451
|
-
// SynthGroup for synthesizers
|
|
452
|
-
const synthGroup = new SynthGroup();
|
|
453
|
-
const synth1 = cacophony.createOscillator({ frequency: 440, type: 'sine' });
|
|
454
|
-
const synth2 = cacophony.createOscillator({ frequency: 660, type: 'square' });
|
|
455
|
-
synthGroup.addSynth(synth1);
|
|
456
|
-
synthGroup.addSynth(synth2);
|
|
457
|
-
synthGroup.play();
|
|
458
|
-
synthGroup.volume = 0.5;
|
|
459
|
-
synthGroup.type = 'triangle';
|
|
460
|
-
synthGroup.pause();
|
|
461
|
-
synthGroup.resume();
|
|
462
|
-
|
|
463
|
-
// Remove a synth from the group
|
|
464
|
-
synthGroup.removeSynth(synth1);
|
|
465
|
-
```
|
|
587
|
+
// Advance through sounds in sequence, one call at a time
|
|
588
|
+
const dialog = await cacophony.createGroupFromUrls(['line1.mp3', 'line2.mp3', 'line3.mp3']);
|
|
589
|
+
dialog.playOrdered(true); // Plays line1, then advances internal order
|
|
590
|
+
dialog.playOrdered(true); // Plays line2
|
|
591
|
+
dialog.playOrdered(true); // Plays line3
|
|
592
|
+
dialog.playOrdered(true); // Plays line1 again because looping is enabled
|
|
593
|
+
|
|
594
|
+
// SynthGroup for synthesizers
|
|
595
|
+
const synthGroup = new SynthGroup();
|
|
596
|
+
const synth1 = cacophony.createOscillator({ frequency: 440, type: 'sine' });
|
|
597
|
+
const synth2 = cacophony.createOscillator({ frequency: 660, type: 'square' });
|
|
598
|
+
synthGroup.addSynth(synth1);
|
|
599
|
+
synthGroup.addSynth(synth2);
|
|
600
|
+
synthGroup.play();
|
|
601
|
+
synthGroup.volume = 0.5;
|
|
602
|
+
synthGroup.type = 'triangle';
|
|
603
|
+
synthGroup.pause();
|
|
604
|
+
synthGroup.resume();
|
|
605
|
+
|
|
606
|
+
// Remove a synth from the group
|
|
607
|
+
synthGroup.removeSynth(synth1);
|
|
608
|
+
```
|
|
466
609
|
|
|
467
610
|
## Synthesizer Functionality
|
|
468
611
|
|
|
@@ -489,22 +632,22 @@ bass.addFilter(cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 }
|
|
|
489
632
|
bass.play();
|
|
490
633
|
lead.play();
|
|
491
634
|
|
|
492
|
-
// Modulate frequency over time
|
|
493
|
-
let time = 0;
|
|
494
|
-
setInterval(() => {
|
|
495
|
-
const frequency = 440 + Math.sin(time) * 100;
|
|
496
|
-
sineOsc.frequency = frequency;
|
|
497
|
-
time += 0.1;
|
|
498
|
-
}, 50);
|
|
499
|
-
|
|
500
|
-
// Pause and resume the active synth playback without losing its settings
|
|
501
|
-
sineOsc.pause();
|
|
502
|
-
sineOsc.resume();
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
`synth.pause()` keeps the existing synth playback object so `synth.resume()` can restart it with the current frequency, detune, type, volume, pan, and filter settings. `synth.play()` still creates a fresh playback instance, just like `Sound.play()`.
|
|
506
|
-
|
|
507
|
-
## 3D Audio Positioning
|
|
635
|
+
// Modulate frequency over time
|
|
636
|
+
let time = 0;
|
|
637
|
+
setInterval(() => {
|
|
638
|
+
const frequency = 440 + Math.sin(time) * 100;
|
|
639
|
+
sineOsc.frequency = frequency;
|
|
640
|
+
time += 0.1;
|
|
641
|
+
}, 50);
|
|
642
|
+
|
|
643
|
+
// Pause and resume the active synth playback without losing its settings
|
|
644
|
+
sineOsc.pause();
|
|
645
|
+
sineOsc.resume();
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
`synth.pause()` keeps the existing synth playback object so `synth.resume()` can restart it with the current frequency, detune, type, volume, pan, and filter settings. `synth.play()` still creates a fresh playback instance, just like `Sound.play()`.
|
|
649
|
+
|
|
650
|
+
## 3D Audio Positioning
|
|
508
651
|
|
|
509
652
|
Create immersive soundscapes with precise spatial audio control using HRTF (Head-Related Transfer Function) or stereo panning.
|
|
510
653
|
|
|
@@ -512,9 +655,9 @@ Create immersive soundscapes with precise spatial audio control using HRTF (Head
|
|
|
512
655
|
const cacophony = new Cacophony();
|
|
513
656
|
|
|
514
657
|
// Create sounds with HRTF panning
|
|
515
|
-
const ambience = await cacophony.createSound('forest_ambience.mp3',
|
|
516
|
-
const birdSound = await cacophony.createSound('bird_chirp.mp3',
|
|
517
|
-
const footsteps = await cacophony.createSound('footsteps.mp3',
|
|
658
|
+
const ambience = await cacophony.createSound('forest_ambience.mp3', 'buffer', 'HRTF');
|
|
659
|
+
const birdSound = await cacophony.createSound('bird_chirp.mp3', 'buffer', 'HRTF');
|
|
660
|
+
const footsteps = await cacophony.createSound('footsteps.mp3', 'buffer', 'HRTF');
|
|
518
661
|
|
|
519
662
|
// Position sounds in 3D space
|
|
520
663
|
// Coordinate system: X (left- to right+), Y (down- to up+), Z (front+ to back-)
|
|
@@ -550,7 +693,7 @@ setInterval(() => {
|
|
|
550
693
|
}, 50);
|
|
551
694
|
|
|
552
695
|
// Stereo panning (simple left-right)
|
|
553
|
-
const stereoSound = await cacophony.createSound('audio.mp3',
|
|
696
|
+
const stereoSound = await cacophony.createSound('audio.mp3', 'buffer', 'stereo');
|
|
554
697
|
stereoSound.stereoPan = 0.5; // -1 (left) to 1 (right)
|
|
555
698
|
stereoSound.play();
|
|
556
699
|
```
|
|
@@ -838,7 +981,7 @@ const controller = new AbortController();
|
|
|
838
981
|
|
|
839
982
|
const soundPromise = cacophony.createSound(
|
|
840
983
|
'large-file.mp3',
|
|
841
|
-
|
|
984
|
+
'buffer',
|
|
842
985
|
'HRTF',
|
|
843
986
|
controller.signal
|
|
844
987
|
);
|
|
@@ -858,7 +1001,7 @@ try {
|
|
|
858
1001
|
// Works with groups too
|
|
859
1002
|
const group = await cacophony.createGroupFromUrls(
|
|
860
1003
|
['a.mp3', 'b.mp3', 'c.mp3'],
|
|
861
|
-
|
|
1004
|
+
'buffer',
|
|
862
1005
|
'HRTF',
|
|
863
1006
|
controller.signal
|
|
864
1007
|
);
|
|
@@ -869,9 +1012,9 @@ const group = await cacophony.createGroupFromUrls(
|
|
|
869
1012
|
Call `cleanup()` when done with sounds to free resources:
|
|
870
1013
|
|
|
871
1014
|
```typescript
|
|
872
|
-
const sound = await cacophony.createSound('temp.mp3');
|
|
873
|
-
sound.play();
|
|
874
|
-
sound.cleanup(); // Tears down playbacks, including pausing and resetting active HTML/streaming media
|
|
1015
|
+
const sound = await cacophony.createSound('temp.mp3');
|
|
1016
|
+
sound.play();
|
|
1017
|
+
sound.cleanup(); // Tears down playbacks, including pausing and resetting active HTML/streaming media
|
|
875
1018
|
|
|
876
1019
|
// Clear memory cache
|
|
877
1020
|
cacophony.clearMemoryCache();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BaseContext } from './context';
|
|
2
|
+
/**
|
|
3
|
+
* Options for `installAutoplayUnlock`.
|
|
4
|
+
*/
|
|
5
|
+
export interface AutoplayUnlockOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The audio context that should be resumed and primed.
|
|
8
|
+
*/
|
|
9
|
+
context: BaseContext;
|
|
10
|
+
/**
|
|
11
|
+
* Called after the context has been resumed and the primer buffer has been
|
|
12
|
+
* started. Used by `Cacophony` to emit the public `unlock` event.
|
|
13
|
+
* Errors thrown by the callback are caught and logged so a faulty listener
|
|
14
|
+
* cannot break the unlock flow.
|
|
15
|
+
*/
|
|
16
|
+
onUnlock: () => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Install one-time unlock listeners. Returns a cleanup function that removes
|
|
20
|
+
* the listeners if they have not already fired (e.g. when a `Cacophony`
|
|
21
|
+
* instance is torn down before the user interacts).
|
|
22
|
+
*
|
|
23
|
+
* If the environment is non-browser (`document` is undefined) or the context
|
|
24
|
+
* is not in `suspended` state, this is a no-op and returns a no-op cleanup.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare function installAutoplayUnlock(opts: AutoplayUnlockOptions): () => void;
|
package/dist/bus.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { AudioNode, BaseContext, BiquadFilterNode, GainNode } from './context';
|
|
2
|
+
import { CacophonyEffect } from './effects';
|
|
3
|
+
/**
|
|
4
|
+
* Connection target for {@link Bus.connect} / {@link Bus.disconnect}. Either
|
|
5
|
+
* another Bus (we connect into its `input`) or a raw AudioNode (we connect
|
|
6
|
+
* directly to it — escape hatch for advanced wiring).
|
|
7
|
+
*/
|
|
8
|
+
export type BusConnectionTarget = Bus | AudioNode;
|
|
9
|
+
/**
|
|
10
|
+
* A named summing node with a filter chain and per-edge send gain. See
|
|
11
|
+
* module-level docstring for topology.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Bus {
|
|
14
|
+
/** Stable name for registry lookup, or null for anonymous buses. */
|
|
15
|
+
readonly name: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Entry node — connect upstream sources here (sound playbacks, other bus
|
|
18
|
+
* outputs).
|
|
19
|
+
*/
|
|
20
|
+
readonly input: GainNode;
|
|
21
|
+
/**
|
|
22
|
+
* Exit node — connected to downstream targets (other bus inputs, master,
|
|
23
|
+
* raw nodes) via {@link connect}.
|
|
24
|
+
*/
|
|
25
|
+
readonly output: GainNode;
|
|
26
|
+
private readonly _context;
|
|
27
|
+
private readonly _filterNodes;
|
|
28
|
+
private readonly _filterChainEdges;
|
|
29
|
+
private readonly _sendGains;
|
|
30
|
+
private readonly _directConnections;
|
|
31
|
+
/**
|
|
32
|
+
* Hook invoked by the owning Cacophony instance to remove this bus from
|
|
33
|
+
* the named-bus registry on destroy. Anonymous buses leave this undefined.
|
|
34
|
+
*/
|
|
35
|
+
private readonly _onDestroy?;
|
|
36
|
+
private _destroyed;
|
|
37
|
+
/**
|
|
38
|
+
* @param context Web Audio context the bus's nodes live on.
|
|
39
|
+
* @param name Name to register under, or null for an anonymous bus.
|
|
40
|
+
* @param input Optional pre-existing GainNode to use as the input. Used by
|
|
41
|
+
* the `master` bus to alias `cacophony.globalGainNode`. If omitted, a
|
|
42
|
+
* fresh GainNode is allocated.
|
|
43
|
+
* @param onDestroy Optional registry-cleanup hook fired by destroy().
|
|
44
|
+
*/
|
|
45
|
+
constructor(context: BaseContext, name?: string | null, input?: GainNode, onDestroy?: () => void);
|
|
46
|
+
/** True after {@link destroy} has been called. */
|
|
47
|
+
get destroyed(): boolean;
|
|
48
|
+
/** Output node gain — controls the overall level the bus sends downstream. */
|
|
49
|
+
get gain(): number;
|
|
50
|
+
set gain(v: number);
|
|
51
|
+
/** Live filter chain (read-only view). */
|
|
52
|
+
get filters(): readonly AudioNode[];
|
|
53
|
+
/**
|
|
54
|
+
* Add a filter node to the bus's chain. Accepts:
|
|
55
|
+
*
|
|
56
|
+
* - A Cacophony-built BiquadFilterNode (created via
|
|
57
|
+
* `cacophony.createBiquadFilter`) → added directly to the chain.
|
|
58
|
+
* - A {@link CacophonyEffect} → `build(context)` is awaited; the resulting
|
|
59
|
+
* node is added to the chain.
|
|
60
|
+
* - A raw third-party AudioNode → REJECTED. Wrap it with
|
|
61
|
+
* `cacophony.shareEffect(node)` (or a proper CacophonyEffect class) to
|
|
62
|
+
* make the shared-state intent explicit.
|
|
63
|
+
*
|
|
64
|
+
* @throws if the bus has been destroyed, or if the argument is a raw
|
|
65
|
+
* AudioNode that is not a Cacophony-built biquad.
|
|
66
|
+
*/
|
|
67
|
+
addFilter(arg: BiquadFilterNode | CacophonyEffect | AudioNode): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Remove a filter node from the bus's chain. The node must have been added
|
|
70
|
+
* via {@link addFilter}; the same object identity is used to match.
|
|
71
|
+
*
|
|
72
|
+
* @throws if the bus has been destroyed or if the node was never added.
|
|
73
|
+
*/
|
|
74
|
+
removeFilter(node: AudioNode): void;
|
|
75
|
+
/**
|
|
76
|
+
* Connect this bus's output to another bus or to a raw AudioNode.
|
|
77
|
+
*
|
|
78
|
+
* If `gain` is omitted or equal to 1, connect directly (output →
|
|
79
|
+
* targetInput). If `gain` is provided, allocate an internal GainNode for
|
|
80
|
+
* per-edge attenuation: output → sendGain → targetInput. The sendGain is
|
|
81
|
+
* tracked so {@link disconnect} can tear it down cleanly.
|
|
82
|
+
*
|
|
83
|
+
* Re-connecting a target that is already wired is a no-op for the direct
|
|
84
|
+
* case; for a gained connection, the existing sendGain's `gain.value` is
|
|
85
|
+
* updated in place (no new edge is allocated).
|
|
86
|
+
*
|
|
87
|
+
* @throws if the bus has been destroyed.
|
|
88
|
+
*/
|
|
89
|
+
connect(target: BusConnectionTarget, gain?: number): void;
|
|
90
|
+
/**
|
|
91
|
+
* Disconnect this bus's output from a target previously connected with
|
|
92
|
+
* {@link connect}. Tears down the allocated sendGain (if any). No-op if
|
|
93
|
+
* the target was never connected.
|
|
94
|
+
*
|
|
95
|
+
* @throws if the bus has been destroyed.
|
|
96
|
+
*/
|
|
97
|
+
disconnect(target: BusConnectionTarget): void;
|
|
98
|
+
/**
|
|
99
|
+
* Tear down the bus — disconnects input, output, every send-gain, every
|
|
100
|
+
* filter, then deregisters from the owner Cacophony's named-bus map.
|
|
101
|
+
* Subsequent `addFilter`/`removeFilter`/`connect`/`disconnect` calls throw.
|
|
102
|
+
*
|
|
103
|
+
* Sounds routed to a destroyed bus fall back to master on their next
|
|
104
|
+
* playback (the routeTo machinery checks `destroyed` at preplay).
|
|
105
|
+
*/
|
|
106
|
+
destroy(): void;
|
|
107
|
+
/**
|
|
108
|
+
* Rebuild the chain `input → [filter1 → ... → filterN] → output`. Called
|
|
109
|
+
* after any add/remove of a filter. Disconnects only the internal chain
|
|
110
|
+
* edges this bus created, then reapplies the chain. The output node's edges
|
|
111
|
+
* to downstream targets are not touched.
|
|
112
|
+
*/
|
|
113
|
+
private _refreshFilters;
|
|
114
|
+
private _connectFilterChainEdge;
|
|
115
|
+
private _disconnectFilterChainEdges;
|
|
116
|
+
private _throwIfDestroyed;
|
|
117
|
+
}
|
package/dist/cache.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { BaseContext } from './context';
|
|
2
|
+
import { AudioEventCallbacks } from './events';
|
|
3
|
+
/**
|
|
4
|
+
* Subset of {@link AudioEventCallbacks} relevant to {@link AudioCache} public API.
|
|
5
|
+
* Lets callers opt in to any combination of loading/cache events without
|
|
6
|
+
* having to import the full union.
|
|
7
|
+
*/
|
|
8
|
+
export type CacheCallbacks = Pick<AudioEventCallbacks, "onLoadingStart" | "onLoadingProgress" | "onLoadingComplete" | "onLoadingError" | "onCacheHit" | "onCacheMiss" | "onCacheError">;
|
|
2
9
|
export interface ICache {
|
|
3
|
-
getAudioBuffer(context: BaseContext, url: string, signal?: AbortSignal, callbacks?:
|
|
4
|
-
onLoadingStart?: (event: any) => void;
|
|
5
|
-
onLoadingProgress?: (event: any) => void;
|
|
6
|
-
onLoadingComplete?: (event: any) => void;
|
|
7
|
-
onLoadingError?: (event: any) => void;
|
|
8
|
-
onCacheHit?: (event: any) => void;
|
|
9
|
-
onCacheMiss?: (event: any) => void;
|
|
10
|
-
onCacheError?: (event: any) => void;
|
|
11
|
-
}): Promise<AudioBuffer>;
|
|
10
|
+
getAudioBuffer(context: BaseContext, url: string, signal?: AbortSignal, callbacks?: CacheCallbacks): Promise<AudioBuffer>;
|
|
12
11
|
clearMemoryCache(): void;
|
|
13
12
|
}
|
|
14
13
|
/**
|
|
@@ -41,10 +40,9 @@ export declare class AudioCache implements ICache {
|
|
|
41
40
|
static setCacheExpirationTime(time: number): void;
|
|
42
41
|
private static openCache;
|
|
43
42
|
/**
|
|
44
|
-
* Calls all registered callbacks for a specific event type on a URL
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* @param eventData - Data to pass to the callbacks
|
|
43
|
+
* Calls all registered callbacks for a specific event type on a URL.
|
|
44
|
+
* Generic over the callback name so the payload type is checked against
|
|
45
|
+
* the canonical {@link CacheCallbacks} shape rather than `any`.
|
|
48
46
|
*/
|
|
49
47
|
private static callAllCallbacks;
|
|
50
48
|
private static getOrCreatePendingRequest;
|
|
@@ -52,10 +50,19 @@ export declare class AudioCache implements ICache {
|
|
|
52
50
|
private static getBufferFromCache;
|
|
53
51
|
private static fetchAndCacheBuffer;
|
|
54
52
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* Atomically write a response body and its metadata into the cache.
|
|
54
|
+
* On failure, deletes both partial entries (best-effort via `allSettled`)
|
|
55
|
+
* and rethrows the original error.
|
|
56
|
+
*/
|
|
57
|
+
private static writeBufferAndMetadata;
|
|
58
|
+
/**
|
|
59
|
+
* Creates a ReadableStream wrapper that tracks download progress.
|
|
60
|
+
* Uses the callback aggregation system to emit progress to all registered listeners.
|
|
61
|
+
* Honours `signal` between reads and releases the underlying reader on
|
|
62
|
+
* any exit path (done, error, abort).
|
|
57
63
|
* @param response - The fetch Response object with ReadableStream body
|
|
58
64
|
* @param url - URL being downloaded (for progress event data and callback lookup)
|
|
65
|
+
* @param signal - Optional AbortSignal observed at chunk boundaries
|
|
59
66
|
* @returns Object containing the progress-tracking stream and total size
|
|
60
67
|
*/
|
|
61
68
|
private static createProgressTrackingStream;
|
|
@@ -85,14 +92,6 @@ export declare class AudioCache implements ICache {
|
|
|
85
92
|
* @returns Promise that resolves to decoded AudioBuffer
|
|
86
93
|
* @throws Error if audio cannot be fetched or decoded
|
|
87
94
|
*/
|
|
88
|
-
getAudioBuffer(context: BaseContext, url: string, signal?: AbortSignal, callbacks?:
|
|
89
|
-
onLoadingStart?: (event: any) => void;
|
|
90
|
-
onLoadingProgress?: (event: any) => void;
|
|
91
|
-
onLoadingComplete?: (event: any) => void;
|
|
92
|
-
onLoadingError?: (event: any) => void;
|
|
93
|
-
onCacheHit?: (event: any) => void;
|
|
94
|
-
onCacheMiss?: (event: any) => void;
|
|
95
|
-
onCacheError?: (event: any) => void;
|
|
96
|
-
}): Promise<AudioBuffer>;
|
|
95
|
+
getAudioBuffer(context: BaseContext, url: string, signal?: AbortSignal, callbacks?: CacheCallbacks): Promise<AudioBuffer>;
|
|
97
96
|
clearMemoryCache(): void;
|
|
98
97
|
}
|