21abc12audio-spike-sorting: benchmark framework for audio event detection and classificationJeremy Magland 1/**
2 * Demo data: three synthetic event types generated as if they had been
3 * recorded (one long clip per type, events well separated), then pushed
4 * through the same onset detector a real recording would meet. Lets the
5 * whole pipeline be tried without a microphone, and gives the browser
6 * smoke test something deterministic to chew on.
7 */
8import { detectOnsets } from './analysis/onsets.ts';
9import { mulberry32 } from './bench/rng.ts';
10import { FS } from './bench/types.ts';
11import type { ClipMeta, EventType } from './store.ts';
13export interface DemoClip { typeName: string; color: string; samples: Float32Array; meta: ClipMeta; }
15const uid = () => Math.random().toString(36).slice(2, 10);
17export function makeDemoData(eventsPerType = 40): DemoClip[] {
18 const rng = mulberry32(1234);
19 const burstBase = new Float32Array(Math.round(0.18 * FS));
20 for (let i = 0; i < burstBase.length; i++) burstBase[i] = rng() * 2 - 1;
22 const kinds: { name: string; color: string; gen: (out: Float32Array, at: number) => void }[] = [
23 {
24 name: 'demo low tone', color: '#e4573d',
25 gen: (out, at) => {
26 const f = 300 * (1 + 0.01 * (rng() - 0.5)), tau = 0.06 * (1 + 0.2 * (rng() - 0.5));
27 for (let i = 0; i < 0.18 * FS && at + i < out.length; i++) {
28 const t = i / FS;
29 out[at + i] += Math.exp(-t / tau) * Math.sin(2 * Math.PI * f * t) * 0.7;
30 }
31 },
32 },
33 {
34 name: 'demo high tone', color: '#3d7de4',
35 gen: (out, at) => {
36 const f = 1400 * (1 + 0.01 * (rng() - 0.5)), tau = 0.025 * (1 + 0.2 * (rng() - 0.5));
37 for (let i = 0; i < 0.18 * FS && at + i < out.length; i++) {
38 const t = i / FS;
39 out[at + i] += Math.exp(-t / tau) * Math.sin(2 * Math.PI * f * t) * 0.7;
40 }
41 },
42 },
43 {
44 name: 'demo burst', color: '#3dbb6e',
45 gen: (out, at) => {
46 const tau = 0.012 * (1 + 0.2 * (rng() - 0.5));
47 for (let i = 0; i < burstBase.length && at + i < out.length; i++) {
48 const t = i / FS;
49 out[at + i] += Math.exp(-t / tau) * (burstBase[i] + 0.15 * (rng() * 2 - 1)) * 0.6;
50 }
51 },
52 },
53 ];
55 return kinds.map((kind) => {
56 const gap = 0.3;
57 const n = Math.round((0.5 + eventsPerType * gap) * FS);
58 const samples = new Float32Array(n);
59 for (let i = 0; i < n; i++) samples[i] = (rng() * 2 - 1) * 0.002;
60 for (let e = 0; e < eventsPerType; e++) {
61 const at = Math.round((0.25 + e * gap + 0.02 * (rng() - 0.5)) * FS);
62 kind.gen(samples, at);
63 }
64 const sens = 0.6;
65 const onsets = detectOnsets(samples, FS, sens);
66 const meta: ClipMeta = { id: uid(), sens, onsets, excluded: [] };
67 return { typeName: kind.name, color: kind.color, samples, meta };
68 });
69}
71export function demoTypes(clips: DemoClip[]): EventType[] {
72 return clips.map((c) => ({ id: uid(), name: c.typeName, color: c.color, clips: [c.meta] }));
73}