21abc12audio-spike-sorting: benchmark framework for audio event detection and classificationJeremy Magland 1/**
2 * Whole-project export and import as one JSON file: event types with
3 * their clip audio (base64), algorithm sources, and the benchmark
4 * configuration. Benchmark results are deliberately left out; they are
5 * reproducible from the config and the seed.
6 */
7import type { AlgorithmDef } from './bench/types.ts';
8import type { BenchConfig, ClipMeta, EventType } from './store.ts';
10export interface ProjectFile {
11 format: 'audio-spike-sorting';
12 version: 1;
13 types: (EventType & { clipAudio: Record<string, { b64: string; sr: number }> })[];
14 config: BenchConfig;
15 algorithms: AlgorithmDef[];
16}
18function f32ToB64(x: Float32Array): string {
19 const bytes = new Uint8Array(x.buffer, x.byteOffset, x.byteLength);
20 let s = '';
21 const CHUNK = 0x8000;
22 for (let i = 0; i < bytes.length; i += CHUNK) {
23 s += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
24 }
25 return btoa(s);
26}
28function b64ToF32(b64: string): Float32Array {
29 const bin = atob(b64);
30 const bytes = new Uint8Array(bin.length);
31 for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
32 return new Float32Array(bytes.buffer);
33}
35export function exportProject(
36 types: EventType[],
37 config: BenchConfig,
38 algorithms: AlgorithmDef[],
39 getAudio: (clipId: string) => { samples: Float32Array; sr: number } | null,
40): string {
41 const doc: ProjectFile = {
42 format: 'audio-spike-sorting',
43 version: 1,
44 types: types.map((t) => {
45 const clipAudio: Record<string, { b64: string; sr: number }> = {};
46 for (const c of t.clips) {
47 const a = getAudio(c.id);
48 if (a) clipAudio[c.id] = { b64: f32ToB64(a.samples), sr: a.sr };
49 }
50 return { ...t, clipAudio };
51 }),
52 config,
53 algorithms,
54 };
55 return JSON.stringify(doc);
56}
58export interface ImportedProject {
59 types: EventType[];
60 config: BenchConfig;
61 algorithms: AlgorithmDef[];
62 clipAudio: { id: string; samples: Float32Array; sr: number }[];
63}
65export function importProject(json: string): ImportedProject {
66 const doc = JSON.parse(json) as ProjectFile;
67 if (doc.format !== 'audio-spike-sorting') throw new Error('not an audio-spike-sorting project file');
68 const clipAudio: ImportedProject['clipAudio'] = [];
69 const types: EventType[] = doc.types.map((t) => {
70 for (const [id, a] of Object.entries(t.clipAudio ?? {})) {
71 clipAudio.push({ id, samples: b64ToF32(a.b64), sr: a.sr });
72 }
73 const clips: ClipMeta[] = t.clips;
74 return { id: t.id, name: t.name, color: t.color, clips };
75 });
76 return { types, config: doc.config, algorithms: doc.algorithms, clipAudio };
77}