/ proof-of-concept / midiboot
Sign in
proof-of-concept / midiboot
midiboot / src / instruments / simulated / mrender.ts
63 lines · 2.3 KBCodeBlameHistory
78851f6Replace instrawave with simulated instruments driven by manifestsJeremy Magland 1import { executeCode, type RuntimeValue } from 'numbl';
2import { mapDuration, mapPitch, mapVelocity, type SimManifest } from './manifest.ts';
4/**
5 * One note through one simulated instrument: build the parameter struct
6 * from the model's own defaults plus the manifest's overrides, call the
7 * entry function through numbl, and hand back the mono trace. Pure with
8 * respect to the environment (no DOM, no worker), so the browser worker
9 * and the node tests share it.
10 */
12export interface MFile {
13 name: string;
14 source: string;
17export interface SimNoteOpts {
18 pitch: number; // MIDI note number
19 velocity: number; // 0..1
20 durationSec: number;
21 sampleRate: number;
22 quality: 'preview' | 'full';
23 settings: Record<string, number>; // knob values; unknown keys are ignored
26interface RuntimeTensorLike {
27 kind: 'tensor';
28 data: Float64Array;
31export function renderSimNote(manifest: SimManifest, mfiles: MFile[], opts: SimNoteOpts): Float32Array {
32 const assigns: string[] = [];
33 const setP = (key: string, value: number) => {
34 if (!Number.isFinite(value)) throw new Error(`parameter ${key} is not finite`);
35 assigns.push(`P.${key} = ${value};`);
36 };
37 for (const [k, v] of Object.entries(manifest.fixed)) setP(k, v);
38 for (const knob of manifest.knobs) {
39 const v = opts.settings[knob.key];
40 setP(knob.key, v === undefined ? knob.default : Math.max(knob.min, Math.min(knob.max, v)));
41 }
42 if (opts.quality === 'preview') for (const [k, v] of Object.entries(manifest.preview)) setP(k, v);
43 setP('sample_rate', opts.sampleRate);
44 const pv = mapPitch(manifest, opts.pitch);
45 if (pv) setP(pv.key, pv.value);
46 const vv = mapVelocity(manifest, opts.velocity);
47 setP(vv.key, vv.value);
48 const dv = mapDuration(manifest, opts.durationSec);
49 if (dv) setP(dv.key, dv.value);
51 const code = [
52 `P = ${manifest.defaults}();`,
53 ...assigns,
54 `y_out = ${manifest.entry}(P);`,
55 ].join('\n');
57 const r = executeCode(code, { displayResults: false }, mfiles, 'midiboot_note.m');
58 const y = r.variableValues.y_out as RuntimeValue as RuntimeTensorLike;
59 if (!y || y.kind !== 'tensor') throw new Error(`${manifest.name}: entry '${manifest.entry}' did not return a tensor`);
60 const out = new Float32Array(y.data.length);
61 for (let i = 0; i < y.data.length; i++) out[i] = y.data[i];
62 return out;
↑↓ move↵openescclose