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.
Try it right now (no files)
Section titled “Try it right now (no files)”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 = 22050const y = new Float32Array(sr * 4) // 4 secondsconst period = Math.round((sr * 60) / 120) // one click every 0.5 sfor (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 exactIn the browser
Section titled “In the browser”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 trackerconst { 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`)In Node
Section titled “In Node”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]Explicit tiers, never silent
Section titled “Explicit tiers, never silent”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.