Core Concepts
Understanding these core concepts will help you use Realtime BPM Analyzer effectively.
Usage Modes
The library supports different usage patterns depending on your needs:
Real-Time Analysis (Default)
Analyze audio while it's playing:
- Accumulates data throughout playback
- Provides increasingly accurate results
- No automatic reset
- Best for: Music players, video players, defined-length audio
const bpmAnalyzer = await createRealtimeBpmAnalyzer(audioContext);
// Analyzes continuously, accumulates confidenceContinuous Analysis (Auto-Reset)
For long-running or infinite streams:
- Automatically resets after stabilization period
- Prevents memory buildup in 24/7 scenarios
- Best for: Radio streams, live broadcasts, streaming services
const bpmAnalyzer = await createRealtimeBpmAnalyzer(audioContext, {
continuousAnalysis: true,
stabilizationTime: 20000 // Reset every 20 seconds
});Offline/Buffer Analysis
Analyze complete audio files without real-time constraints:
- Processes entire buffer at once
- Fastest results
- Best for: File uploads, music libraries, batch processing
import { analyzeFullBuffer } from 'realtime-bpm-analyzer';
const buffer = await audioContext.decodeAudioData(arrayBuffer);
const tempos = await analyzeFullBuffer(buffer);
console.log('BPM:', tempos[0].tempo);Audio Graph Architecture
The library uses the Web Audio API's node-based architecture:
┌─────────────┐
│ Audio Source│ (audio element, microphone, file)
└──────┬──────┘
│
▼
┌─────────────┐
│ Low-Pass │ (optional filter - isolates bass)
│ Filter │
└──────┬──────┘
│
▼
┌─────────────┐
│ BPM │ (AudioWorkletProcessor)
│ Analyzer │
└──────┬──────┘
│
▼
┌─────────────┐
│ Destination │ (speakers)
└─────────────┘Why the Low-Pass Filter?
The optional getBiquadFilter() creates a low-pass filter that:
- Isolates bass frequencies (typically where the beat is strongest)
- Reduces noise from high-frequency content
- Improves peak detection accuracy
You can skip it if analyzing music with prominent high-frequency beats.
How BPM Detection Works
1. Peak Detection
The analyzer scans the audio signal at multiple amplitude thresholds (0.95, 0.90, 0.85... down to 0.20):
Amplitude
1.0 ┤ ╭╮ threshold: 0.9
0.9 ┤ ╭╯╰╮ ╭─╮ ← Peak detected here
0.8 ┤ ╭╯ ╰──╯ ╰╮
0.7 ┤ ╭╯ ╰╮
└──────────────────→ TimeLower thresholds detect more peaks, higher thresholds detect only strong peaks.
2. Interval Calculation
Once peaks are found, the analyzer calculates time intervals between them:
Peak 1 Peak 2 Peak 3 Peak 4
|─────────|─────────|─────────|
500ms 500ms 500ms3. BPM Conversion
Intervals are converted to BPM:
BPM = 60,000 / interval_in_millisecondsFor the example above:
60,000 / 500 = 120 BPM4. Tempo Grouping
The algorithm then:
- Normalizes BPM to common ranges (90-180 BPM)
- Groups similar tempos together
- Counts occurrences (confidence score)
- Returns top candidates sorted by confidence
BPM Candidates
Results are returned as an array of candidates:
[
{ tempo: 128, count: 45 },
{ tempo: 64, count: 23 },
{ tempo: 96, count: 12 }
]- tempo: Detected BPM value
- count: Number of matching intervals (higher = more confident)
The first candidate (highest count) is usually the most accurate.
Threshold System
The library uses a progressive threshold system:
- Starts at high threshold (0.95) - only strong peaks
- If enough peaks found, calculates BPM
- If successful, raises minimum threshold
- This makes future detection more selective and accurate
Benefits:
- Early results from strong beats
- Increasing accuracy over time
- Filters out weak/noise peaks
Configuration Options
When creating the processor, you can customize behavior:
const bpmAnalyzer = await createRealtimeBpmAnalyzer(audioContext, {
// Enable continuous analysis with auto-reset
continuousAnalysis: false,
// Time before auto-reset (only if continuousAnalysis = true)
stabilizationTime: 20000, // 20 seconds
// Minimum time between peaks at same threshold (prevents double-detection)
muteTimeInIndexes: 10000,
// Enable debug events
debug: false
});continuousAnalysis
false(default): Accumulate data indefinitelytrue: Reset afterstabilizationTimeto prevent memory growth
stabilizationTime
- Default:
20000ms (20 seconds) - Only used when
continuousAnalysis: true - Time until analyzer resets and starts fresh
muteTimeInIndexes
- Default:
10000samples - Prevents detecting the same beat twice
- Adjust based on expected BPM range
debug
false(default): No debug eventstrue: Emits additional events for debugging
Events Reference
bpm
Emitted continuously during analysis:
analyzer.on('bpm', (data) => {
// data.bpm: Array of tempo candidates
// data.threshold: Current detection threshold
console.log('BPM:', data.bpm[0].tempo);
console.log('Threshold:', data.threshold);
});bpmStable
Emitted when stable BPM detected:
analyzer.on('bpmStable', (data) => {
// More confident detection
console.log('Stable BPM:', data.bpm[0].tempo);
console.log('Confidence:', data.bpm[0].count);
console.log('Threshold:', data.threshold);
});analyzerReset
Emitted when continuous analysis resets:
analyzer.on('analyzerReset', () => {
console.log('Analyzer reset - starting fresh analysis');
});Best Practices
✅ Do
- Use low-pass filter for music with prominent bass
- Wait for
bpmStableevent before displaying final result - Handle multiple candidates (show top 3-5)
- Create
AudioContexton user interaction
❌ Don't
- Create
AudioContextimmediately on page load (autoplay restrictions) - Assume first BPM event is accurate (wait for stability)
- Ignore the
countproperty (it indicates confidence) - Use continuous analysis for short audio clips
Next Steps
Now that you understand the core concepts: