cacophony 0.28.0 → 0.30.3
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 +269 -34
- package/bin/cacophony.mjs +6 -0
- package/dist/autoplayUnlock.d.ts +5 -5
- package/dist/backend-available.d.ts +1 -0
- package/dist/bus.d.ts +9 -58
- package/dist/cache.d.ts +32 -6
- package/dist/cacophony-PShY6dPK.cjs +2 -0
- package/dist/cacophony-PShY6dPK.cjs.map +1 -0
- package/dist/cacophony-o1zE5oDP.js +3857 -0
- package/dist/cacophony-o1zE5oDP.js.map +1 -0
- package/dist/cacophony.d.ts +99 -12
- package/dist/cli/audio-sink.d.ts +11 -0
- package/dist/cli/commands.d.ts +139 -0
- package/dist/cli/effects-registry.d.ts +35 -0
- package/dist/cli/exit.test.d.ts +1 -0
- package/dist/cli/index.cjs +83 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.mjs +1148 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/cli/index.test.d.ts +1 -0
- package/dist/cli/live.d.ts +28 -0
- package/dist/cli/live.test.d.ts +1 -0
- package/dist/cli/logging.d.ts +7 -0
- package/dist/cli/loudness.test.d.ts +1 -0
- package/dist/cli/meter.d.ts +20 -0
- package/dist/cli/package.test.d.ts +1 -0
- package/dist/cli/params.test.d.ts +1 -0
- package/dist/cli/render-core.test.d.ts +1 -0
- package/dist/cli/render.d.ts +78 -0
- package/dist/cli/render.test.d.ts +1 -0
- package/dist/cli/repl.d.ts +5 -0
- package/dist/cli/repl.test.d.ts +1 -0
- package/dist/cli/replay.d.ts +28 -0
- package/dist/cli/session.d.ts +92 -0
- package/dist/cli/wav.d.ts +39 -0
- package/dist/cli/wav.test.d.ts +1 -0
- package/dist/container.d.ts +3 -4
- package/dist/context.d.ts +19 -9
- package/dist/effectChain.d.ts +44 -0
- package/dist/effects.d.ts +86 -11
- package/dist/filters.d.ts +1 -3
- package/dist/group.d.ts +10 -4
- package/dist/hlsAdapter.d.ts +20 -0
- package/dist/index.cjs +1 -2
- package/dist/index.d.ts +10 -4
- package/dist/index.mjs +3 -5213
- package/dist/logger.d.ts +22 -0
- package/dist/mediaStream.d.ts +7 -36
- package/dist/microphone.d.ts +28 -55
- package/dist/node.cjs +6 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.ts +84 -0
- package/dist/node.mjs +65 -0
- package/dist/node.mjs.map +1 -0
- package/dist/oscillatorMixin.d.ts +17 -0
- package/dist/pannerMixin.d.ts +0 -2
- package/dist/pcmStream.d.ts +104 -0
- package/dist/playback.d.ts +8 -35
- package/dist/routableSource.d.ts +80 -0
- package/dist/sound.d.ts +13 -97
- package/dist/src-Bss6ztQN.cjs +2 -0
- package/dist/src-Bss6ztQN.cjs.map +1 -0
- package/dist/src-CCQXsJcG.js +400 -0
- package/dist/src-CCQXsJcG.js.map +1 -0
- package/dist/synth.d.ts +7 -49
- package/dist/synthPlayback.d.ts +43 -0
- package/dist/webCodecsStream.d.ts +35 -0
- package/dist/worklets.d.ts +24 -0
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/highlight.css +1 -1
- package/docs/assets/icons.js +17 -14
- package/docs/assets/icons.svg +1 -1
- package/docs/assets/main.js +5 -4
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/assets/style.css +1460 -1224
- package/docs/classes/AudioCache.html +133 -46
- package/docs/classes/BarberpoleEffect.html +136 -0
- package/docs/classes/BiquadEffect.html +92 -5
- package/docs/classes/Bus.html +450 -141
- package/docs/classes/Cacophony.html +1428 -251
- package/docs/classes/DynamicsEffect.html +136 -9
- package/docs/classes/FdnReverbEffect.html +136 -9
- package/docs/classes/FoaDecoder.html +133 -42
- package/docs/classes/FoaDecoderEffect.html +93 -6
- package/docs/classes/FrequencyShifterEffect.html +136 -0
- package/docs/classes/Group.html +507 -53
- package/docs/classes/HarmonizerEffect.html +136 -0
- package/docs/classes/ImpulseResponseEffect.html +94 -7
- package/docs/classes/KWeightingFilter.html +99 -8
- package/docs/classes/LoudnessMeter.html +206 -35
- package/docs/classes/MediaStreamPlayback.html +980 -90
- package/docs/classes/MediaStreamSound.html +720 -75
- package/docs/classes/MicrophoneStream.html +741 -0
- package/docs/classes/ModulatedDelayEffect.html +137 -8
- package/docs/classes/PcmStreamPlayback.html +980 -0
- package/docs/classes/PcmStreamSound.html +868 -0
- package/docs/classes/PhaserEffect.html +136 -9
- package/docs/classes/Playback.html +1280 -190
- package/docs/classes/ReverbEffect.html +137 -10
- package/docs/classes/ShareEffect.html +93 -6
- package/docs/classes/Sound.html +971 -176
- package/docs/classes/SpectralFreezeEffect.html +136 -0
- package/docs/classes/StereoWidenerEffect.html +93 -0
- package/docs/classes/Synth.html +875 -95
- package/docs/classes/SynthGroup.html +379 -22
- package/docs/classes/TremoloEffect.html +137 -8
- package/docs/classes/TruePeakDetector.html +151 -20
- package/docs/classes/WaveshaperEffect.html +138 -9
- package/docs/functions/encodeMonoToFoaSN3D.html +58 -11
- package/docs/functions/integratedLoudness.html +45 -2
- package/docs/functions/integratedUngatedLoudness.html +45 -2
- package/docs/functions/loudnessRange.html +45 -2
- package/docs/functions/timeStretch.html +53 -7
- package/docs/functions/timeStretchChannels.html +46 -2
- package/docs/functions/truePeakDb.html +46 -3
- package/docs/hierarchy.html +67 -1
- package/docs/index.html +467 -139
- package/docs/interfaces/AudioBuffer.html +127 -8
- package/docs/interfaces/AudioBufferSourceNode.html +305 -29
- package/docs/interfaces/AudioEventCallbacks.html +105 -11
- package/docs/interfaces/AudioListener.html +153 -10
- package/docs/interfaces/AudioNode.html +240 -21
- package/docs/interfaces/AudioParam.html +125 -7
- package/docs/interfaces/AudioWorklet.html +64 -2
- package/docs/interfaces/AudioWorkletNode.html +253 -24
- package/docs/interfaces/BarberpoleOptions.html +84 -0
- package/docs/interfaces/BaseContext.html +305 -24
- package/docs/interfaces/BaseSound.html +233 -17
- package/docs/interfaces/BiquadCoefficients.html +83 -8
- package/docs/interfaces/BiquadFilterNode.html +285 -27
- package/docs/interfaces/BuiltEffectGraph.html +82 -7
- package/docs/interfaces/CacheErrorEvent.html +76 -6
- package/docs/interfaces/CacheHitEvent.html +70 -5
- package/docs/interfaces/CacheMissEvent.html +70 -5
- package/docs/interfaces/CacophonyEffect.html +77 -7
- package/docs/interfaces/CacophonyLogger.html +98 -0
- package/docs/interfaces/ChannelMergerNode.html +239 -21
- package/docs/interfaces/ChannelSplitterNode.html +239 -21
- package/docs/interfaces/ConvolverNode.html +251 -23
- package/docs/interfaces/DynamicsOptions.html +102 -16
- package/docs/interfaces/FadeStartEvent.html +70 -5
- package/docs/interfaces/FdnReverbOptions.html +94 -14
- package/docs/interfaces/FoaDecoderOptions.html +61 -5
- package/docs/interfaces/FrequencyShifterOptions.html +68 -0
- package/docs/interfaces/GainNode.html +245 -22
- package/docs/interfaces/GlobalPlaybackEvent.html +64 -4
- package/docs/interfaces/HarmonizerOptions.html +82 -0
- package/docs/interfaces/ImpulseResponseOptions.html +81 -9
- package/docs/interfaces/LoadingCompleteEvent.html +76 -6
- package/docs/interfaces/LoadingErrorEvent.html +76 -6
- package/docs/interfaces/LoadingProgressEvent.html +82 -7
- package/docs/interfaces/LoadingStartEvent.html +64 -4
- package/docs/interfaces/LoudnessChannelInput.html +64 -4
- package/docs/interfaces/LoudnessReading.html +84 -10
- package/docs/interfaces/MediaElementSourceNode.html +245 -22
- package/docs/interfaces/MediaStreamAudioSourceNode.html +245 -22
- package/docs/interfaces/MediaStreamSoundOptions.html +75 -5
- package/docs/interfaces/MicrophoneStreamOptions.html +117 -0
- package/docs/interfaces/ModulatedDelayOptions.html +111 -19
- package/docs/interfaces/OfflineOptions.html +76 -6
- package/docs/interfaces/OscillatorNode.html +291 -27
- package/docs/interfaces/PannerNode.html +323 -35
- package/docs/interfaces/PcmStreamBufferEvent.html +55 -0
- package/docs/interfaces/PcmStreamSoundOptions.html +113 -0
- package/docs/interfaces/PhaserOptions.html +102 -16
- package/docs/interfaces/PlayOptions.html +77 -7
- package/docs/interfaces/PlaybackErrorEvent.html +76 -6
- package/docs/interfaces/ReverbOptions.html +151 -29
- package/docs/interfaces/RuntimeOptions.html +118 -6
- package/docs/interfaces/SoundCleanupHoldings.html +70 -5
- package/docs/interfaces/SoundErrorEvent.html +82 -7
- package/docs/interfaces/SpectralFreezeOptions.html +78 -0
- package/docs/interfaces/StereoPannerNode.html +245 -22
- package/docs/interfaces/StereoWidenerOptions.html +70 -0
- package/docs/interfaces/StreamCapabilities.html +73 -0
- package/docs/interfaces/TimeStretchOptions.html +85 -11
- package/docs/interfaces/TremoloOptions.html +86 -12
- package/docs/interfaces/WaveshaperOptions.html +86 -12
- package/docs/media/LICENSE.txt +21 -0
- package/docs/modules.html +56 -110
- package/docs/types/BaseAudioEvents.html +109 -1
- package/docs/types/BuiltEffect.html +33 -1
- package/docs/types/BusConnectionTarget.html +35 -2
- package/docs/types/CacheEventCallback.html +35 -2
- package/docs/types/CacophonyEvents.html +150 -3
- package/docs/types/ErrorEventCallback.html +35 -2
- package/docs/types/FadeType.html +35 -2
- package/docs/types/HrtfPannerOptions.html +35 -2
- package/docs/types/ImpulseResponseSource.html +33 -1
- package/docs/types/LoadingEventCallback.html +35 -2
- package/docs/types/LoopCount.html +35 -2
- package/docs/types/LoudnessChannel.html +35 -2
- package/docs/types/Orientation.html +68 -2
- package/docs/types/PanCloneOverrides.html +73 -1
- package/docs/types/PanType.html +35 -2
- package/docs/types/PcmStreamEvents.html +79 -0
- package/docs/types/PcmStreamState.html +33 -0
- package/docs/types/PlaybackEvents.html +35 -2
- package/docs/types/Position.html +35 -2
- package/docs/types/SoundEvents.html +35 -2
- package/docs/types/SoundType.html +33 -1
- package/docs/types/SourceNode.html +33 -1
- package/docs/types/SynthEvents.html +35 -2
- package/docs/types/ThreeDOptions.html +35 -2
- package/docs/types/WebCodecsStreamSound.html +33 -0
- package/docs/variables/CHANNEL_WEIGHTS.html +35 -2
- package/docs/variables/K_WEIGHTING_STAGE1_48K.html +35 -2
- package/docs/variables/K_WEIGHTING_STAGE2_48K.html +35 -2
- package/docs/variables/consoleLogger.html +38 -0
- package/docs/variables/noopLogger.html +35 -0
- package/package.json +30 -7
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/stream.d.ts +0 -16
- package/docs/classes/MicrophonePlayback.html +0 -25
package/README.md
CHANGED
|
@@ -129,26 +129,53 @@ sound.stop(); // Stops all three playbacks
|
|
|
129
129
|
|
|
130
130
|
## Sound Types
|
|
131
131
|
|
|
132
|
-
Cacophony supports three sound
|
|
132
|
+
Cacophony supports three URL-backed sound labels:
|
|
133
133
|
|
|
134
134
|
| Type | Memory | Latency | Seeking | Multiple Instances | Best For |
|
|
135
135
|
|------|--------|---------|---------|-------------------|----------|
|
|
136
136
|
| **Buffer** (default) | High | None | Full | Yes | Sound effects, UI sounds, short music clips |
|
|
137
|
-
| **HTML** | Medium | Low | Full | Yes |
|
|
138
|
-
| **Streaming** |
|
|
137
|
+
| **HTML** | Medium | Low | Full | Yes | Media-element-backed background music, large audio files, and podcasts |
|
|
138
|
+
| **Streaming** | Bounded | Low | Range-backed | One stream per source | Sample-accurate URL audio with effects, panning, and metering |
|
|
139
139
|
|
|
140
140
|
```typescript
|
|
141
141
|
// Buffer - entire file loaded into memory
|
|
142
142
|
const sfx = await cacophony.createSound('explosion.mp3', 'buffer');
|
|
143
143
|
|
|
144
|
-
// HTML -
|
|
145
|
-
const music = await cacophony.createSound('bgm.mp3', 'html');
|
|
146
|
-
|
|
147
|
-
// Streaming -
|
|
148
|
-
const radio = await cacophony.createStream('https://example.com/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
// HTML - media-element-backed playback, good for large files
|
|
145
|
+
const music = await cacophony.createSound('bgm.mp3', 'html');
|
|
146
|
+
|
|
147
|
+
// Streaming - incremental demux + WebCodecs decode into a PCM worklet
|
|
148
|
+
const radio = await cacophony.createStream('https://example.com/live-radio.mp3');
|
|
149
|
+
console.log(radio.streamCapabilities);
|
|
150
|
+
// { transport: 'webcodecs', seekable: false, live: true, duration: Infinity }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`createStream(url)` uses a fetch → demux → WebCodecs `AudioDecoder` → PCM
|
|
154
|
+
worklet path when WebCodecs is present and can decode the primary audio track.
|
|
155
|
+
The returned source has `soundType === 'streaming'` and uses the same volume,
|
|
156
|
+
pan/HRTF, filter, bus, send, and loudness-meter APIs as every other routable
|
|
157
|
+
source. MP3, ADTS/MP4 AAC, Ogg, FLAC, and WAVE containers are supported.
|
|
158
|
+
|
|
159
|
+
If WebCodecs is absent or the primary track has no WebCodecs decoder,
|
|
160
|
+
`createStream()` falls back to a media-element `Sound`. Check
|
|
161
|
+
`streamCapabilities.transport` for `'webcodecs'` or `'media-element'` rather
|
|
162
|
+
than assuming a tier.
|
|
163
|
+
|
|
164
|
+
Native HLS (`.m3u8`) playback, notably in Safari, uses the media element
|
|
165
|
+
directly. In browsers without native HLS, `createStream()` lazily uses
|
|
166
|
+
`hls.js`, declared as an optional peer dependency:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npm install hls.js
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If neither native HLS nor an installed, MSE-capable `hls.js` is available,
|
|
173
|
+
`createStream()` rejects with install guidance instead of leaving a media
|
|
174
|
+
element silently waiting. hls.js failures after loading are emitted through
|
|
175
|
+
the Sound's `soundError` event, and `sound.cleanup()` detaches and destroys the
|
|
176
|
+
hls.js instance.
|
|
177
|
+
|
|
178
|
+
### Format Fallback (Howler-style)
|
|
152
179
|
|
|
153
180
|
Pass an array of URLs and Cacophony picks the first one the browser can play.
|
|
154
181
|
It queries `HTMLAudioElement.canPlayType` per extension and fetches only the
|
|
@@ -290,9 +317,86 @@ const playback = sound.play()[0];
|
|
|
290
317
|
playback.filters[0].frequency.value = 500; // Only affects this playback
|
|
291
318
|
```
|
|
292
319
|
|
|
293
|
-
See [TypeDoc](https://cacophony.js.org) for complete filter parameters and options.
|
|
294
|
-
|
|
295
|
-
|
|
320
|
+
See [TypeDoc](https://cacophony.js.org) for complete filter parameters and options.
|
|
321
|
+
|
|
322
|
+
### Per-source effects
|
|
323
|
+
|
|
324
|
+
Use `addEffect` for source character such as distortion, EQ, tremolo, or pitch
|
|
325
|
+
processing. The source stores the `CacophonyEffect` as a recipe and builds a
|
|
326
|
+
fresh effect instance for every playback, before that playback's panner. The
|
|
327
|
+
returned playback handle can be automated without changing another playback.
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
const guitar = await cacophony.createSound('guitar.mp3');
|
|
331
|
+
const distortion = cacophony.createDistortion({ drive: 4 });
|
|
332
|
+
guitar.addEffect(distortion);
|
|
333
|
+
|
|
334
|
+
const [take] = guitar.play();
|
|
335
|
+
const liveTremolo = await take.addEffect(cacophony.createTremolo({ rate: 5 }));
|
|
336
|
+
take.rampEffectParam(liveTremolo, 'depth', 0.8, { duration: 250 });
|
|
337
|
+
|
|
338
|
+
guitar.removeEffect(distortion); // future playbacks no longer build it
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Advanced paper-backed effects
|
|
342
|
+
|
|
343
|
+
The advanced suite adds five non-neural effects derived from published DSP
|
|
344
|
+
algorithms. They work as per-source recipes or shared bus effects and expose
|
|
345
|
+
their live controls through `rampEffectParam`.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// Equal-Hz SSB translation: harmonic material becomes bell-like/inharmonic.
|
|
349
|
+
guitar.addEffect(cacophony.createFrequencyShifter({ frequency: 180 }));
|
|
350
|
+
|
|
351
|
+
// Endlessly descending notches (use a positive rate to reverse direction).
|
|
352
|
+
drums.addEffect(cacophony.createBarberpole({ rate: -0.1, stages: 32 }));
|
|
353
|
+
|
|
354
|
+
// Dry voice plus a fifth and octave, all produced in one STFT analysis pass.
|
|
355
|
+
vocals.addEffect(cacophony.createHarmonizer({
|
|
356
|
+
semitonesA: 7,
|
|
357
|
+
semitonesB: 12,
|
|
358
|
+
gainA: 0.6,
|
|
359
|
+
gainB: 0.45,
|
|
360
|
+
}));
|
|
361
|
+
|
|
362
|
+
// Capture/release a phase-continuing spectral texture at runtime.
|
|
363
|
+
const freeze = await playback.addEffect(cacophony.createSpectralFreeze());
|
|
364
|
+
playback.rampEffectParam(freeze, 'freeze', 1); // capture
|
|
365
|
+
playback.rampEffectParam(freeze, 'freeze', 0); // return to live input
|
|
366
|
+
|
|
367
|
+
// Convert mono or stereo material to transient-preserving decorrelated stereo.
|
|
368
|
+
ambience.addEffect(cacophony.createStereoWidener({
|
|
369
|
+
width: 0.75,
|
|
370
|
+
decorrelation: 1,
|
|
371
|
+
transientProtection: 0.8,
|
|
372
|
+
}));
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`createFrequencyShifter` implements Wardle's Hilbert-transform single-sideband
|
|
376
|
+
modulator (DAFx-98). `createBarberpole` implements the warped spectral-delay SSB
|
|
377
|
+
structure of Esqueda, Valimaki and Parker (DAFx-15). `createHarmonizer` extends
|
|
378
|
+
the Laroche-Dolson identity-phase-locked peak shifter to two simultaneous
|
|
379
|
+
voices. `createSpectralFreeze` preserves measured inter-frame phase advance
|
|
380
|
+
instead of looping one static FFT frame. `createStereoWidener` uses sparse
|
|
381
|
+
velvet-noise decorrelation and temporarily retreats to the direct path for
|
|
382
|
+
transients.
|
|
383
|
+
|
|
384
|
+
Keep shared space effects such as reverb on a bus and feed them with a wet
|
|
385
|
+
send. This leaves the playback's dry character chain intact while every source
|
|
386
|
+
sent to the bus shares one live reverb instance.
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
const vocals = await cacophony.createSound('vocals.mp3');
|
|
390
|
+
vocals.addEffect(cacophony.createDistortion({ drive: 1.5 })); // dry, per playback
|
|
391
|
+
|
|
392
|
+
const chamber = cacophony.createBus('chamber');
|
|
393
|
+
await chamber.addFilter(cacophony.createReverb({ wet: 1, dry: 0 }));
|
|
394
|
+
vocals.routeTo(chamber, 0.25); // 25% wet send; primary output stays on master
|
|
395
|
+
|
|
396
|
+
vocals.play();
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## Custom Audio Routing
|
|
296
400
|
|
|
297
401
|
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.
|
|
298
402
|
|
|
@@ -738,16 +842,22 @@ Capture, process, and manipulate live audio input:
|
|
|
738
842
|
```typescript
|
|
739
843
|
const cacophony = new Cacophony();
|
|
740
844
|
|
|
741
|
-
try {
|
|
742
|
-
const micStream = await cacophony.getMicrophoneStream(
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
845
|
+
try {
|
|
846
|
+
const micStream = await cacophony.getMicrophoneStream({
|
|
847
|
+
panType: 'stereo',
|
|
848
|
+
stereoPan: 0,
|
|
849
|
+
constraints: {
|
|
850
|
+
audio: { echoCancellation: true }
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// Apply filters to microphone input
|
|
855
|
+
const lowPassFilter = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 });
|
|
856
|
+
micStream.addFilter(lowPassFilter);
|
|
857
|
+
|
|
858
|
+
// Control microphone volume
|
|
859
|
+
micStream.volume = 0.8;
|
|
860
|
+
micStream.play();
|
|
751
861
|
|
|
752
862
|
// Pause and resume
|
|
753
863
|
setTimeout(() => {
|
|
@@ -755,17 +865,142 @@ try {
|
|
|
755
865
|
setTimeout(() => micStream.resume(), 2000);
|
|
756
866
|
}, 5000);
|
|
757
867
|
|
|
758
|
-
} catch (error) {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
868
|
+
} catch (error) {
|
|
869
|
+
// Permission and device failures reject getMicrophoneStream().
|
|
870
|
+
console.error("Error accessing microphone:", error);
|
|
871
|
+
}
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
Microphone monitoring is routed through the master bus, so global volume,
|
|
875
|
+
mute, master effects, and metering apply. Each playback exposes the standard
|
|
876
|
+
play, pause, stop, and error event surface and is fully disconnected when the
|
|
877
|
+
microphone is stopped.
|
|
766
878
|
|
|
767
|
-
|
|
768
|
-
|
|
879
|
+
## Audio Streaming
|
|
880
|
+
|
|
881
|
+
`createStream()` incrementally fetches and demuxes MP3, ADTS/MP4 AAC, Ogg,
|
|
882
|
+
FLAC, and WAVE audio. A WebCodecs `AudioDecoder` produces PCM chunks, which are
|
|
883
|
+
resampled to the active audio context and written into the same AudioWorklet
|
|
884
|
+
ring buffer used by `createPcmStreamSound()`. The returned stream is therefore
|
|
885
|
+
sample-accurately scheduled, pannable, filterable, bus-routable, and meterable.
|
|
886
|
+
|
|
887
|
+
The current streaming resampler uses linear interpolation without an
|
|
888
|
+
anti-aliasing filter. When downsampling, source frequencies above the target
|
|
889
|
+
Nyquist frequency can alias into the audible band; this is an accepted v1
|
|
890
|
+
quality trade-off pending a future band-limited resampler.
|
|
891
|
+
|
|
892
|
+
```typescript
|
|
893
|
+
const controller = new AbortController();
|
|
894
|
+
const stream = await cacophony.createStream('/music.ogg', controller.signal, 'stereo');
|
|
895
|
+
|
|
896
|
+
console.log(stream.streamCapabilities);
|
|
897
|
+
// {
|
|
898
|
+
// transport: 'webcodecs',
|
|
899
|
+
// seekable: true,
|
|
900
|
+
// live: false,
|
|
901
|
+
// duration: 183.42,
|
|
902
|
+
// }
|
|
903
|
+
|
|
904
|
+
stream.routeTo(musicBus);
|
|
905
|
+
stream.addFilter(cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1800 }));
|
|
906
|
+
await cacophony.createLoudnessMeter(musicBus);
|
|
907
|
+
stream.play();
|
|
908
|
+
stream.seek(30); // reopens the decoder at 30s when the server supports byte ranges
|
|
909
|
+
|
|
910
|
+
controller.abort(); // cancels fetch/decoder work and tears down the PCM worklet
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
`seekable` becomes `true` only after the server answers a byte-range request
|
|
914
|
+
with a range response. Live sources report `live: true`, `seekable: false`, and
|
|
915
|
+
`duration: Infinity`; calling `seek()` on one throws a clear error. A finite
|
|
916
|
+
file whose duration metadata is unavailable also reports `Infinity` rather
|
|
917
|
+
than inventing a duration.
|
|
918
|
+
|
|
919
|
+
When `AudioDecoder` is unavailable, or the primary audio track cannot be
|
|
920
|
+
decoded by WebCodecs, `createStream()` returns the compatibility
|
|
921
|
+
media-element tier. Its `streamCapabilities.transport` is `'media-element'`;
|
|
922
|
+
the browser owns buffering, seeking, and live-stream behavior in that tier.
|
|
923
|
+
|
|
924
|
+
### Media-element streaming contract
|
|
925
|
+
|
|
926
|
+
`createSound(url, 'streaming')` selects the media-element tier directly.
|
|
927
|
+
`createStream(url)` returns the same tier when WebCodecs is unavailable or
|
|
928
|
+
cannot decode the primary track, and always uses it for HLS. After metadata
|
|
929
|
+
loads, `streamCapabilities` reports the browser's duration and whether it
|
|
930
|
+
currently exposes a seekable range. A live source has `live: true` and
|
|
931
|
+
`duration: Infinity`; an ordinary finite resource reports its real duration.
|
|
932
|
+
The `Sound` itself has `duration === NaN` while unplayed because it has no
|
|
933
|
+
playback yet. After `preplay()` or `play()`, `sound.duration` and
|
|
934
|
+
`playback.duration` read the loaded media element and return the finite value
|
|
935
|
+
or `Infinity` for a live source.
|
|
936
|
+
|
|
937
|
+
Media playback moves from unplayed to playing only after the element's
|
|
938
|
+
`play()` promise resolves. `pause()` moves it to paused, `resume()` calls
|
|
939
|
+
`play()` again and returns it to playing, and `stop()` moves it to stopped and
|
|
940
|
+
resets it. Playback events follow those accepted transitions: `play`, `pause`,
|
|
941
|
+
then both `play` and `resume` on a resume, followed by `stop`. The owning
|
|
942
|
+
`Sound` emits `play` after the initial accepted play, plus its collective
|
|
943
|
+
`pause`, `resume`, and `stop` events.
|
|
944
|
+
|
|
945
|
+
`seek(time)` writes the media element's `currentTime` only when the browser
|
|
946
|
+
exposes a seekable range. A live or finite media source without such a range
|
|
947
|
+
throws a descriptive error and remains at its current position. Infinite
|
|
948
|
+
looping uses the media element's native `loop`; finite loop counts are managed
|
|
949
|
+
by Cacophony and emit `loopEnd` between iterations before the final `ended`.
|
|
950
|
+
|
|
951
|
+
Every `preplay()` creates an independent media element, so a media-backed
|
|
952
|
+
`Sound` supports concurrent playback instances. `cleanup()` pauses each
|
|
953
|
+
element, clears its `src`, calls `load()` to release the resource, disconnects
|
|
954
|
+
its Web Audio nodes, and removes the playback. Offline `timeStretch()` requires
|
|
955
|
+
a decoded `AudioBuffer`, so calling `timeStretch()` on this tier throws a
|
|
956
|
+
predictable buffer-required error.
|
|
957
|
+
|
|
958
|
+
For audio that arrives as decoded samples instead of a URL, use the
|
|
959
|
+
AudioWorklet-backed push source:
|
|
960
|
+
|
|
961
|
+
```typescript
|
|
962
|
+
const pcm = await cacophony.createPcmStreamSound({
|
|
963
|
+
channelCount: 1,
|
|
964
|
+
bufferDuration: 1,
|
|
965
|
+
latency: 0.05,
|
|
966
|
+
signal: abortController.signal,
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
pcm.on('underrun', () => console.warn('PCM producer fell behind'));
|
|
970
|
+
pcm.on('drain', () => writeNextChunk());
|
|
971
|
+
pcm.on('ended', () => console.log('PCM playback finished'));
|
|
972
|
+
|
|
973
|
+
pcm.play();
|
|
974
|
+
const hasCapacity = pcm.write(samples); // interleaved Float32Array at context.sampleRate
|
|
975
|
+
if (!hasCapacity) {
|
|
976
|
+
// The accepted write filled the ring buffer, or the chunk was rejected
|
|
977
|
+
// because it would exceed capacity. Wait for drain before writing again.
|
|
978
|
+
}
|
|
979
|
+
console.log(pcm.bufferedDuration);
|
|
980
|
+
pcm.end(); // no more writes; "ended" fires after buffered PCM is consumed
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
`bufferDuration` fixes the ring-buffer capacity; `latency` sets how much PCM is
|
|
984
|
+
collected before initial consumption. `write()` returns `false` when the buffer
|
|
985
|
+
needs producer backpressure. An accepted write that fills the buffer also
|
|
986
|
+
returns `false`; a chunk that would exceed remaining capacity is rejected and
|
|
987
|
+
must be retried after `drain`.
|
|
988
|
+
|
|
989
|
+
PCM underrun emits silence plus one `underrun` event per underrun episode;
|
|
990
|
+
writing more samples recovers playback without repeating stale audio.
|
|
991
|
+
`pause()` stops consuming and `resume()` continues from the buffered frame.
|
|
992
|
+
Volume, stereo/HRTF pan, filters, buses, and sends use the same public APIs as
|
|
993
|
+
other sources. Seek is not supported for a push PCM source. Loop is not
|
|
994
|
+
supported because the source does not retain consumed samples.
|
|
995
|
+
|
|
996
|
+
Adaptive HLS/DASH and DRM are outside the WebCodecs pull transport. `.m3u8`
|
|
997
|
+
URLs use the media tier: native HLS where `HTMLAudioElement.canPlayType()`
|
|
998
|
+
reports support (notably Safari), then the optional `hls.js` peer in
|
|
999
|
+
MSE-capable browsers. Install that peer with `npm install hls.js`. DASH and DRM
|
|
1000
|
+
remain outside this integration.
|
|
1001
|
+
|
|
1002
|
+
```typescript
|
|
1003
|
+
const cacophony = new Cacophony();
|
|
769
1004
|
|
|
770
1005
|
try {
|
|
771
1006
|
const streamedSound = await cacophony.createStream('https://example.com/live_radio_stream');
|
package/dist/autoplayUnlock.d.ts
CHANGED
|
@@ -16,12 +16,12 @@ export interface AutoplayUnlockOptions {
|
|
|
16
16
|
onUnlock: () => void;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* instance is torn down
|
|
19
|
+
* Watch for context suspension and install unlock listeners while needed.
|
|
20
|
+
* Returns a cleanup function that removes both the context watcher and any
|
|
21
|
+
* armed gesture listeners (e.g. when a `Cacophony` instance is torn down).
|
|
22
22
|
*
|
|
23
|
-
* If the environment is non-browser (`document` is undefined)
|
|
24
|
-
*
|
|
23
|
+
* If the environment is non-browser (`document` is undefined), this is a
|
|
24
|
+
* no-op and returns a no-op cleanup.
|
|
25
25
|
*
|
|
26
26
|
* @internal
|
|
27
27
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const nodeBackendAvailable: boolean;
|
package/dist/bus.d.ts
CHANGED
|
@@ -39,16 +39,7 @@ export declare class Bus {
|
|
|
39
39
|
*/
|
|
40
40
|
readonly output: GainNode;
|
|
41
41
|
private readonly _context;
|
|
42
|
-
private readonly
|
|
43
|
-
/**
|
|
44
|
-
* Filter nodes currently bypassed (skipped in the audible chain). A bypassed
|
|
45
|
-
* node stays in {@link _filterEntries} — so {@link filters} order, identity, and
|
|
46
|
-
* its live AudioParams are preserved — but {@link _desiredFilterChainEdges}
|
|
47
|
-
* builds the series chain over the NON-bypassed filters only, wiring the
|
|
48
|
-
* signal around it. Membership is by node identity.
|
|
49
|
-
*/
|
|
50
|
-
private readonly _bypassedFilters;
|
|
51
|
-
private readonly _filterChainEdges;
|
|
42
|
+
private readonly _effectChain;
|
|
52
43
|
private readonly _sendGains;
|
|
53
44
|
private readonly _directConnections;
|
|
54
45
|
/**
|
|
@@ -64,6 +55,7 @@ export declare class Bus {
|
|
|
64
55
|
* the named-bus registry on destroy. Anonymous buses leave this undefined.
|
|
65
56
|
*/
|
|
66
57
|
private readonly _onDestroy?;
|
|
58
|
+
private readonly _destroyable;
|
|
67
59
|
private _destroyed;
|
|
68
60
|
/**
|
|
69
61
|
* @param context Web Audio context the bus's nodes live on.
|
|
@@ -72,8 +64,9 @@ export declare class Bus {
|
|
|
72
64
|
* the `master` bus to alias `cacophony.globalGainNode`. If omitted, a
|
|
73
65
|
* fresh GainNode is allocated.
|
|
74
66
|
* @param onDestroy Optional registry-cleanup hook fired by destroy().
|
|
67
|
+
* @param destroyable Whether destroy() may tear down this bus.
|
|
75
68
|
*/
|
|
76
|
-
constructor(context: BaseContext, name?: string | null, input?: GainNode, onDestroy?: () => void);
|
|
69
|
+
constructor(context: BaseContext, name?: string | null, input?: GainNode, onDestroy?: () => void, destroyable?: boolean);
|
|
77
70
|
/** True after {@link destroy} has been called. */
|
|
78
71
|
get destroyed(): boolean;
|
|
79
72
|
/** Output node gain — controls the overall level the bus sends downstream. */
|
|
@@ -111,9 +104,9 @@ export declare class Bus {
|
|
|
111
104
|
/**
|
|
112
105
|
* Reorder the existing filter chain. `nodes` must be a PERMUTATION of the
|
|
113
106
|
* current filters — the same set of node objects (matched by identity), the
|
|
114
|
-
* same length, with no duplicates — just in a new order. Because
|
|
115
|
-
* {@link
|
|
116
|
-
* are reconnected; unchanged edges are left untouched.
|
|
107
|
+
* same length, with no duplicates — just in a new order. Because the owned
|
|
108
|
+
* {@link EffectChain} reconciles incrementally, only the edges that actually
|
|
109
|
+
* move are reconnected; unchanged edges are left untouched.
|
|
117
110
|
*
|
|
118
111
|
* @throws if the bus has been destroyed, or if `nodes` is not a permutation
|
|
119
112
|
* of the current filters.
|
|
@@ -126,8 +119,8 @@ export declare class Bus {
|
|
|
126
119
|
* it is skipped in the audible series chain: the signal is wired around it.
|
|
127
120
|
* Un-bypassing wires it back in at its original position.
|
|
128
121
|
*
|
|
129
|
-
* The reconnect goes through the incremental {@link
|
|
130
|
-
*
|
|
122
|
+
* The reconnect goes through the incremental {@link EffectChain}, so only the
|
|
123
|
+
* seam around `node` is touched — the rest of the chain is left connected.
|
|
131
124
|
*
|
|
132
125
|
* @param node A filter node currently on this bus (from {@link addFilter} or
|
|
133
126
|
* {@link filters}).
|
|
@@ -180,21 +173,6 @@ export declare class Bus {
|
|
|
180
173
|
duration?: number;
|
|
181
174
|
type?: FadeType;
|
|
182
175
|
}): void;
|
|
183
|
-
/**
|
|
184
|
-
* Resolve the named {@link AudioParam} on a node. Tries the worklet
|
|
185
|
-
* `parameters` map first, then a directly-exposed native param
|
|
186
|
-
* (`node[paramName]`). Returns `undefined` if neither yields an AudioParam.
|
|
187
|
-
*
|
|
188
|
-
* Detection is structural (duck-typed), never `instanceof` — the mocked test
|
|
189
|
-
* context may not provide the `AudioParam` global.
|
|
190
|
-
*/
|
|
191
|
-
private _resolveAudioParam;
|
|
192
|
-
/**
|
|
193
|
-
* Structural AudioParam check: a value is treated as an AudioParam if it
|
|
194
|
-
* exposes the ramp scheduling methods. Avoids `instanceof AudioParam` so it
|
|
195
|
-
* works under the standardized-audio-context mock (which may lack the global).
|
|
196
|
-
*/
|
|
197
|
-
private _isAudioParam;
|
|
198
176
|
/**
|
|
199
177
|
* Connect this bus's output to another bus or to a raw AudioNode.
|
|
200
178
|
*
|
|
@@ -258,32 +236,5 @@ export declare class Bus {
|
|
|
258
236
|
destroy(options?: {
|
|
259
237
|
drainTo?: Bus;
|
|
260
238
|
}): void;
|
|
261
|
-
/**
|
|
262
|
-
* Reconcile the live chain to `input → [filter1 → ... → filterN] → output`.
|
|
263
|
-
* Called after any add/remove/reorder of a filter. This is an INCREMENTAL
|
|
264
|
-
* diff, not a full rebuild: it disconnects only edges that are no longer part
|
|
265
|
-
* of the desired chain and connects only edges that are newly required,
|
|
266
|
-
* leaving edges present in both the old and new chain connected and
|
|
267
|
-
* untouched (no audible click on the unchanged portion of the chain).
|
|
268
|
-
*
|
|
269
|
-
* Edges are matched by OBJECT IDENTITY on both endpoints. Only this bus's own
|
|
270
|
-
* internal `input → ... → output` edges are touched — never a broad
|
|
271
|
-
* `node.disconnect()`, never the outbound send/direct edges.
|
|
272
|
-
*/
|
|
273
|
-
private _refreshFilters;
|
|
274
|
-
/**
|
|
275
|
-
* Compute the desired ordered chain edge list from the current
|
|
276
|
-
* `_filterEntries`, skipping any node in {@link _bypassedFilters}: the series
|
|
277
|
-
* chain is built over the NON-bypassed filters only. With no active (non-
|
|
278
|
-
* bypassed) filters — whether the bus has no filters at all or every filter is
|
|
279
|
-
* bypassed — the desired list is `[[input, output]]` (the direct edge);
|
|
280
|
-
* otherwise `[[input, a1], [a1, a2], ..., [aN, output]]` over the active
|
|
281
|
-
* filters `a1..aN`. Bypassed nodes stay in {@link _filterEntries} (and thus in
|
|
282
|
-
* {@link filters}) but receive no inbound/outbound chain edge.
|
|
283
|
-
*/
|
|
284
|
-
private _desiredFilterChainEdges;
|
|
285
|
-
private _normalizeBuiltEffect;
|
|
286
|
-
private _connectFilterChainEdge;
|
|
287
|
-
private _disconnectFilterChainEdges;
|
|
288
239
|
private _throwIfDestroyed;
|
|
289
240
|
}
|
package/dist/cache.d.ts
CHANGED
|
@@ -33,11 +33,19 @@ export interface ICache {
|
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
35
|
export declare class AudioCache implements ICache {
|
|
36
|
-
private
|
|
37
|
-
private
|
|
38
|
-
private
|
|
36
|
+
private pendingRequests;
|
|
37
|
+
private pendingCallbacks;
|
|
38
|
+
private decodedBuffers;
|
|
39
39
|
private static cacheExpirationTime;
|
|
40
40
|
static setCacheExpirationTime(time: number): void;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the browser Cache API (`caches`) global is available in the
|
|
43
|
+
* current runtime. Uses `typeof` so it never throws when the global is
|
|
44
|
+
* entirely absent (e.g. Node). When this returns `false`, the URL path
|
|
45
|
+
* degrades to fetch + decode (still using the in-memory LRU) instead of
|
|
46
|
+
* touching the persistent Cache API.
|
|
47
|
+
*/
|
|
48
|
+
private static isCacheApiAvailable;
|
|
41
49
|
private static openCache;
|
|
42
50
|
/**
|
|
43
51
|
* Calls all registered callbacks for a specific event type on a URL.
|
|
@@ -45,10 +53,10 @@ export declare class AudioCache implements ICache {
|
|
|
45
53
|
* the canonical {@link CacheCallbacks} shape rather than `any`.
|
|
46
54
|
*/
|
|
47
55
|
private static callAllCallbacks;
|
|
48
|
-
private
|
|
56
|
+
private getOrCreatePendingRequest;
|
|
49
57
|
private static updateMetadata;
|
|
50
58
|
private static getBufferFromCache;
|
|
51
|
-
private
|
|
59
|
+
private fetchAndCacheBuffer;
|
|
52
60
|
/**
|
|
53
61
|
* Atomically write a response body and its metadata into the cache.
|
|
54
62
|
* On failure, deletes both partial entries (best-effort via `allSettled`)
|
|
@@ -65,13 +73,31 @@ export declare class AudioCache implements ICache {
|
|
|
65
73
|
* @param signal - Optional AbortSignal observed at chunk boundaries
|
|
66
74
|
* @returns Object containing the progress-tracking stream and total size
|
|
67
75
|
*/
|
|
68
|
-
private
|
|
76
|
+
private createProgressTrackingStream;
|
|
69
77
|
/**
|
|
70
78
|
* Collects all chunks from a ReadableStream into a single ArrayBuffer
|
|
71
79
|
* @param stream - The ReadableStream to collect from
|
|
72
80
|
* @returns Promise that resolves to the complete ArrayBuffer
|
|
73
81
|
*/
|
|
74
82
|
private static collectStreamToArrayBuffer;
|
|
83
|
+
/**
|
|
84
|
+
* Fetch + decode an audio URL WITHOUT touching the persistent Cache API.
|
|
85
|
+
* Used when the Cache API global (`caches`) is unavailable (e.g. Node).
|
|
86
|
+
*
|
|
87
|
+
* Mirrors the network branch of {@link fetchAndCacheBuffer}/{@link getAudioBuffer}
|
|
88
|
+
* but never reads from or writes to `caches`. Still:
|
|
89
|
+
* - uses the in-memory decoded-buffer LRU (write happens in {@link getAudioBuffer}),
|
|
90
|
+
* - emits progress callbacks via {@link createProgressTrackingStream},
|
|
91
|
+
* - is deduped through {@link getOrCreatePendingRequest} by the caller.
|
|
92
|
+
*/
|
|
93
|
+
private fetchAndDecodeWithoutCache;
|
|
94
|
+
/**
|
|
95
|
+
* Run the shared network-result pipeline: fetch bytes, decode them, emit
|
|
96
|
+
* loading callbacks, and retain the decoded buffer in the in-memory LRU.
|
|
97
|
+
* The caller supplies the fetch strategy so persistent-cache and degraded
|
|
98
|
+
* runtimes share this plumbing without changing their network behavior.
|
|
99
|
+
*/
|
|
100
|
+
private fetchDecodeAndCache;
|
|
75
101
|
private static decodeAudioData;
|
|
76
102
|
private static getMetadataFromCache;
|
|
77
103
|
/**
|