https://github.com/ctoth/cacophony
Powerful Typescript Webaudio library with built-in cache support
https://github.com/ctoth/cacophony
Last synced: about 2 months ago
JSON representation
Powerful Typescript Webaudio library with built-in cache support
- Host: GitHub
- URL: https://github.com/ctoth/cacophony
- Owner: ctoth
- License: mit
- Created: 2023-08-18T05:27:02.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2026-02-19T20:07:04.000Z (4 months ago)
- Last Synced: 2026-02-19T22:24:38.522Z (4 months ago)
- Language: TypeScript
- Size: 2.4 MB
- Stars: 7
- Watchers: 2
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# Cacophony: Advanced Browser Audio Library
Cacophony is a powerful and intuitive audio library designed for modern web applications. It provides a high-level interface to the Web Audio API, simplifying complex audio operations while offering fine-grained control. Cacophony is perfect for projects ranging from simple sound playback to sophisticated audio processing and 3D audio positioning.
## Key Features
- **Versatile Audio Source Handling**: Manage audio from various sources including `AudioBuffer`, URL strings, synthesizers, and live microphone input
- **Comprehensive Playback Control**: Play, stop, pause, resume, loop, and seek within audio with ease
- **3D Audio Positioning**: Create immersive soundscapes with precise spatial audio control
- **Advanced Audio Processing**: Apply and manage a variety of audio filters for enhanced sound manipulation
- **Dynamic Volume Control**: Adjust global and individual volume levels with support for smooth fading effects
- **Synthesizer Integration**: Create and manipulate synthesized sounds with customizable oscillator options
- **Efficient Group Management**: Organize and control multiple sounds or synthesizers as groups for streamlined audio management
- **Live Microphone Input**: Capture and process real-time audio input from the user's microphone
- **Network-Backed Playback**: Play audio directly from URLs using media-element-backed sounds
- **Flexible Caching**: Implement efficient audio caching strategies for improved performance
## Installation
```bash
npm install cacophony
```
## Quick Start
```typescript
import { Cacophony } from 'cacophony';
async function audioDemo() {
const cacophony = new Cacophony();
// Create and play a sound with 3D positioning
const sound = await cacophony.createSound('path/to/audio.mp3');
sound.play();
sound.position = [1, 0, -1]; // Set sound position in 3D space
// Create and play a synthesizer
const synth = cacophony.createOscillator({ frequency: 440, type: 'sine' });
synth.play();
// Apply a filter to the synth
const filter = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 });
synth.addFilter(filter);
// Create a group of sounds
const group = await cacophony.createGroupFromUrls(['sound1.mp3', 'sound2.mp3']);
group.play(); // Play all sounds in the group
// Capture microphone input
const micStream = await cacophony.getMicrophoneStream();
micStream.play();
}
audioDemo();
```
## Core Concepts
### Sound vs Playback Architecture
Cacophony uses a two-tier architecture that separates audio assets from their playback instances:
- **Sound**: Represents an audio asset (file or buffer). Acts as a container managing multiple playback instances.
- **Playback**: Represents a single playback instance of a Sound. Each call to `play()` creates a new Playback.
This design allows the same sound to be played multiple times simultaneously with different settings (volume, position, playback rate, etc.).
```typescript
const sound = await cacophony.createSound('laser.mp3');
// Play the same sound three times with different settings
const [playback1] = sound.play();
playback1.volume = 0.3;
playback1.playbackRate = 1.0;
const [playback2] = sound.play();
playback2.volume = 0.8;
playback2.playbackRate = 1.5; // Higher pitched
const [playback3] = sound.play();
playback3.volume = 0.5;
playback3.position = [5, 0, 0]; // Positioned to the right
// Control individual playbacks
setTimeout(() => playback1.pause(), 1000);
setTimeout(() => playback2.stop(), 2000);
// Or control all playbacks through the Sound
sound.stop(); // Stops all three playbacks
```
## Sound Types
Cacophony supports three sound types:
| Type | Memory | Latency | Seeking | Multiple Instances | Best For |
|------|--------|---------|---------|-------------------|----------|
| **Buffer** (default) | High | None | Full | Yes | Sound effects, UI sounds, short music clips |
| **HTML** | Medium | Low | Full | Yes | Background music, large audio files, podcasts |
| **Streaming** | Medium | Low | Full | Yes | Network-backed playback created via `createStream()` |
```typescript
// Buffer - entire file loaded into memory
const sfx = await cacophony.createSound('explosion.mp3', SoundType.Buffer);
// HTML - streams from network, good for large files
const music = await cacophony.createSound('bgm.mp3', SoundType.HTML);
// Streaming - convenience helper for network-backed playback
const radio = await cacophony.createStream('https://example.com/stream.m3u8');
```
## Playback Control
### Seeking
```typescript
const sound = await cacophony.createSound('podcast.mp3');
sound.play();
// Seek to 30 seconds
sound.seek(30);
// Seek on individual playback
const [playback] = sound.play();
playback.seek(45);
// Get current time
console.log(playback.currentTime); // Current position in seconds
console.log(playback.duration); // Total duration
```
### Playback Rate
Control the speed of playback (affects pitch):
```typescript
const sound = await cacophony.createSound('audio.mp3');
sound.playbackRate = 1.0; // Normal speed
sound.playbackRate = 2.0; // Double speed (higher pitch)
sound.playbackRate = 0.5; // Half speed (lower pitch)
// Apply to individual playback
const [playback] = sound.play();
playback.playbackRate = 1.25;
```
### Looping
```typescript
const sound = await cacophony.createSound('music.mp3');
// Loop indefinitely
sound.loop('infinite');
sound.play();
// Loop exactly 3 times
sound.loop(3);
sound.play();
// No looping (default)
sound.loop(0);
```
## Volume Control
Cacophony provides a hierarchical volume control system:
```typescript
const cacophony = new Cacophony();
const sound = await cacophony.createSound('audio.mp3');
const [playback] = sound.play();
// Global volume (affects all audio)
cacophony.volume = 0.5; // 50% of original volume
// Sound volume (affects all playbacks of this sound)
sound.volume = 0.8; // 80% of global volume
// Individual playback volume
playback.volume = 0.6; // 60% of sound volume
// Final volume = global × sound × playback = 0.5 × 0.8 × 0.6 = 0.24
// Mute/unmute
cacophony.mute();
cacophony.unmute();
```
## Audio Filters
Cacophony provides powerful audio filtering capabilities using BiquadFilterNode. Filters can be applied to Sounds, Synths, Playbacks, and Groups.
Supported filter types: `lowpass`, `highpass`, `bandpass`, `lowshelf`, `highshelf`, `peaking`, `notch`, `allpass`.
```typescript
const cacophony = new Cacophony();
const sound = await cacophony.createSound('audio.mp3');
// Create filters
const lowpass = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000, Q: 1 });
const highshelf = cacophony.createBiquadFilter({ type: 'highshelf', frequency: 5000, gain: -6 });
// Apply filters (they are chained in order)
sound.addFilter(lowpass);
sound.addFilter(highshelf);
// Apply to Synth
const synth = cacophony.createOscillator({ frequency: 440, type: 'sawtooth' });
synth.addFilter(lowpass);
// Remove filters
sound.removeFilter(lowpass);
// Note: Filters are cloned to each playback for independent processing
const playback = sound.play()[0];
playback.filters[0].frequency.value = 500; // Only affects this playback
```
See [TypeDoc](https://cacophony.js.org) for complete filter parameters and options.
## Custom Audio Routing
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.
### Accessing the Audio Graph
Every Playback instance exposes its output node and standard Web Audio connection methods:
```typescript
const sound = await cacophony.createSound('audio.mp3');
const [playback] = sound.play();
// Access the output node (final node in the internal chain)
const outputNode = playback.outputNode; // Returns GainNode
// Connect to custom destination
const customEffect = cacophony.context.createDelay(0.5);
playback.connect(customEffect);
customEffect.connect(cacophony.context.destination);
// Disconnect from default routing
playback.disconnect(); // Disconnect from all
playback.disconnect(specificNode); // Disconnect from specific node
```
### Building Effect Chains
Route playbacks through multiple effects:
```typescript
const sound = await cacophony.createSound('guitar.mp3');
const [playback] = sound.play();
// Create effect nodes
const distortion = cacophony.context.createWaveShaper();
const delay = cacophony.context.createDelay(1.0);
const reverb = cacophony.context.createConvolver();
// Chain: playback → distortion → delay → reverb → destination
playback.disconnect(); // Disconnect from default
playback.connect(distortion)
.connect(delay)
.connect(reverb)
.connect(cacophony.context.destination);
```
### Parallel Effects (Send/Return)
Create parallel effect sends like a mixing console:
```typescript
const sound = await cacophony.createSound('vocals.mp3');
const [playback] = sound.play();
// Create send effects
const reverb = cacophony.context.createConvolver();
const reverbReturn = cacophony.context.createGain();
reverbReturn.gain.value = 0.3; // 30% wet
// Dry signal to destination
playback.connect(cacophony.context.destination);
// Parallel wet signal: playback → reverb → reverbReturn → destination
playback.connect(reverb)
.connect(reverbReturn)
.connect(cacophony.context.destination);
```
### Dynamic Routing
Change routing in real-time:
```typescript
const sound = await cacophony.createSound('audio.mp3');
const [playback] = sound.play();
const cleanPath = cacophony.context.destination;
const fxPath = cacophony.context.createConvolver();
fxPath.connect(cacophony.context.destination);
// Toggle between clean and effects
let useFx = false;
button.addEventListener('click', () => {
playback.disconnect();
if (useFx) {
playback.connect(fxPath);
} else {
playback.connect(cleanPath);
}
useFx = !useFx;
});
```
### Integrating with Web Audio Nodes
Cacophony works seamlessly with any Web Audio API nodes:
```typescript
const [playback] = sound.play();
// Built-in nodes
const analyser = cacophony.context.createAnalyser();
const compressor = cacophony.context.createDynamicsCompressor();
const panner = cacophony.context.createStereoPanner();
// AudioWorklet processors
const customProcessor = new AudioWorkletNode(cacophony.context, 'my-processor');
// Chain them together
playback.disconnect();
playback.connect(compressor)
.connect(analyser)
.connect(panner)
.connect(customProcessor)
.connect(cacophony.context.destination);
// Visualize with analyser
const dataArray = new Uint8Array(analyser.frequencyBinCount);
function draw() {
analyser.getByteFrequencyData(dataArray);
// ... draw visualization
requestAnimationFrame(draw);
}
```
### Architecture Notes
- **Internal chain**: `source → panner → [filters] → gainNode`
- **outputNode** exposes the final `gainNode` in this chain
- Default routing: `outputNode → globalGainNode → destination`
- Calling `disconnect()` breaks the default routing, allowing full manual control
## Cloning
Clone sounds to create variations without reloading files. Clones share the same AudioBuffer but have independent settings.
```typescript
const footstep = await cacophony.createSound('footstep.mp3');
// Create variations for different enemy sizes
const largeEnemy = footstep.clone({
position: [10, 0, -5],
playbackRate: 0.8, // Slower/lower pitch
volume: 0.9
});
const smallEnemy = footstep.clone({
position: [-5, 0, -10],
playbackRate: 1.25, // Faster/higher pitch
volume: 0.4
});
largeEnemy.play();
smallEnemy.play();
// Weapon sound variants
const gunshotBase = await cacophony.createSound('gunshot.mp3');
const weapons = {
pistol: gunshotBase.clone({ volume: 0.6, playbackRate: 1.2 }),
rifle: gunshotBase.clone({ volume: 1.0, playbackRate: 1.0 }),
shotgun: gunshotBase.clone({
volume: 1.2,
playbackRate: 0.8,
filters: [cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1200 })]
})
};
// Musical instrument samples by pitch-shifting
const pianoC4 = await cacophony.createSound('piano_c4.mp3');
const keyboard = {
C4: pianoC4,
D4: pianoC4.clone({ playbackRate: 1.122 }), // +2 semitones
E4: pianoC4.clone({ playbackRate: 1.260 }), // +4 semitones
F4: pianoC4.clone({ playbackRate: 1.335 }), // +5 semitones
G4: pianoC4.clone({ playbackRate: 1.498 }), // +7 semitones
};
```
## Group Functionality
Groups allow controlling multiple sounds or synthesizers as a single unit.
```typescript
const cacophony = new Cacophony();
// Create from URLs
const soundGroup = await cacophony.createGroupFromUrls(['drum.mp3', 'bass.mp3', 'synth.mp3']);
// Or create from existing Sound instances
const sound1 = await cacophony.createSound('drum.mp3');
const sound2 = await cacophony.createSound('bass.mp3');
const sound3 = await cacophony.createSound('synth.mp3');
const soundGroup2 = await cacophony.createGroup([sound1, sound2, sound3]);
// Play all sounds simultaneously
soundGroup.play();
// Control all sounds at once
soundGroup.volume = 0.7;
soundGroup.playbackRate = 1.2;
soundGroup.position = [5, 0, 0];
soundGroup.loop('infinite');
// Apply filters to entire group
const lowpass = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 });
soundGroup.addFilter(lowpass);
// Play random sound from group
const footsteps = await cacophony.createGroupFromUrls([
'footstep1.mp3',
'footstep2.mp3',
'footstep3.mp3',
'footstep4.mp3'
]);
footsteps.playRandom(); // Picks one at random
// Advance through sounds in sequence, one call at a time
const dialog = await cacophony.createGroupFromUrls(['line1.mp3', 'line2.mp3', 'line3.mp3']);
dialog.playOrdered(true); // Plays line1, then advances internal order
dialog.playOrdered(true); // Plays line2
dialog.playOrdered(true); // Plays line3
dialog.playOrdered(true); // Plays line1 again because looping is enabled
// SynthGroup for synthesizers
const synthGroup = new SynthGroup();
const synth1 = cacophony.createOscillator({ frequency: 440, type: 'sine' });
const synth2 = cacophony.createOscillator({ frequency: 660, type: 'square' });
synthGroup.addSynth(synth1);
synthGroup.addSynth(synth2);
synthGroup.play();
synthGroup.volume = 0.5;
synthGroup.type = 'triangle';
synthGroup.pause();
synthGroup.resume();
// Remove a synth from the group
synthGroup.removeSynth(synth1);
```
## Synthesizer Functionality
Create oscillator-based sounds with four waveform types: `sine`, `square`, `sawtooth`, `triangle`.
```typescript
const cacophony = new Cacophony();
// Create oscillators
const sineOsc = cacophony.createOscillator({ frequency: 440, type: 'sine' });
sineOsc.play();
// Change parameters in real-time
sineOsc.frequency = 880; // Change frequency
sineOsc.type = 'sawtooth'; // Change waveform
sineOsc.detune = 10; // Detune in cents (1/100th of a semitone)
// Layer multiple oscillators
const bass = cacophony.createOscillator({ frequency: 110, type: 'sine' });
const lead = cacophony.createOscillator({ frequency: 440, type: 'sawtooth' });
bass.volume = 0.7;
lead.volume = 0.5;
bass.addFilter(cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 }));
bass.play();
lead.play();
// Modulate frequency over time
let time = 0;
setInterval(() => {
const frequency = 440 + Math.sin(time) * 100;
sineOsc.frequency = frequency;
time += 0.1;
}, 50);
// Pause and resume the active synth playback without losing its settings
sineOsc.pause();
sineOsc.resume();
```
`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()`.
## 3D Audio Positioning
Create immersive soundscapes with precise spatial audio control using HRTF (Head-Related Transfer Function) or stereo panning.
```typescript
const cacophony = new Cacophony();
// Create sounds with HRTF panning
const ambience = await cacophony.createSound('forest_ambience.mp3', SoundType.Buffer, 'HRTF');
const birdSound = await cacophony.createSound('bird_chirp.mp3', SoundType.Buffer, 'HRTF');
const footsteps = await cacophony.createSound('footsteps.mp3', SoundType.Buffer, 'HRTF');
// Position sounds in 3D space
// Coordinate system: X (left- to right+), Y (down- to up+), Z (front+ to back-)
ambience.position = [0, 0, -5]; // Slightly behind the listener
birdSound.position = [10, 5, 0]; // To the right and above
footsteps.position = [-2, -1, 2]; // Slightly to the left and in front
// Configure distance attenuation
birdSound.threeDOptions = {
distanceModel: 'inverse', // 'linear', 'inverse', or 'exponential'
refDistance: 1,
rolloffFactor: 1
};
// Play the sounds
ambience.play();
birdSound.play();
footsteps.play();
// Set listener position and orientation
cacophony.listenerPosition = [0, 0, 0];
cacophony.listenerOrientation = {
forward: [0, 0, -1], // Looking towards negative Z
up: [0, 1, 0]
};
// Animate bird sound position
let time = 0;
setInterval(() => {
const x = Math.sin(time) * 10;
birdSound.position = [x, 5, 0];
time += 0.05;
}, 50);
// Stereo panning (simple left-right)
const stereoSound = await cacophony.createSound('audio.mp3', SoundType.Buffer, 'stereo');
stereoSound.stereoPan = 0.5; // -1 (left) to 1 (right)
stereoSound.play();
```
See [TypeDoc](https://cacophony.js.org) for distance models, cone effects, and advanced 3D audio options.
## Microphone Input
Capture, process, and manipulate live audio input:
```typescript
const cacophony = new Cacophony();
try {
const micStream = await cacophony.getMicrophoneStream();
micStream.play();
// Apply filters to microphone input
const lowPassFilter = cacophony.createBiquadFilter({ type: 'lowpass', frequency: 1000 });
micStream.addFilter(lowPassFilter);
// Control microphone volume
micStream.volume = 0.8;
// Pause and resume
setTimeout(() => {
micStream.pause();
setTimeout(() => micStream.resume(), 2000);
}, 5000);
} catch (error) {
console.error("Error accessing microphone:", error);
}
```
## Audio Streaming
Stream audio content efficiently:
```typescript
const cacophony = new Cacophony();
try {
const streamedSound = await cacophony.createStream('https://example.com/live_radio_stream');
streamedSound.play();
// Apply real-time effects to the stream
const highPassFilter = cacophony.createBiquadFilter({ type: 'highpass', frequency: 500 });
streamedSound.addFilter(highPassFilter);
// Control streaming playback
setTimeout(() => {
streamedSound.pause();
setTimeout(() => streamedSound.play(), 5000);
}, 10000);
} catch (error) {
console.error("Error streaming audio:", error);
}
```
## Event System
Cacophony provides a comprehensive event system for monitoring loading progress, cache performance, and audio playback state.
### Loading Progress
```typescript
const cacophony = new Cacophony();
cacophony.on('loadingStart', (event) => {
console.log(`Started loading: ${event.url}`);
showSpinner();
});
cacophony.on('loadingProgress', (event) => {
const percent = event.progress * 100;
updateProgressBar(event.progress);
});
cacophony.on('loadingComplete', (event) => {
console.log(`Loaded: ${event.url} (${event.size} bytes)`);
hideSpinner();
});
const sound = await cacophony.createSound('large-audio-file.mp3');
```
### Error Handling
```typescript
// Global error handling
cacophony.on('loadingError', (event) => {
console.error(`Failed to load ${event.url}:`, event.error);
showErrorToast(`Failed to load audio: ${event.errorType}`);
});
// Sound-specific error handling
sound.on('soundError', (event) => {
if (event.recoverable) {
console.log('Retrying...');
sound.play();
} else {
console.error('Unrecoverable error:', event.error);
}
});
```
### Global Playback Events
```typescript
// Monitor all playback globally
cacophony.on('globalPlay', (event) => {
console.log('Audio started:', event.source);
showGlobalAudioIndicator();
});
cacophony.on('globalStop', (event) => {
console.log('Audio stopped:', event.source);
hideGlobalAudioIndicator();
});
cacophony.on('globalPause', (event) => {
console.log('Audio paused:', event.source);
});
```
### Playback Events
```typescript
const sound = await cacophony.createSound('audio.mp3');
sound.on('play', (playback) => {
console.log('Playing:', playback);
updatePlayButton('pause');
});
sound.on('pause', () => {
updatePlayButton('play');
});
sound.on('volumeChange', (volume) => {
updateVolumeSlider(volume);
});
// Playback instance events
const [playback] = sound.play();
playback.on('play', (pb) => {
console.log('Playback started:', pb.currentTime);
});
playback.on('error', (event) => {
if (event.recoverable) {
console.log('Recoverable error, retrying...');
playback.play();
}
});
```
### Synth Events
```typescript
const synth = cacophony.createOscillator({ frequency: 440, type: 'sine' });
synth.on('frequencyChange', (freq) => {
console.log('Frequency changed to:', freq, 'Hz');
});
synth.on('typeChange', (type) => {
console.log('Waveform changed to:', type);
});
synth.frequency = 880; // Triggers frequencyChange event
synth.type = 'square'; // Triggers typeChange event
```
### Complete Event Reference
#### Cacophony Events
| Event | Payload | Description |
|-------|---------|-------------|
| `loadingStart` | `LoadingStartEvent` | Fired when audio loading begins. Contains `url` and `timestamp`. |
| `loadingProgress` | `LoadingProgressEvent` | Fired during download. Contains `url`, `loaded`, `total`, `progress` (0-1), `timestamp`. |
| `loadingComplete` | `LoadingCompleteEvent` | Fired when loading succeeds. Contains `url`, `duration`, `size`, `timestamp`. |
| `loadingError` | `LoadingErrorEvent` | Fired when loading fails. Contains `url`, `error`, `errorType`, `timestamp`. |
| `cacheHit` | `CacheHitEvent` | Fired on cache hit. Contains `url`, `cacheType` ('memory'\|'browser'\|'conditional'), `timestamp`. |
| `cacheMiss` | `CacheMissEvent` | Fired on cache miss. Contains `url`, `reason` ('not-found'\|'expired'\|'invalid'), `timestamp`. |
| `cacheError` | `CacheErrorEvent` | Fired on cache operation error. Contains `url`, `error`, `operation`, `timestamp`. |
| `globalPlay` | `GlobalPlaybackEvent` | Fired when any Sound or Synth starts playing. Contains `source`, `timestamp`. |
| `globalStop` | `GlobalPlaybackEvent` | Fired when any Sound or Synth stops. Contains `source`, `timestamp`. |
| `globalPause` | `GlobalPlaybackEvent` | Fired when any Sound or Synth pauses. Contains `source`, `timestamp`. |
| `volumeChange` | `number` | Fired when global volume changes. Receives new volume value. |
| `mute` | `void` | Fired when audio is muted globally. |
| `unmute` | `void` | Fired when audio is unmuted globally. |
| `suspend` | `void` | Fired when audio context is suspended. |
| `resume` | `void` | Fired when audio context is resumed. |
#### Sound Events
| Event | Payload | Description |
|-------|---------|-------------|
| `play` | `Playback` | Fired when sound starts playing. Receives the Playback instance. |
| `stop` | `void` | Fired when sound stops. |
| `pause` | `void` | Fired when sound pauses. |
| `resume` | `void` | Fired when sound resumes after being paused. |
| `ended` | `void` | Fired when sound playback ends naturally. |
| `loopEnd` | `void` | Fired when a loop iteration completes. |
| `volumeChange` | `number` | Fired when volume changes. Receives new volume value. |
| `rateChange` | `number` | Fired when playback rate changes. Receives new rate value. |
| `soundError` | `SoundErrorEvent` | Fired on playback errors. Contains `url`, `error`, `errorType`, `timestamp`, `recoverable`. |
#### Playback Events
| Event | Payload | Description |
|-------|---------|-------------|
| `play` | `BasePlayback` | Fired when playback starts. Receives the Playback instance. |
| `stop` | `void` | Fired when playback stops. |
| `pause` | `void` | Fired when playback pauses. |
| `resume` | `void` | Fired when playback resumes after being paused. |
| `ended` | `void` | Fired when playback ends naturally. |
| `seek` | `number` | Fired when playback position changes. Receives new time in seconds. |
| `volumeChange` | `number` | Fired when playback volume changes. Receives new volume value. |
| `error` | `PlaybackErrorEvent` | Fired on playback errors. Contains `error`, `errorType`, `timestamp`, `recoverable`. |
#### Synth Events
| Event | Payload | Description |
|-------|---------|-------------|
| `play` | `SynthPlayback` | Fired when synth starts playing. Receives the SynthPlayback instance. |
| `stop` | `void` | Fired when synth stops. |
| `pause` | `void` | Fired when synth pauses. |
| `resume` | `void` | Fired when synth resumes after being paused. |
| `ended` | `void` | Fired when synth playback ends naturally. |
| `frequencyChange` | `number` | Fired when frequency changes. Receives new frequency in Hz. |
| `typeChange` | `OscillatorType` | Fired when waveform type changes. Receives new type ('sine'\|'square'\|'sawtooth'\|'triangle'). |
| `detuneChange` | `number` | Fired when detune changes. Receives new detune value in cents. |
| `volumeChange` | `number` | Fired when volume changes. Receives new volume value. |
| `error` | `PlaybackErrorEvent` | Fired on playback errors. Contains `error`, `errorType`, `timestamp`, `recoverable`. |
## Caching
Cacophony implements intelligent three-layer caching for optimal performance:
**Memory Cache (LRU)** → **Browser Cache API** → **Network**
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).
```typescript
const cacophony = new Cacophony();
// First load - fetches from network, stores in cache
const sound1 = await cacophony.createSound('audio.mp3');
// Second load - instant from memory cache
const sound2 = await cacophony.createSound('audio.mp3');
// Monitor cache performance via events
cacophony.on('cacheHit', (event) => {
console.log(`${event.cacheType} cache hit: ${event.url}`);
// cacheType: 'memory' | 'browser' | 'conditional'
});
cacophony.on('cacheMiss', (event) => {
console.log(`Cache miss: ${event.url} - ${event.reason}`);
// reason: 'not-found' | 'expired' | 'invalid'
});
// Clear memory cache (browser cache persists)
cacophony.clearMemoryCache();
// Optional: configure TTL for when no validation tokens exist
import { AudioCache } from 'cacophony';
AudioCache.setCacheExpirationTime(60 * 60 * 1000); // 1 hour
```
The caching system requires no configuration in most cases. It automatically optimizes for performance while respecting HTTP standards.
## Cancellation with AbortSignal
Cancel audio loading operations:
```typescript
const controller = new AbortController();
const soundPromise = cacophony.createSound(
'large-file.mp3',
SoundType.Buffer,
'HRTF',
controller.signal
);
// Cancel loading
router.on('navigate', () => controller.abort());
try {
const sound = await soundPromise;
sound.play();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Loading was cancelled');
}
}
// Works with groups too
const group = await cacophony.createGroupFromUrls(
['a.mp3', 'b.mp3', 'c.mp3'],
SoundType.Buffer,
'HRTF',
controller.signal
);
```
## Resource Management
Call `cleanup()` when done with sounds to free resources:
```typescript
const sound = await cacophony.createSound('temp.mp3');
sound.play();
sound.cleanup(); // Tears down playbacks, including pausing and resetting active HTML/streaming media
// Clear memory cache
cacophony.clearMemoryCache();
```
Cacophony uses `FinalizationRegistry` for automatic cleanup when objects are garbage collected, but explicit cleanup is recommended for large applications.
## Audio Context Control
Suspend and resume the entire audio context to pause all audio processing. Useful for mobile apps when entering background mode or for performance optimization:
```typescript
const cacophony = new Cacophony();
// Suspend audio context (pauses ALL audio, saves battery)
cacophony.pause();
// Resume audio context
cacophony.resume();
// Example: pause when app goes to background
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
cacophony.pause();
} else {
cacophony.resume();
}
});
```
Note: This is different from `sound.pause()` which pauses individual sounds. `cacophony.pause()` suspends the entire audio engine.
## API Documentation
For complete API documentation including all methods, parameters, and options, see the [TypeDoc documentation](https://cacophony.js.org).
## License
Cacophony is open-source software licensed under the [MIT License](LICENSE.txt).