memlnaut-nisps/docs/redesign/manifold-export/ui_kits/memlnaut/console/model.jsx
monkey-w1n5t0n 24057e7b5f docs(redesign): Manifold research findings + design specs + build plan
Phase-1 audits (feedback-behaviour, engine-surface, design/Manifold), the
judge-panelled rl-feedback-design, dock/backends/inputs specs, the
upstream-firmware survey, the Manifold design mirror, and BUILD-PLAN.md
(resume anchor + locked decisions).
2026-06-28 04:14:12 +02:00

122 lines
7.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Console 2.0 — shared instrument model. Pure data + inference used by all
three altitudes (Console / Perform / Zen) so they share one spine. */
function mkParams(spec) {
const out = [];
for (const [group, names] of spec) {
names.forEach((name) => out.push({ name, group, status: 'live', val: 0.5, min: 0, max: 1, curve: 0.5 }));
}
return out;
}
const MF_MODES = [
{ id: 'paf_synth', label: 'PAF Synth', cls: 'Synth', glyph: '∿', input: 'xy',
params: mkParams([['formant', ['F1', 'F2', 'F3', 'tilt', 'spread', 'skirt']], ['pitch', ['root', 'glide', 'detune']], ['amp', ['gain', 'attack', 'decay']], ['filter', ['cutoff', 'res', 'env']], ['fx', ['drive', 'air', 'width']]]) },
{ id: 'channel_strip', label: 'Channel Strip', cls: 'Synth', glyph: '▤', input: 'joystick',
params: mkParams([['filter', ['lo', 'loMid', 'hiMid', 'hi']], ['amp', ['comp', 'gate', 'makeup']], ['fx', ['sat', 'width', 'glue', 'tilt', 'air']]]) },
{ id: 'verb_fx', label: 'Verb FX', cls: 'Synth', glyph: '◞', input: 'joystick',
params: mkParams([['fx', ['size', 'decay', 'damp', 'diff']], ['mod', ['rate', 'depth']], ['filter', ['lo', 'hi']]]) },
{ id: 'elysiamorf', label: 'Elysiamorf', cls: 'Synth', glyph: '❋', input: 'xy',
params: mkParams([['formant', ['grain', 'size', 'pos', 'spray']], ['mod', ['rate', 'depth', 'jitter']], ['amp', ['gain', 'env']], ['filter', ['cutoff', 'res']], ['fx', ['blur', 'shimmer', 'freeze', 'width']]]) },
{ id: 'memlcelium', label: 'MEML Celium', cls: 'Sequencer', glyph: '☷', input: 'xy',
params: mkParams([['mod', ['cvA', 'cvB', 'gate', 'div']], ['pitch', ['root', 'scale', 'oct']], ['amp', ['vca', 'slew']]]) },
{ id: 'breakor', label: 'Breakor', cls: 'Sequencer', glyph: '⊟', input: 'joystick',
params: mkParams([['mod', ['density', 'swing', 'fill', 'stutter']], ['amp', ['punch', 'decay']], ['filter', ['tone', 'crush']], ['fx', ['glitch', 'rev']]]) },
{ id: 'sound_analysis_midi', label: 'Sound Analysis → MIDI', cls: 'Controller', glyph: '⇉', input: 'audio_in', badge: '1-input',
params: mkParams([['mod', ['cc1', 'cc2', 'cc3', 'cc4']], ['pitch', ['note', 'bend']], ['amp', ['vel', 'press']]]) },
{ id: 'visualizer', label: 'Visualizer', cls: 'Visual', glyph: '◑', input: 'xy',
params: mkParams([['mod', ['hue', 'sat', 'flow', 'warp']], ['amp', ['bloom', 'fade']], ['fx', ['grain', 'trail']]]) },
{ id: 'c15', label: 'C15', cls: 'Synth', glyph: '◆', input: 'xy', placeholder: true, badge: 'soon', params: mkParams([['amp', ['a', 'b']]]) },
];
function applyCurve(v, c) { const e = 0.25 + c * 1.75; return Math.pow(Math.max(0, Math.min(1, v)), e); }
function MF_infer(pos, seed, params) {
const [x, y] = pos;
return params.map((p, i) => {
if (p.status === 'off') return 0; // deactivated → muted
if (p.status === 'fixed') return p.val ?? 0.5; // deactivated → held static
// live → driven by the model, shaped by min/max/curve
const a = Math.sin(x * (1.2 + i * 0.19) * Math.PI + i * 0.7 + seed) * 0.5 + 0.5;
const b = Math.cos(y * (0.9 + i * 0.13) * Math.PI - i * 0.4 - seed * 0.5) * 0.5 + 0.5;
let v = a * 0.55 + b * 0.45;
v = p.min + applyCurve(v, p.curve) * (p.max - p.min);
return Math.max(0, Math.min(1, v));
});
}
function MF_seededGradient(rev) {
const n = 4, norms = [], status = [];
for (let i = 0; i < n; i++) {
const r = Math.abs(Math.sin((rev + 1) * (i + 1) * 12.9898) * 43758.5453 % 1);
norms.push(0.2 + r * 0.8);
status.push(r > 0.85 ? 'exploding' : r < 0.18 ? 'vanishing' : r < 0.3 ? 'converged' : 'healthy');
}
return { norms, status };
}
/* useInstrument — the shared reactive spine + verdict/training actions.
Returns flat state + actions; each view renders as much of it as it needs. */
let MF_SNAP_ID = 0;
function useInstrument(initialMode) {
const [modeId, setModeId] = React.useState(initialMode || 'paf_synth');
const mode = MF_MODES.find((m) => m.id === modeId) || MF_MODES[0];
const [params, setParams] = React.useState(() => mode.params.map((p) => ({ ...p })));
const [pos, setPos] = React.useState([0.5, 0.5]);
const [seed, setSeed] = React.useState(0.4);
const [axes, setAxes] = React.useState({ boldness: 0.55, memory: 0.4, precision: 0.5 });
const [noiseCap, setNoiseCap] = React.useState(0.12);
const [examples, setExamples] = React.useState(0);
const [loss, setLoss] = React.useState([]);
const [busy, setBusy] = React.useState(false);
const [snapshots, setSnapshots] = React.useState([]);
const [ab, setAB] = React.useState('B');
const [holdingA, setHoldingA] = React.useState(false);
const aRef = React.useRef(null);
const [health, setHealth] = React.useState(0.8);
const [rev, setRev] = React.useState(1);
const [follow, setFollow] = React.useState(false);
const [pins, setPins] = React.useState([]);
const [firstSession, setFirstSession] = React.useState(true);
React.useEffect(() => {
setParams(mode.params.map((p) => ({ ...p }))); setPos([0.5, 0.5]); setExamples(0);
setLoss([]); setSnapshots([]); setSeed(0.4); setFollow(false); setPins([]); setHealth(0.8); setRev(1);
}, [modeId]);
const effSeed = holdingA && aRef.current ? aRef.current.seed : seed;
const values = React.useMemo(() => MF_infer(pos, effSeed, params), [pos, effSeed, params]);
const gradient = React.useMemo(() => MF_seededGradient(rev), [rev]);
const pushSnap = (tag) => setSnapshots((s) => [...s, { id: ++MF_SNAP_ID, tag, noise: noiseCap, seed }].slice(-50));
const commit = () => {
setFirstSession(false); pushSnap('commit +'); setBusy(true);
setNoiseCap((n) => Math.max(0.02, n * 0.7)); setHealth((h) => Math.min(1, h + 0.08)); setRev((r) => r + 1);
setTimeout(() => { setLoss((prev) => { const b = prev.length ? prev[prev.length - 1] : 0.5; return [...prev, Math.max(0.004, b * (0.8 + Math.random() * 0.08))].slice(-120); }); setBusy(false); }, 260);
};
const perturb = () => {
setFirstSession(false); pushSnap('perturb ');
setSeed((s) => s + (Math.random() - 0.5) * (noiseCap * 4 + 0.3));
setNoiseCap((n) => Math.min(0.5, n + 0.06)); setHealth((h) => Math.max(0.1, h - 0.06)); setRev((r) => r + 1);
};
const reroll = () => { setFirstSession(false); pushSnap('re-roll'); setSeed(Math.random() * 6); setNoiseCap(0.4); setHealth(0.5); setRev((r) => r + 1); };
const undo = () => setSnapshots((s) => { if (!s.length) return s; const last = s[s.length - 1]; setSeed(last.seed); setNoiseCap(last.noise); setRev((r) => r + 1); return s.slice(0, -1); });
const train = () => { setBusy(true); setTimeout(() => { setLoss((p) => { const b = p.length ? p[p.length - 1] : 0.5; return [...p, Math.max(0.004, b * 0.82)].slice(-120); }); setBusy(false); }, 260); };
const setParam = (i, patch) => setParams((ps) => ps.map((p, j) => (j === i ? { ...p, ...patch } : p)));
const cycleStatus = (i) => setParams((ps) => ps.map((p, j) => (j === i ? { ...p, status: ({ off: 'fixed', fixed: 'live', live: 'off' })[p.status] } : p)));
const toggleAB = () => { if (ab === 'B') { aRef.current = { seed }; setAB('A'); } else { if (aRef.current) setSeed(aRef.current.seed); setAB('B'); } };
const cycleMode = (dir = 1) => {
const live = MF_MODES.filter((m) => !m.placeholder);
const i = live.findIndex((m) => m.id === modeId);
setModeId(live[(i + dir + live.length) % live.length].id);
};
return {
MODES: MF_MODES, modeId, setModeId, cycleMode, mode, params, setParam, cycleStatus,
pos, setPos, seed, values, axes, setAxes, setAxis: (k, v) => setAxes((s) => ({ ...s, [k]: v })),
noiseCap, setNoiseCap, examples, loss, busy, train,
snapshots, commit, perturb, reroll, undo, ab, toggleAB, holdingA, setHoldingA,
health, rev, gradient, follow, setFollow, pins, setPins, firstSession,
};
}
window.MF_MODES = MF_MODES;
window.MF_infer = MF_infer;
window.MF_seededGradient = MF_seededGradient;
window.useInstrument = useInstrument;