Skip to content

Quickstart

Pleco-Xa’s analysis functions take raw samples and a sample rate — arrays in, arrays out — so the same calls work in the browser, Node, and Web Workers.

You don’t need an audio file to see a result. This synthesizes a 120 BPM click track in memory and tracks its tempo — it runs as-is in Node, the browser, or a Web Worker:

import { beat_track } from 'pleco-xa'
// A synthetic 120 BPM click track — no files, no AudioContext.
const sr = 22050
const y = new Float32Array(sr * 4) // 4 seconds
const period = Math.round((sr * 60) / 120) // one click every 0.5 s
for (let i = 0; i < y.length; i += period) {
for (let k = 0; k < 200 && i + k < y.length; k++) {
y[i + k] = Math.exp(-k / 40) * Math.sin((2 * Math.PI * 1000 * k) / sr)
}
}
const { tempo, beats } = beat_track(y, sr)
console.log(`${tempo.toFixed(1)} BPM, ${beats.length} beats`)
// "117.5 BPM, 7 beats" — 117.5, not 120: autocorrelation lag quantization at hop 512; the beat grid is exact

Decode a file into an AudioBuffer with the Web Audio API, then hand its channel data to the library.

import { beat_track, loop } from 'pleco-xa'
const ctx = new AudioContext()
const audioBuffer = await ctx.decodeAudioData(await (await fetch('song.mp3')).arrayBuffer())
const y = audioBuffer.getChannelData(0)
const sr = audioBuffer.sampleRate
// Tempo + beats — the Ellis dynamic-programming beat tracker
const { tempo, beats } = beat_track(y, sr)
console.log(`${tempo.toFixed(1)} BPM, ${beats.length} beats`)
// The signature feature: find the best loop point (async)
const result = await loop.detect(audioBuffer, { strategy: 'fast' })
console.log(`loop ${result.loopStart.toFixed(2)}s → ${result.loopEnd.toFixed(2)}s`)

There is no AudioContext in Node, so decode a WAV with the built-in decodeWav, then call the same analysis functions.

import { decodeWav, feature, tempo } from 'pleco-xa'
import { readFileSync } from 'node:fs'
const buf = readFileSync('clip.wav')
const { channels, sampleRate } = decodeWav(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength))
const y = channels[0]
console.log('tempo:', tempo(y, sampleRate))
const mfcc = feature.mfcc(y, { sr: sampleRate }) // [n_mfcc][n_frames]

Quality is the default path. Where a fast or live variant exists it is a separate, named call — for example quickTempo for a ~5–10 s live estimate — never a silent fallback. If a quality gate can’t be met, the function throws with diagnostics rather than returning a fabricated number.

26 CI-gated test suites (237 tests) run on every push; the loop detector used here is additionally locked against committed golden fixtures on real audio, within ±10 ms.