Documentation Index Fetch the complete documentation index at: https://mintlify.com/raphaelsalaja/userinterface-wiki/llms.txt
Use this file to discover all available pages before exploring further.
Web Audio API Implementation
This guide explores the production Web Audio API implementation used throughout the User Interface Wiki for procedural sound generation. All sound effects are synthesized in real-time using oscillators, noise generators, and audio processing nodes.
Architecture
AudioContext Management
The implementation uses a singleton pattern to manage the AudioContext lifecycle:
let audioContext : AudioContext | null = null ;
function getAudioContext () : AudioContext {
if ( ! audioContext ) {
audioContext = new AudioContext ();
}
if ( audioContext . state === "suspended" ) {
audioContext . resume ();
}
return audioContext ;
}
The AudioContext may be suspended by browser autoplay policies. Always resume the context on user interaction.
Sound Synthesis Patterns
Click Sound: Filtered Noise
Short burst of filtered noise for immediate tactile feedback:
click : () => {
try {
const ctx = getAudioContext ();
const t = ctx . currentTime ;
// Generate white noise buffer
const noise = ctx . createBufferSource ();
const buf = ctx . createBuffer ( 1 , ctx . sampleRate * 0.008 , ctx . sampleRate );
const data = buf . getChannelData ( 0 );
for ( let i = 0 ; i < data . length ; i ++ ) {
data [ i ] = ( Math . random () * 2 - 1 ) * Math . exp ( - i / 50 );
}
noise . buffer = buf ;
// Bandpass filter for tonal character
const filter = ctx . createBiquadFilter ();
filter . type = "bandpass" ;
filter . frequency . value = 4000 + Math . random () * 1000 ;
filter . Q . value = 3 ;
const gain = ctx . createGain ();
gain . gain . value = 0.5 + Math . random () * 0.15 ;
// Connect audio graph
noise . connect ( filter );
filter . connect ( gain );
gain . connect ( ctx . destination );
noise . start ( t );
} catch {}
}
Key Techniques:
Exponential decay envelope: Math.exp(-i / 50)
Random frequency variation: 4000 + Math.random() * 1000
Short duration: 8ms buffer
Pop Sound: Frequency Sweep
Smooth frequency glide for non-destructive feedback:
pop : () => {
const ctx = getAudioContext ();
const t = ctx . currentTime ;
const osc = ctx . createOscillator ();
const gain = ctx . createGain ();
osc . type = "sine" ;
osc . frequency . setValueAtTime ( 400 , t );
osc . frequency . exponentialRampToValueAtTime ( 150 , t + 0.04 );
gain . gain . setValueAtTime ( 0.35 , t );
gain . gain . exponentialRampToValueAtTime ( 0.001 , t + 0.05 );
osc . connect ( gain );
gain . connect ( ctx . destination );
osc . start ( t );
osc . stop ( t + 0.05 );
}
Parameters:
Start frequency: 400 Hz
End frequency: 150 Hz
Duration: 50ms
Envelope: Exponential decay
Success Sound: Arpeggio Sequence
Musical phrase conveying positive outcome:
success : () => {
const ctx = getAudioContext ();
const t = ctx . currentTime ;
const notes = [ 523.25 , 659.25 , 783.99 ]; // C5, E5, G5
const spacing = 0.08 ;
notes . forEach (( freq , i ) => {
const osc = ctx . createOscillator ();
const osc2 = ctx . createOscillator ();
const gain = ctx . createGain ();
const filter = ctx . createBiquadFilter ();
osc . type = "triangle" ;
osc . frequency . value = freq ;
osc2 . type = "sine" ;
osc2 . frequency . value = freq * 2 ; // Octave harmonic
filter . type = "lowpass" ;
filter . frequency . value = 3000 ;
const start = t + i * spacing ;
const duration = 0.15 ;
gain . gain . setValueAtTime ( 0 , start );
gain . gain . linearRampToValueAtTime ( 0.25 , start + 0.01 );
gain . gain . exponentialRampToValueAtTime ( 0.001 , start + duration );
osc . connect ( gain );
osc2 . connect ( gain );
gain . connect ( filter );
filter . connect ( ctx . destination );
osc . start ( start );
osc2 . start ( start );
osc . stop ( start + duration );
osc2 . stop ( start + duration );
});
// Shimmer finish
const shimmer = ctx . createOscillator ();
const shimmerGain = ctx . createGain ();
shimmer . type = "sine" ;
shimmer . frequency . value = 1046.5 ; // C6
shimmerGain . gain . setValueAtTime ( 0 , t + 0.24 );
shimmerGain . gain . linearRampToValueAtTime ( 0.15 , t + 0.26 );
shimmerGain . gain . exponentialRampToValueAtTime ( 0.001 , t + 0.45 );
shimmer . connect ( shimmerGain );
shimmerGain . connect ( ctx . destination );
shimmer . start ( t + 0.24 );
shimmer . stop ( t + 0.45 );
}
Musical Structure:
Notes: C major triad (C5, E5, G5)
Timing: 80ms spacing between notes
Harmonics: Fundamental + octave doubling
Finish: High shimmer at C6
Error Sound: Distorted Dissonance
Harsh tone conveying failure:
error : () => {
const ctx = getAudioContext ();
const t = ctx . currentTime ;
const osc1 = ctx . createOscillator ();
const osc2 = ctx . createOscillator ();
const gain = ctx . createGain ();
const distortion = ctx . createWaveShaper ();
// Waveshaper curve for distortion
const curve = new Float32Array ( 256 );
for ( let i = 0 ; i < 256 ; i ++ ) {
const x = i / 128 - 1 ;
curve [ i ] = Math . tanh ( x * 2 );
}
distortion . curve = curve ;
// Detuned oscillators create beating
osc1 . type = "sawtooth" ;
osc1 . frequency . setValueAtTime ( 180 , t );
osc1 . frequency . exponentialRampToValueAtTime ( 80 , t + 0.25 );
osc2 . type = "square" ;
osc2 . frequency . setValueAtTime ( 190 , t );
osc2 . frequency . exponentialRampToValueAtTime ( 85 , t + 0.25 );
gain . gain . setValueAtTime ( 0 , t );
gain . gain . linearRampToValueAtTime ( 0.3 , t + 0.02 );
gain . gain . setValueAtTime ( 0.3 , t + 0.08 );
gain . gain . linearRampToValueAtTime ( 0.25 , t + 0.1 );
gain . gain . exponentialRampToValueAtTime ( 0.001 , t + 0.3 );
const filter = ctx . createBiquadFilter ();
filter . type = "lowpass" ;
filter . frequency . value = 800 ;
osc1 . connect ( distortion );
osc2 . connect ( distortion );
distortion . connect ( gain );
gain . connect ( filter );
filter . connect ( ctx . destination );
osc1 . start ( t );
osc2 . start ( t );
osc1 . stop ( t + 0.3 );
osc2 . stop ( t + 0.3 );
}
Dissonance Techniques:
Detuned oscillators: 180 Hz vs 190 Hz (beating effect)
WaveShaper distortion: tanh transfer function
Frequency glide downward
Lowpass filter at 800 Hz
Audio Graph Patterns
Node Connection Best Practices
Tab Title
Tab Title
Tab Title
// Simple oscillator → gain → destination
const osc = ctx . createOscillator ();
const gain = ctx . createGain ();
osc . connect ( gain );
gain . connect ( ctx . destination );
// Oscillator → filter → gain → destination
const osc = ctx . createOscillator ();
const filter = ctx . createBiquadFilter ();
const gain = ctx . createGain ();
osc . connect ( filter );
filter . connect ( gain );
gain . connect ( ctx . destination );
// Multiple sources → shared processing
const osc1 = ctx . createOscillator ();
const osc2 = ctx . createOscillator ();
const gain = ctx . createGain ();
osc1 . connect ( gain );
osc2 . connect ( gain );
gain . connect ( ctx . destination );
Envelope Shaping
Linear vs Exponential Ramps:
// Linear: Constant rate of change
gain . gain . linearRampToValueAtTime ( 0.5 , t + 0.1 );
// Exponential: Natural-sounding decay
gain . gain . exponentialRampToValueAtTime ( 0.001 , t + 0.1 );
Exponential ramps cannot reach zero. Use 0.001 as the target value instead.
Attack-Decay-Sustain-Release (ADSR):
const t = ctx . currentTime ;
const attack = 0.01 ;
const decay = 0.05 ;
const sustain = 0.7 ;
const release = 0.1 ;
gain . gain . setValueAtTime ( 0 , t );
gain . gain . linearRampToValueAtTime ( 1 , t + attack );
gain . gain . exponentialRampToValueAtTime ( sustain , t + attack + decay );
gain . gain . setValueAtTime ( sustain , t + duration - release );
gain . gain . exponentialRampToValueAtTime ( 0.001 , t + duration );
Performance Considerations
Memory Management
// ✅ Good: Reuse AudioContext
const ctx = getAudioContext ();
// ❌ Bad: Create new context per sound
const ctx = new AudioContext ();
Error Handling
All sound functions wrap Web Audio code in try-catch blocks:
sounds = {
click : () => {
try {
// Web Audio code
} catch {}
}
}
Rationale:
Browsers may not support Web Audio
AudioContext may fail to resume
Sounds should never break functionality
Buffer Generation
// Efficient: Generate once, reuse buffer
const buf = ctx . createBuffer ( 1 , ctx . sampleRate * 0.008 , ctx . sampleRate );
const data = buf . getChannelData ( 0 );
for ( let i = 0 ; i < data . length ; i ++ ) {
data [ i ] = ( Math . random () * 2 - 1 ) * Math . exp ( - i / 50 );
}
noise . buffer = buf ;
Integration with React
Button Component
components/button/index.tsx
import { sounds } from "@/lib/sounds" ;
function Button ({ sound = true , onClick , ... props } : ButtonProps ) {
const handleClick : typeof onClick = ( event ) => {
if ( sound ) {
sounds . click ();
}
onClick ?.( event );
};
return < BaseButton onClick ={ handleClick } { ... props } />;
}
Context-Specific Sounds
// Success action
onSubmit = {() => {
sounds . success ();
// ... success logic
}}
// Error handling
onError = {() => {
sounds . error ();
// ... error logic
}}
// State toggle
onToggle = {() => {
sounds . toggle ();
// ... toggle logic
}}
Browser Compatibility
Safari requires user gesture to unlock AudioContext. The implementation automatically resumes suspended contexts on first interaction.
All major browsers support Web Audio API:
Chrome/Edge: Full support
Firefox: Full support
Safari: Full support (requires gesture unlock)
Mobile browsers: Full support
Advanced Techniques
Frequency Randomization
Add subtle variation to prevent repetition fatigue:
filter . frequency . value = 4000 + Math . random () * 1000 ;
Gain Randomization
Simulate natural variation in interaction force:
gain . gain . value = 0.5 + Math . random () * 0.15 ;
Noise Envelopes
// Sharp attack, fast decay
data [ i ] = ( Math . random () * 2 - 1 ) * Math . exp ( - i / 50 );
// Smooth bell curve
const env = Math . sin (( i / data . length ) * Math . PI );
data [ i ] = ( Math . random () * 2 - 1 ) * env ;
Reference
Filter Types
Type Use Case lowpassDull harsh sounds, isolate bass highpassRemove rumble, emphasize brightness bandpassIsolate mid-range, telephone effect notchRemove specific frequency peakingBoost/cut specific frequency
Oscillator Types
Type Character sinePure, smooth, gentle triangleWarm, hollow squareBright, harsh, retro sawtoothBuzzy, aggressive
Timing Guidelines
Sound Type Duration Use Case Click/Tick 4-15ms Immediate feedback Pop/Confirm 40-60ms State change confirmation Success 300-500ms Task completion Error 250-300ms Attention without alarm Warning 200-300ms Alert with repeat
Next Steps
Motion Implementation Explore advanced animation patterns with Motion
Performance Optimization Learn bundle size and runtime optimization techniques