/** * Runs a MATLAB-syntax classifier through numbl, with the same interface * the audio-spike-sorting benchmark uses, so an algorithm developed * there drops in here unchanged: * * function [times, labels] = anyname(x, fs, training) * * x is the take (column vector), fs its rate, and training a struct with * snips ({1 x nVoices} cell of [snipLen x nSnips] matrices), names, and * pre (samples before the hit onset within each snippet). It returns * event times in seconds and 1-based voice indices. Imported only by the * worker and the node test, never by the page, so numbl stays out of the * main bundle. */ import { executeCode, RTV } from 'numbl'; import type { RuntimeValue } from 'numbl'; import { extractFunctionName } from './source.ts'; import type { Detection, TrainingArrays } from './types.ts'; export function runAlgorithm( source: string, x: Float32Array | Float64Array, fs: number, training: TrainingArrays, ): { detections: Detection[]; logs: string[] } { const fnName = extractFunctionName(source); if (!fnName) throw new Error('the classifier must define a function [times, labels] = name(x, fs, training)'); const xd = x instanceof Float64Array ? x : Float64Array.from(x); const trainingStruct = RTV.struct({ snips: RTV.cell( training.snips.map((s) => RTV.tensor(Float64Array.from(s.data), [s.snipLen, Math.max(s.count, 0)])), [1, training.snips.length], ), names: RTV.cell(training.names.map((n) => RTV.char(n)), [1, training.names.length]), pre: RTV.num(training.pre), }); const logs: string[] = []; const result = executeCode( `[times__, labels__] = ${fnName}(x, fs, training);\ntimes__ = times__(:); labels__ = labels__(:);`, { initialVariableValues: { x: RTV.tensor(xd, [xd.length, 1]), fs: RTV.num(fs), training: trainingStruct }, optimization: '1', displayResults: false, onOutput: (text: string) => logs.push(text), }, [{ name: `${fnName}.m`, source }], 'main.m', ); const times = toVector(result.variableValues.times__); const labels = toVector(result.variableValues.labels__); if (times.length !== labels.length) { throw new Error(`times (${times.length}) and labels (${labels.length}) differ in length`); } const detections: Detection[] = times.map((t, i) => ({ t, typeIndex: Math.round(labels[i]) - 1 })); detections.sort((a, b) => a.t - b.t); return { detections, logs }; } function toVector(v: RuntimeValue | undefined): number[] { if (v === undefined || v === null) throw new Error('the classifier did not return times/labels'); if (typeof v === 'number') return [v]; const t = v as { data?: Float64Array }; if (t.data instanceof Float64Array) return Array.from(t.data); throw new Error('times/labels must be numeric vectors'); }