Real-Time Beat Detection in Web-Based DJ Applications
In modern DJ software, the ability to detect beats and BPM (Beats Per Minute) in real time is critical—for syncing tracks, visualizations, and live effects. In this article, we’ll explore how to implement a beat detection pipeline in a web context using:
- Web Audio API for client-side audio analysis
- Node.js + WebSockets for real-time event streaming
- PostgreSQL for persisting beat data
- Angular for visualizing beat events
By the end, you’ll have a blueprint for analyzing live audio and broadcasting beat events to front-end clients.
1. Beat Detection Algorithms
There are multiple approaches to detecting beats in audio. Here’s a quick comparison:
Algorithm | Accuracy | Time Complexity | Best For |
---|---|---|---|
Autocorrelation | Medium (±2 BPM) | O(n²) | Simple, offline analysis |
Spectral Flux | High (±0.5 BPM) | O(n log n) | Real-time, per-frame analysis |
Onset Detection | Very High (±0.1 BPM) | O(n) | Live DJ applications |
Spectral Flux—which measures changes in the frequency spectrum over time—is a sweet spot for real-time accuracy and performance.
2. Client-Side: Web Audio API
We’ll use the Web Audio API to capture audio (e.g., from a local file or microphone), compute a spectrum, and run spectral-flux algorithm.
// Initialize AudioContext and AnalyserNode
const audioCtx = new AudioContext();
const analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const freqData = new Float32Array(bufferLength);
// Load audio source (e.g., <audio> element)
const audioElement = document.querySelector('audio');
const sourceNode = audioCtx.createMediaElementSource(audioElement);
sourceNode.connect(analyser);
analyser.connect(audioCtx.destination);
// Spectral Flux variables
let lastSpectrum = new Float32Array(bufferLength);
function detectBeats() {
analyser.getFloatFrequencyData(freqData);
// Compute spectral flux
let flux = 0;
for (let i = 0; i < bufferLength; i++) {
const value = Math.max(0, freqData[i] - lastSpectrum[i]);
flux += value;
lastSpectrum[i] = freqData[i];
}
// Threshold to determine beat
if (flux > FLUX_THRESHOLD) {
const timestamp = audioCtx.currentTime;
onBeatDetected(timestamp);
}
requestAnimationFrame(detectBeats);
}
audioElement.onplay = () => {
audioCtx.resume();
detectBeats();
};
Here, whenever flux
exceeds a chosen threshold, we consider it a beat.
3. Streaming Beats with Node.js & WebSockets
To broadcast beats to multiple clients (e.g., visualizers, remote controllers), we’ll pipe beat events over a WebSocket.
// server.js (Node.js + ws)
const http = require('http');
const WebSocket = require('ws');
const server = http.createServer();
const wss = new WebSocket.Server({ server });
wss.on('connection', ws => {
console.log('Client connected');
ws.on('message', msg => {
// Handle incoming messages if needed
});
});
// Expose an endpoint for clients to post beats
const express = require('express');
const app = express();
app.use(express.json());
app.post('/beat', (req, res) => {
const { timestamp } = req.body;
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ event: 'beat', timestamp }));
}
});
res.sendStatus(200);
});
server.on('request', app);
server.listen(3000, () => console.log('Listening on port 3000'));
On the client side, modify onBeatDetected
to POST the beat and/or send directly via WebSocket:
function onBeatDetected(timestamp) {
// Send via WebSocket
socket.send(JSON.stringify({ event: 'beat', timestamp }));
// Optionally persist
fetch('/beat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ timestamp }),
});
}
4. Persisting Beats in PostgreSQL
To analyze live sets later (e.g., generating BPM curves), store beats in a simple table:
Table | Columns | Description |
---|---|---|
beats | id (PK) | Auto-increment |
track_id (FK) | Associated track | |
timestamp (float8) | AudioContext time (seconds) | |
received_at (timestamp) | Server insertion time |
CREATE TABLE beats (
id SERIAL PRIMARY KEY,
track_id INT REFERENCES tracks(id),
timestamp DOUBLE PRECISION NOT NULL,
received_at TIMESTAMPTZ DEFAULT NOW()
);
Insert from Node.js:
// Using node-postgres
const { Pool } = require('pg');
const pool = new Pool();
async function saveBeat(trackId, timestamp) {
await pool.query(
'INSERT INTO beats(track_id, timestamp) VALUES($1, $2)',
[trackId, timestamp]
);
}
5. Visualizing Beats in Angular
Leverage Angular’s change detection and RxJS to render beat pulses:
// beat.service.ts
import { Injectable } from '@angular/core';
import { webSocket } from 'rxjs/webSocket';
@Injectable({ providedIn: 'root' })
export class BeatService {
private socket$ = webSocket('ws://localhost:3000');
beat$ = this.socket$.multiplex(
() => ({ subscribe: 'beat' }),
() => ({ unsubscribe: 'beat' }),
msg => msg.event === 'beat'
);
}
<!-- beat-visualizer.component.html -->
<div class="pulse" [class.active]="active"></div>
// beat-visualizer.component.ts
import { Component, OnInit } from '@angular/core';
import { BeatService } from './beat.service';
import { timer } from 'rxjs';
@Component({
selector: 'app-beat-visualizer',
templateUrl: './beat-visualizer.component.html',
styleUrls: ['./beat-visualizer.component.scss']
})
export class BeatVisualizerComponent implements OnInit {
active = false;
constructor(private beatService: BeatService) {}
ngOnInit() {
this.beatService.beat$.subscribe(() => this.flash());
}
flash() {
this.active = true;
timer(100).subscribe(() => (this.active = false));
}
}
Add some CSS:
.pulse {
width: 100px;
height: 100px;
border-radius: 50%;
background: lightgray;
transition: box-shadow 0.1s ease-in-out;
}
.pulse.active {
box-shadow: 0 0 20px 5px cyan;
}
Conclusion
By combining the Web Audio API for live spectral analysis, Node.js + WebSockets for streaming beat events, and a PostgreSQL backend for storage, you can build robust, real-time DJ applications. Integrating with Angular on the front end—complete with visualizers and controls—yields a fully-featured platform for DJs and audiences alike.
Feel free to adapt this pipeline for syncing tracks across devices, generating BPM-based lighting cues, or building collaborative online DJ experiences. Happy coding!
Top comments (0)