How It Works
Understanding the technical approach behind Realtime BPM Analyzer helps you make better decisions when implementing it in your projects.
Overview
The library uses the Web Audio API to analyze audio signals and detect rhythmic patterns. The core algorithm identifies "peaks" in the audio signal that correspond to beats, then analyzes the intervals between these peaks to determine tempo.
Architecture
Audio Processing Pipeline
Audio Source
↓
Media Element Source / Stream / Buffer
↓
Low-Pass Filter (optional, recommended)
↓
AudioWorklet Processor
↓
Peak Detection & Analysis
↓
BPM CandidatesKey Components
- AudioWorklet: Runs analysis on a separate thread for better performance
- Peak Detection: Identifies rhythmic patterns in the audio signal
- Threshold Analysis: Tests multiple sensitivity levels to find optimal peaks
- Interval Grouping: Groups similar intervals to identify tempo patterns
- Confidence Scoring: Ranks BPM candidates by reliability
The Algorithm
Step 1: Audio Capture
The library receives audio data in real-time through the Web Audio API:
// Data arrives in chunks via AudioWorkletProcessor
process(inputs: Float32Array[][], outputs: Float32Array[][]) {
const input = inputs[0][0]; // First channel
// Process audio samples...
}Step 2: Peak Detection
The algorithm scans the audio signal looking for peaks - points where the signal strength exceeds a threshold:
function findPeaksAtThreshold(data, threshold) {
const peaks = [];
const skipForwardIndexes = computeIndexesToSkip(0.25, sampleRate);
for (let i = 0; i < data.length; i++) {
if (data[i] > threshold) {
peaks.push(i);
i += skipForwardIndexes; // Skip ~0.25s to ignore descending phase
}
}
return peaks;
}Why skip forward? When a beat occurs, the signal rises then falls. Skipping ~0.25 seconds prevents counting the same beat multiple times.
Step 3: Multiple Thresholds
The algorithm tests multiple threshold levels (from high to low sensitivity) to find the optimal detection level:
// Try thresholds from 0.9 down to 0.3
for (let threshold = 0.9; threshold >= 0.3; threshold -= 0.05) {
const peaks = findPeaksAtThreshold(data, threshold);
if (peaks.length >= minPeaks) {
// Found enough peaks, use this threshold
break;
}
}This ensures the algorithm works with:
- Loud, heavily compressed tracks (high threshold)
- Quiet, dynamic recordings (low threshold)
- Everything in between
Step 4: Interval Calculation
Once peaks are identified, the algorithm calculates the time intervals between consecutive peaks:
function computeIntervals(peaks, sampleRate) {
const intervals = [];
for (let i = 0; i < peaks.length - 1; i++) {
const interval = (peaks[i + 1] - peaks[i]) / sampleRate * 1000; // in ms
intervals.push(interval);
}
return intervals;
}Step 5: Tempo Grouping
Intervals are grouped by tempo, allowing slight variations (within ±5%):
function groupByTempo(intervals) {
const groups = [];
for (const interval of intervals) {
const tempo = Math.round(60000 / interval); // Convert to BPM
// Find existing group or create new one
let group = groups.find(g =>
Math.abs(g.tempo - tempo) / tempo < 0.05 // 5% tolerance
);
if (!group) {
group = { tempo, count: 0, intervals: [] };
groups.push(group);
}
group.count++;
group.intervals.push(interval);
}
return groups;
}Step 6: Candidate Ranking
BPM candidates are sorted by:
- Count (how many intervals match this tempo)
- Consistency (how stable the intervals are)
function getBPMCandidates(groups) {
return groups
.sort((a, b) => b.count - a.count) // Most common first
.map(group => ({
tempo: Math.round(group.tempo),
count: group.count,
confidence: calculateConfidence(group)
}));
}Signal Processing Features
Low-Pass Filtering
The optional getBiquadFilter() function creates a low-pass filter that:
- Removes high frequencies (treble)
- Emphasizes bass frequencies (where beats typically occur)
- Improves detection accuracy
Frequency Response:
- Frequency: 150 Hz cutoff
- Q Factor: 1 (mild slope)
- Type: Lowpass
This helps isolate kick drums and bass elements that carry the rhythm.
Why It Works
Most music has a strong rhythmic element in the bass range (60-150 Hz):
- Kick drums: ~60-100 Hz
- Bass guitar: ~40-200 Hz
- Electronic bass: ~50-150 Hz
Filtering out higher frequencies reduces false positives from:
- Hi-hats and cymbals
- Vocals
- Melodic instruments
Event System
bpm Event
Emitted continuously during analysis:
analyzer.on('bpm', (data) => {
// data.bpm: Array of tempo candidates
// data.threshold: Detection threshold used
console.log('Current BPM:', data.bpm[0].tempo);
console.log('Count:', data.bpm[0].count);
});Characteristics:
- Updates frequently (every ~1-2 seconds)
- May fluctuate as more data arrives
- Useful for progressive display
bpmStable Event
Emitted when confident detection is achieved:
analyzer.on('bpmStable', (data) => {
// More reliable, higher confidence result
console.log('Stable BPM:', data.bpm[0].tempo);
console.log('Confidence count:', data.bpm[0].count);
});Characteristics:
- More reliable result
- Requires sufficient data collection
- Threshold increases over time for better accuracy
- Best for final BPM determination
Performance Optimizations
AudioWorklet Threading
AudioWorklet runs on a separate audio rendering thread:
- Doesn't block UI: Analysis runs independently
- Real-time priority: Gets CPU time when needed
- Low latency: Processes audio as it arrives
Memory Management
For continuous analysis (streams):
createRealtimeBpmAnalyzer(audioContext, {
continuousAnalysis: true,
stabilizationTime: 20000 // Clear data every 20 seconds
});This prevents memory leaks in long-running analysis.
Data Buffering
The processor accumulates audio data before analysis:
- Minimum requirement: ~10 seconds at 90 BPM
- Better accuracy: 20+ seconds of data
- Trade-off: Speed vs accuracy
Limitations & Edge Cases
Complex Time Signatures
The algorithm works best with:
- ✅ 4/4 time (most popular music)
- ✅ Simple rhythms
- ⚠️ May struggle with 5/4, 7/8, or polyrhythmic music
Tempo Changes
- Fixed tempo: Excellent accuracy
- Slight variations: Handles well (±5% tolerance)
- Major changes: May report multiple candidates or incorrect BPM
Audio Quality
- Clear recordings: Best results
- Compressed/loud: Works well
- Very quiet: May need lower threshold
- Heavy noise: Use low-pass filter
Further Reading
Next Steps
- Performance Tips - Optimize for your use case
- Browser Compatibility - Platform support details
- Examples - Practical implementations