@uln/impulse
v0.1.1
Published
Real-time audio feature extraction for the browser using WebAssembly. Extract BPM, beats, frequency bands, and spectral features from audio streams.
Downloads
71
Maintainers
Readme
@impulse/web
Real-time audio feature extraction for the browser using WebAssembly.
Extract BPM, beats, frequency bands, spectral features, and detect song transitions from audio streams with sub-millisecond latency.
Features
- BPM Detection - Autocorrelation-based tempo tracking (configurable range, default 60-200 BPM)
- Beat Tracking - Phase-accurate beat detection with confidence scoring
- Onset Detection - Classify transients as kicks, snares, hi-hats, or generic onsets
- Frequency Bands - 8-band energy analysis (sub-bass to air)
- Spectral Features - Centroid, rolloff, flux, zero-crossing rate
- Transition Detection - Detect song changes during DJ sets
- Zero-Copy - Efficient Float32Array handling via WebAssembly
Installation
npm install @impulse/web
# or
pnpm add @impulse/web
# or
yarn add @impulse/webQuick Start
import init, { ImpulseProcessor, WasmConfig } from '@impulse/web';
async function main() {
// Initialize the WASM module (call once at startup)
await init();
// Create processor with club-optimized settings
const config = WasmConfig.forClub();
const processor = ImpulseProcessor.newWithConfig(48000, config);
// Connect to audio input
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioContext = new AudioContext({ sampleRate: 48000 });
const source = audioContext.createMediaStreamSource(stream);
// Use AudioWorklet for real-time processing (recommended)
// Or use ScriptProcessorNode for simpler setup:
const scriptNode = audioContext.createScriptProcessor(2048, 2, 2);
scriptNode.onaudioprocess = (event) => {
const left = event.inputBuffer.getChannelData(0);
const right = event.inputBuffer.getChannelData(1);
// Process stereo audio and get features
const output = processor.processStereo(left, right, performance.now());
// Use extracted features
console.log('BPM:', output.beat.bpm);
console.log('RMS:', output.features.rms);
console.log('Bass energy:', output.features.bands.values[1]);
};
source.connect(scriptNode);
scriptNode.connect(audioContext.destination);
}
main();API Reference
Initialization
init(): Promise<void>
Initialize the WASM module. Must be called once before using any other functions.
import init from '@impulse/web';
await init();Configuration
WasmConfig
Configuration class for the audio processor.
// Create default config
const config = new WasmConfig();
// Create club-optimized config (70-180 BPM, transitions enabled)
const clubConfig = WasmConfig.forClub();
// Create low-latency config (smaller FFT for faster response)
const lowLatencyConfig = WasmConfig.lowLatency();
// Customize settings
config.setFftSize(2048); // FFT size (power of 2: 1024, 2048, 4096)
config.setHopSize(512); // Hop size (samples between FFT windows)
config.setBpmRange(70, 180); // BPM detection range
config.setTransitionDetection(true); // Enable song transition detection| Preset | FFT Size | Hop Size | BPM Range | Transitions |
|--------|----------|----------|-----------|-------------|
| new() (default) | 2048 | 512 | 60-200 | disabled |
| forClub() | 2048 | 512 | 70-180 | enabled |
| lowLatency() | 1024 | 256 | 60-200 | disabled |
Processor
ImpulseProcessor
Main audio feature extraction processor.
// Create with default config
const processor = new ImpulseProcessor(sampleRate);
// Create with custom config
const processor = ImpulseProcessor.newWithConfig(sampleRate, config);Methods
processMono(samples: Float32Array, timestampMs: number): ControlOutput
Process mono audio samples.
const output = processor.processMono(monoSamples, performance.now());processStereo(left: Float32Array, right: Float32Array, timestampMs: number): ControlOutput
Process stereo audio by averaging channels to mono.
const output = processor.processStereo(leftChannel, rightChannel, performance.now());processInterleaved(interleaved: Float32Array, timestampMs: number): ControlOutput
Process interleaved stereo audio ([L0, R0, L1, R1, ...]).
const output = processor.processInterleaved(interleavedStereo, performance.now());getBpm(): number
Get current BPM estimate.
getBpmConfidence(): number
Get BPM confidence (0.0-1.0).
isBpmReady(): boolean
Check if BPM tracking has enough data for reliable estimates.
getFftSize(): number
Get the configured FFT size.
getSampleRate(): number
Get the configured sample rate.
reset(): void
Reset all processor state. Call this on track changes, seeks, or discontinuities.
Output Types
ControlOutput
Main output from processing functions.
interface ControlOutput {
timestamp: number; // Timestamp in nanoseconds
sequence: number; // Monotonic sequence number
features: AudioFeatures; // Extracted audio features
beat: BeatInfo; // Beat/BPM tracking state
onsets: OnsetEvent[]; // Detected onsets this frame
transition?: TransitionInfo; // Song transition state (if enabled)
}AudioFeatures
Core audio measurements.
interface AudioFeatures {
rms: number; // Root mean square energy (0.0-0.7 typical)
peak: number; // Peak amplitude (0.0-1.0+)
bands: BandEnergies; // 8-band frequency energy
spectral_centroid: number; // Brightness in Hz
spectral_rolloff: number; // 85% energy cutoff in Hz
spectral_flux: number; // Frame-to-frame spectral change
zero_crossing_rate: number; // Noisiness indicator (0.0-1.0)
}BandEnergies
Frequency band energy distribution.
interface BandEnergies {
values: number[]; // 8 bands: sub-bass, bass, low-mid, mid, upper-mid, presence, brilliance, air
}
// Band indices:
// 0: Sub-bass (20-60 Hz)
// 1: Bass (60-250 Hz)
// 2: Low-mid (250-500 Hz)
// 3: Mid (500-2000 Hz)
// 4: Upper-mid (2000-4000 Hz)
// 5: Presence (4000-6000 Hz)
// 6: Brilliance (6000-10000 Hz)
// 7: Air (10000-20000 Hz)BeatInfo
Beat tracking state.
interface BeatInfo {
bpm: number; // Current BPM estimate
confidence: number; // Confidence in estimate (0.0-1.0)
phase: number; // Position within beat (0.0-1.0, 0.0 = on beat)
last_beat: number; // Timestamp of last beat (nanoseconds)
beat_count: number; // Total beats detected
}OnsetEvent
Detected transient/onset.
interface OnsetEvent {
timestamp: number; // When the onset occurred (nanoseconds)
strength: number; // Onset strength (0.0-1.0)
onset_type: OnsetType; // Classification
}
type OnsetType = 'Generic' | 'Kick' | 'Snare' | 'HiHat';TransitionInfo
Song transition detection state.
interface TransitionInfo {
in_transition: boolean; // Whether a transition is in progress
transition_duration: number; // Duration in seconds (0 if not transitioning)
readiness: number; // Detector readiness (0.0-1.0)
event?: TransitionEventInfo; // Event if transition started/ended this frame
}Utilities
log(message: string): void
Log a message to the browser console.
getTimestamp(): number
Get current high-resolution timestamp in milliseconds (uses performance.now()).
Usage with AudioWorklet
For production applications, use AudioWorklet instead of ScriptProcessorNode:
// audio-processor.worklet.ts
class AudioCaptureProcessor extends AudioWorkletProcessor {
process(inputs: Float32Array[][], outputs: Float32Array[][]): boolean {
const input = inputs[0];
if (input.length >= 2) {
this.port.postMessage({
left: input[0].slice(),
right: input[1].slice(),
timestamp: currentTime * 1000 // Convert to ms
});
}
return true;
}
}
registerProcessor('audio-capture', AudioCaptureProcessor);
// main.ts
await audioContext.audioWorklet.addModule('audio-processor.worklet.js');
const workletNode = new AudioWorkletNode(audioContext, 'audio-capture');
workletNode.port.onmessage = (event) => {
const { left, right, timestamp } = event.data;
const output = processor.processStereo(left, right, timestamp);
// Update visualization...
};Usage with Bundlers
Vite
// vite.config.ts
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
export default {
plugins: [wasm(), topLevelAwait()],
optimizeDeps: {
exclude: ['@impulse/web']
}
};Webpack 5
// webpack.config.js
module.exports = {
experiments: {
asyncWebAssembly: true
}
};Performance
- DSP per-hop: ~15 microseconds (695x realtime)
- Throughput: 525x realtime (release build)
- FFT (2048): ~10 microseconds
Benchmarked on Apple M1. Performance scales with FFT size.
Browser Compatibility
Requires:
- WebAssembly support
- Web Audio API (
getUserMediafor audio input)
Tested in:
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Building from Source
Prerequisites:
- Rust toolchain
- wasm-pack (
cargo install wasm-pack)
# Clone the repository
git clone https://github.com/jakeisnt/beatgrid.git
cd beatgrid
# Build the WASM package
cd packages/impulse-wasm
npm run build
# Or build for development (faster, with debug info)
npm run build:devLicense
MIT
