2026-02-11 13:17:17 +01:00
|
|
|
// NISPS Playground - Main application
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
// Wires IML engine to visual system OR C15 synth with joystick input and dual learning modes
|
2026-02-11 13:17:17 +01:00
|
|
|
|
|
|
|
|
import { IML } from './nisps/iml.js';
|
|
|
|
|
import { FlowFieldVisualizer } from './ui/visualizer.js';
|
|
|
|
|
import { VirtualJoystick } from './ui/joystick.js';
|
|
|
|
|
import { Controls } from './ui/controls.js';
|
|
|
|
|
import { ParamDisplay } from './ui/param-display.js';
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
import { C15Bridge } from './synth/c15-bridge.js';
|
|
|
|
|
import { Arpeggiator } from './synth/arpeggiator.js';
|
2026-03-21 23:15:11 +01:00
|
|
|
import { SYNTH_PARAM_MAP, SYNTH_PARAM_NAMES, SYNTH_PARAM_COLORS, applyTame } from './synth/param-map.js';
|
2026-02-11 13:17:17 +01:00
|
|
|
|
|
|
|
|
const N_INPUTS = 2;
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
const N_VISUAL_OUTPUTS = 20;
|
|
|
|
|
const N_SYNTH_OUTPUTS = SYNTH_PARAM_MAP.length; // 126
|
|
|
|
|
const N_OUTPUTS = N_SYNTH_OUTPUTS; // MLP always produces full output; visual uses first 20
|
2026-02-11 13:17:17 +01:00
|
|
|
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
// Visual mode param display config
|
|
|
|
|
const VISUAL_PARAM_NAMES = ['Flow', 'Scale', 'Speed', 'Hue', 'Spread', 'Size', 'Trail', 'Turb', 'Attract', 'Radius', 'DispRate', 'DispAmt', 'Lifetime', 'Respawn', 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate'];
|
|
|
|
|
const VISUAL_PARAM_COLORS = ['#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d'];
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
// --- State ---
|
|
|
|
|
let iml;
|
|
|
|
|
let visualizer;
|
|
|
|
|
let joystick;
|
|
|
|
|
let controls;
|
|
|
|
|
let paramDisplay;
|
|
|
|
|
let learningMode = 'examples'; // 'examples' | 'rl'
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
let outputMode = 'visual'; // 'visual' | 'synth'
|
2026-02-11 13:17:17 +01:00
|
|
|
let noiseLevel = 0.05;
|
|
|
|
|
let rlExplorationDecay = 0.97;
|
|
|
|
|
let animating = true;
|
2026-02-11 16:44:33 +01:00
|
|
|
let gamepadIndex = -1;
|
|
|
|
|
let gamepadButtonsPrev = [];
|
|
|
|
|
let gamepadConnected = false;
|
|
|
|
|
let gamepadLastAxes = [0.5, 0.5];
|
2026-02-11 16:56:19 +01:00
|
|
|
let followMode = false;
|
2026-02-14 09:47:47 +01:00
|
|
|
let visualExpanded = false;
|
|
|
|
|
let appRoot;
|
|
|
|
|
let expandVisualBtn;
|
2026-02-11 13:17:17 +01:00
|
|
|
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
// Synth state
|
|
|
|
|
let c15 = null;
|
|
|
|
|
let arpeggiator = null;
|
|
|
|
|
|
2026-03-21 23:15:11 +01:00
|
|
|
// Devmode: tame level (0 = no mitigation, 1 = strongest)
|
|
|
|
|
// Set via URL ?tame=0.7 or window.setTameLevel(0.7)
|
|
|
|
|
let tameLevel = parseFloat(new URLSearchParams(location.search).get('tame') ?? '0.7');
|
|
|
|
|
if (isNaN(tameLevel)) tameLevel = 0.7;
|
|
|
|
|
tameLevel = Math.max(0, Math.min(1, tameLevel));
|
|
|
|
|
window.setTameLevel = (v) => { tameLevel = Math.max(0, Math.min(1, v)); console.log(`[NISPS] tame=${tameLevel}`); };
|
|
|
|
|
window.getTameLevel = () => tameLevel;
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
// --- Init ---
|
|
|
|
|
function init() {
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
iml = new IML(N_INPUTS, N_OUTPUTS, [32, 48, 64], 1000, 1.0, 0.00001);
|
2026-02-11 13:17:17 +01:00
|
|
|
iml.setLogger(msg => console.log('[NISPS]', msg));
|
|
|
|
|
|
|
|
|
|
// Visualizer
|
2026-02-14 09:47:47 +01:00
|
|
|
appRoot = document.querySelector('.app');
|
|
|
|
|
expandVisualBtn = document.getElementById('expand-visual-btn');
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
const canvas = document.getElementById('visual-canvas');
|
|
|
|
|
visualizer = new FlowFieldVisualizer(canvas);
|
|
|
|
|
|
|
|
|
|
// Joystick
|
|
|
|
|
joystick = new VirtualJoystick(document.getElementById('joystick-container'), {
|
|
|
|
|
size: 160,
|
2026-02-11 16:56:19 +01:00
|
|
|
trackpadScale: 2,
|
2026-02-11 13:17:17 +01:00
|
|
|
springBack: false,
|
|
|
|
|
onChange: onJoystickMove,
|
2026-02-11 16:56:19 +01:00
|
|
|
onFollowModeChange: (enabled) => {
|
|
|
|
|
followMode = enabled;
|
|
|
|
|
refreshDashboard();
|
|
|
|
|
},
|
2026-02-11 13:17:17 +01:00
|
|
|
});
|
|
|
|
|
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
// Parameter display (start in visual mode with 20 params)
|
|
|
|
|
paramDisplay = new ParamDisplay(document.getElementById('param-display'), N_VISUAL_OUTPUTS);
|
2026-02-11 13:17:17 +01:00
|
|
|
|
|
|
|
|
// Controls
|
|
|
|
|
controls = new Controls(document.getElementById('controls-container'), {
|
|
|
|
|
onAddExample,
|
|
|
|
|
onTrain,
|
|
|
|
|
onRandomize,
|
|
|
|
|
onClear,
|
|
|
|
|
onThumbsUp,
|
|
|
|
|
onThumbsDown,
|
|
|
|
|
onModeChange,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Resize handling
|
|
|
|
|
window.addEventListener('resize', () => {
|
2026-02-14 09:47:47 +01:00
|
|
|
resizeVisualizerSurface();
|
2026-02-11 13:17:17 +01:00
|
|
|
});
|
|
|
|
|
|
2026-02-14 09:47:47 +01:00
|
|
|
if (expandVisualBtn) {
|
|
|
|
|
expandVisualBtn.addEventListener('click', () => setVisualExpanded(!visualExpanded));
|
|
|
|
|
updateExpandButtonState();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
// Help overlay
|
|
|
|
|
const helpBtn = document.getElementById('help-btn');
|
|
|
|
|
const helpOverlay = document.getElementById('help-overlay');
|
|
|
|
|
if (helpBtn && helpOverlay) {
|
|
|
|
|
helpBtn.addEventListener('click', () => helpOverlay.classList.toggle('hidden'));
|
|
|
|
|
helpOverlay.addEventListener('click', () => helpOverlay.classList.add('hidden'));
|
|
|
|
|
}
|
|
|
|
|
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
// Side panel
|
|
|
|
|
initSidePanel();
|
|
|
|
|
|
|
|
|
|
// Init synth
|
|
|
|
|
c15 = new C15Bridge();
|
|
|
|
|
c15.onStatusChange = (msg) => {
|
|
|
|
|
const el = document.getElementById('synth-status');
|
|
|
|
|
if (el) el.textContent = msg;
|
|
|
|
|
};
|
|
|
|
|
c15.loadParams();
|
|
|
|
|
arpeggiator = new Arpeggiator(c15);
|
|
|
|
|
|
|
|
|
|
// Wire synth controls
|
|
|
|
|
initSynthControls();
|
|
|
|
|
|
2026-02-11 16:44:33 +01:00
|
|
|
window.addEventListener('gamepadconnected', () => refreshDashboard());
|
|
|
|
|
window.addEventListener('gamepaddisconnected', () => refreshDashboard());
|
2026-02-11 16:56:19 +01:00
|
|
|
window.addEventListener('keydown', onKeyDown);
|
2026-02-11 16:44:33 +01:00
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
// Run initial inference to populate outputs
|
|
|
|
|
iml.setInput(0, 0.5);
|
|
|
|
|
iml.setInput(1, 0.5);
|
|
|
|
|
iml.process();
|
|
|
|
|
visualizer.setParams(iml.getOutputs());
|
|
|
|
|
paramDisplay.update(iml.getOutputs());
|
2026-02-11 16:44:33 +01:00
|
|
|
controls.updateStatus(iml.exampleCount, iml.lastLoss, noiseLevel);
|
|
|
|
|
controls.updateLossPlot(iml.lossHistory);
|
|
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
|
|
|
|
|
// Start animation
|
|
|
|
|
animate();
|
|
|
|
|
|
|
|
|
|
// Load from localStorage if available
|
|
|
|
|
loadState();
|
|
|
|
|
}
|
|
|
|
|
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
// --- Side Panel ---
|
|
|
|
|
function initSidePanel() {
|
|
|
|
|
const panel = document.getElementById('side-panel');
|
|
|
|
|
const toggle = document.getElementById('side-panel-toggle');
|
|
|
|
|
const close = document.getElementById('side-panel-close');
|
|
|
|
|
const backdrop = document.getElementById('side-panel-backdrop');
|
|
|
|
|
|
|
|
|
|
const openPanel = () => {
|
|
|
|
|
panel.classList.add('open');
|
|
|
|
|
backdrop.classList.remove('hidden');
|
|
|
|
|
};
|
|
|
|
|
const closePanel = () => {
|
|
|
|
|
panel.classList.remove('open');
|
|
|
|
|
backdrop.classList.add('hidden');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
toggle.addEventListener('click', openPanel);
|
|
|
|
|
close.addEventListener('click', closePanel);
|
|
|
|
|
backdrop.addEventListener('click', closePanel);
|
|
|
|
|
|
|
|
|
|
// Tab switching
|
|
|
|
|
panel.querySelectorAll('.sp-tab').forEach(tab => {
|
|
|
|
|
tab.addEventListener('click', () => {
|
|
|
|
|
const mode = tab.dataset.mode;
|
|
|
|
|
setOutputMode(mode);
|
|
|
|
|
|
|
|
|
|
panel.querySelectorAll('.sp-tab').forEach(t => t.classList.toggle('active', t === tab));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setOutputMode(mode) {
|
|
|
|
|
outputMode = mode;
|
|
|
|
|
|
|
|
|
|
const synthControls = document.getElementById('synth-controls');
|
|
|
|
|
const visualInfo = document.getElementById('visual-info');
|
|
|
|
|
const presetsVisual = document.getElementById('presets-visual');
|
|
|
|
|
const modeBadge = document.getElementById('mode-badge');
|
|
|
|
|
const paramContainer = document.getElementById('param-display');
|
|
|
|
|
|
|
|
|
|
if (mode === 'synth') {
|
|
|
|
|
synthControls.classList.remove('hidden');
|
|
|
|
|
visualInfo.classList.add('hidden');
|
|
|
|
|
presetsVisual.classList.add('hidden');
|
|
|
|
|
modeBadge.textContent = 'Synth';
|
|
|
|
|
modeBadge.classList.add('synth');
|
|
|
|
|
paramContainer.classList.add('synth-mode');
|
|
|
|
|
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
// Rebuild param display with all synth params
|
|
|
|
|
paramDisplay.rebuild(N_SYNTH_OUTPUTS, SYNTH_PARAM_NAMES, SYNTH_PARAM_COLORS);
|
|
|
|
|
paramDisplay.setDraggable(learningMode === 'examples');
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
} else {
|
|
|
|
|
synthControls.classList.add('hidden');
|
|
|
|
|
visualInfo.classList.remove('hidden');
|
|
|
|
|
presetsVisual.classList.remove('hidden');
|
|
|
|
|
modeBadge.textContent = 'Visual';
|
|
|
|
|
modeBadge.classList.remove('synth');
|
|
|
|
|
paramContainer.classList.remove('synth-mode');
|
|
|
|
|
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
// Rebuild param display with visual params (first 20)
|
|
|
|
|
paramDisplay.rebuild(N_VISUAL_OUTPUTS, VISUAL_PARAM_NAMES, VISUAL_PARAM_COLORS);
|
|
|
|
|
paramDisplay.setDraggable(learningMode === 'examples');
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Re-run inference and route outputs
|
|
|
|
|
routeOutputs(iml.getOutputs());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function routeOutputs(outputs) {
|
|
|
|
|
// Always update visualizer (it's always visible)
|
|
|
|
|
visualizer.setParams(outputs);
|
|
|
|
|
|
2026-03-21 23:15:11 +01:00
|
|
|
// If in synth mode, also send to C15 with tame-level constraining
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
if (outputMode === 'synth' && c15 && c15.running) {
|
|
|
|
|
for (let i = 0; i < outputs.length && i < SYNTH_PARAM_MAP.length; i++) {
|
2026-03-21 23:15:11 +01:00
|
|
|
const value = applyTame(outputs[i], SYNTH_PARAM_MAP[i], tameLevel);
|
|
|
|
|
c15.setParameter(SYNTH_PARAM_MAP[i].id, value);
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Synth Controls ---
|
|
|
|
|
function initSynthControls() {
|
|
|
|
|
const startBtn = document.getElementById('synth-start');
|
|
|
|
|
const volumeSlider = document.getElementById('synth-volume');
|
|
|
|
|
const arpToggle = document.getElementById('arp-toggle');
|
|
|
|
|
const arpProgression = document.getElementById('arp-progression');
|
|
|
|
|
const arpTempo = document.getElementById('arp-tempo');
|
|
|
|
|
const arpOctaves = document.getElementById('arp-octaves');
|
|
|
|
|
const arpOffset = document.getElementById('arp-offset');
|
|
|
|
|
|
|
|
|
|
startBtn.addEventListener('click', async () => {
|
|
|
|
|
if (c15.running) {
|
|
|
|
|
arpeggiator.stop();
|
|
|
|
|
arpToggle.textContent = 'Play';
|
|
|
|
|
arpToggle.classList.remove('playing');
|
|
|
|
|
await c15.stop();
|
|
|
|
|
startBtn.textContent = 'Start Audio';
|
|
|
|
|
} else {
|
|
|
|
|
await c15.start();
|
|
|
|
|
startBtn.textContent = 'Stop Audio';
|
|
|
|
|
// Send current NISPS outputs to synth
|
|
|
|
|
routeOutputs(iml.getOutputs());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
volumeSlider.addEventListener('input', (e) => {
|
|
|
|
|
c15.setMasterVolume(parseFloat(e.target.value));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
arpToggle.addEventListener('click', () => {
|
|
|
|
|
if (!c15.running) return;
|
|
|
|
|
if (arpeggiator.playing) {
|
|
|
|
|
arpeggiator.stop();
|
|
|
|
|
arpToggle.textContent = 'Play';
|
|
|
|
|
arpToggle.classList.remove('playing');
|
|
|
|
|
} else {
|
|
|
|
|
arpeggiator.start();
|
|
|
|
|
arpToggle.textContent = 'Stop';
|
|
|
|
|
arpToggle.classList.add('playing');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
arpProgression.addEventListener('change', (e) => {
|
|
|
|
|
arpeggiator.progression = e.target.value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
arpTempo.addEventListener('input', (e) => {
|
|
|
|
|
const val = parseInt(e.target.value);
|
|
|
|
|
arpeggiator.bpm = val;
|
|
|
|
|
document.getElementById('tempo-val').textContent = val;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
arpOctaves.addEventListener('input', (e) => {
|
|
|
|
|
const val = parseInt(e.target.value);
|
|
|
|
|
arpeggiator.octaves = val;
|
|
|
|
|
document.getElementById('octaves-val').textContent = val;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
arpOffset.addEventListener('input', (e) => {
|
|
|
|
|
const val = parseInt(e.target.value);
|
|
|
|
|
arpeggiator.octaveOffset = val;
|
|
|
|
|
document.getElementById('offset-val').textContent = val;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
// --- Animation loop ---
|
|
|
|
|
function animate() {
|
|
|
|
|
if (!animating) return;
|
2026-02-11 16:44:33 +01:00
|
|
|
pollGamepad();
|
2026-02-11 13:17:17 +01:00
|
|
|
visualizer.draw();
|
|
|
|
|
requestAnimationFrame(animate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Joystick handler ---
|
|
|
|
|
function onJoystickMove(x, y) {
|
|
|
|
|
iml.setInput(0, x);
|
|
|
|
|
iml.setInput(1, y);
|
|
|
|
|
iml.process();
|
|
|
|
|
|
|
|
|
|
const outputs = iml.getOutputs();
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
routeOutputs(outputs);
|
2026-02-11 13:17:17 +01:00
|
|
|
|
|
|
|
|
// Only update param display from network in inference (not when user is dragging)
|
|
|
|
|
if (learningMode !== 'examples' || paramDisplay.activeBar < 0) {
|
|
|
|
|
paramDisplay.update(outputs);
|
|
|
|
|
}
|
2026-02-11 16:44:33 +01:00
|
|
|
|
|
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Examples mode callbacks ---
|
|
|
|
|
function onAddExample() {
|
|
|
|
|
// Use current joystick position as input, param bar values as desired output
|
|
|
|
|
const inputs = [joystick.x, joystick.y];
|
|
|
|
|
const outputs = [...paramDisplay.values];
|
|
|
|
|
iml.addExample(inputs, outputs);
|
|
|
|
|
controls.updateStatus(iml.exampleCount, iml.lastLoss, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
flash('btn-add');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onTrain() {
|
2026-02-11 16:44:33 +01:00
|
|
|
const loss = trainModel();
|
2026-02-11 13:17:17 +01:00
|
|
|
if (loss !== null) {
|
|
|
|
|
// After training, switch back to inference and update display
|
|
|
|
|
const outputs = iml.getOutputs();
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
routeOutputs(outputs);
|
2026-02-11 13:17:17 +01:00
|
|
|
paramDisplay.update(outputs);
|
|
|
|
|
controls.updateStatus(iml.exampleCount, loss, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
controls.updateLossPlot(iml.lossHistory);
|
|
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
flash('btn-train');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onRandomize() {
|
|
|
|
|
iml.randomiseWeights();
|
|
|
|
|
const outputs = iml.getOutputs();
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
routeOutputs(outputs);
|
2026-02-11 13:17:17 +01:00
|
|
|
paramDisplay.update(outputs);
|
|
|
|
|
noiseLevel = 0.05; // reset noise
|
|
|
|
|
controls.updateStatus(iml.exampleCount, iml.lastLoss, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onClear() {
|
|
|
|
|
iml.clearDataset();
|
2026-02-11 16:44:33 +01:00
|
|
|
iml.lossHistory = [];
|
|
|
|
|
iml.bestLoss = null;
|
|
|
|
|
iml.totalTrainingIterations = 0;
|
2026-02-11 13:17:17 +01:00
|
|
|
noiseLevel = 0.05;
|
|
|
|
|
controls.updateStatus(0, null, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
controls.updateLossPlot(iml.lossHistory);
|
|
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
clearState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- RL mode callbacks ---
|
|
|
|
|
function onThumbsUp() {
|
|
|
|
|
// Save current input->output mapping as a positive example
|
|
|
|
|
const inputs = [joystick.x, joystick.y];
|
|
|
|
|
const outputs = [...iml.getOutputs()];
|
|
|
|
|
iml.addExample(inputs, outputs);
|
|
|
|
|
|
|
|
|
|
// Retrain incrementally
|
2026-02-11 16:44:33 +01:00
|
|
|
trainModel();
|
2026-02-11 13:17:17 +01:00
|
|
|
|
|
|
|
|
// Decay noise - more positive examples = less exploration
|
|
|
|
|
noiseLevel *= rlExplorationDecay;
|
|
|
|
|
noiseLevel = Math.max(noiseLevel, 0.005);
|
|
|
|
|
|
|
|
|
|
controls.updateStatus(iml.exampleCount, iml.lastLoss, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
controls.updateLossPlot(iml.lossHistory);
|
|
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
flash('btn-thumbsup');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onThumbsDown() {
|
|
|
|
|
// Increase noise for more exploration
|
|
|
|
|
noiseLevel = Math.min(noiseLevel * 1.5, 0.3);
|
|
|
|
|
|
|
|
|
|
// Perturb weights
|
|
|
|
|
iml.moveWeights(noiseLevel);
|
|
|
|
|
|
|
|
|
|
const outputs = iml.getOutputs();
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
routeOutputs(outputs);
|
2026-02-11 13:17:17 +01:00
|
|
|
paramDisplay.update(outputs);
|
|
|
|
|
controls.updateStatus(iml.exampleCount, iml.lastLoss, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
flash('btn-thumbsdown');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onModeChange(mode) {
|
|
|
|
|
learningMode = mode;
|
|
|
|
|
if (mode === 'examples') {
|
|
|
|
|
paramDisplay.setDraggable(true);
|
|
|
|
|
} else {
|
|
|
|
|
paramDisplay.setDraggable(false);
|
|
|
|
|
}
|
|
|
|
|
controls.updateStatus(iml.exampleCount, iml.lastLoss, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-11 16:56:19 +01:00
|
|
|
function onKeyDown(e) {
|
2026-02-14 09:47:47 +01:00
|
|
|
if (e.key === 'Escape' && visualExpanded) {
|
|
|
|
|
setVisualExpanded(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 16:56:19 +01:00
|
|
|
if (!followMode || learningMode !== 'rl' || e.repeat) return;
|
|
|
|
|
if (e.key === '1' || e.code === 'Numpad1') {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
onThumbsDown();
|
|
|
|
|
} else if (e.key === '2' || e.code === 'Numpad2') {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
onThumbsUp();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 09:47:47 +01:00
|
|
|
function resizeVisualizerSurface() {
|
|
|
|
|
visualizer.resize();
|
|
|
|
|
visualizer.initParticles();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateExpandButtonState() {
|
|
|
|
|
if (!expandVisualBtn) return;
|
|
|
|
|
expandVisualBtn.textContent = visualExpanded ? 'Collapse' : 'Expand';
|
|
|
|
|
expandVisualBtn.title = visualExpanded ? 'Collapse visual area' : 'Expand visual area';
|
|
|
|
|
expandVisualBtn.setAttribute('aria-pressed', visualExpanded ? 'true' : 'false');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setVisualExpanded(expanded) {
|
|
|
|
|
visualExpanded = expanded;
|
|
|
|
|
if (appRoot) {
|
|
|
|
|
appRoot.classList.toggle('expanded', visualExpanded);
|
|
|
|
|
}
|
|
|
|
|
updateExpandButtonState();
|
|
|
|
|
|
|
|
|
|
// Let CSS layout settle before resizing the drawing surface.
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
resizeVisualizerSurface();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
// --- Presets ---
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
// Pad a 20-element visual preset array to N_OUTPUTS with 0.5 defaults
|
|
|
|
|
function padPreset(values20) {
|
|
|
|
|
const padded = new Array(N_OUTPUTS).fill(0.5);
|
|
|
|
|
for (let i = 0; i < values20.length; i++) padded[i] = values20[i];
|
|
|
|
|
return padded;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
window.loadPreset = function(name) {
|
|
|
|
|
iml.clearDataset();
|
|
|
|
|
|
|
|
|
|
if (name === 'calm-to-chaotic') {
|
|
|
|
|
// Bottom-left: slow, smooth, cool; top-right: fast, turbulent, warm
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
iml.addExample([0.1, 0.9], padPreset([0.25, 0.3, 0.1, 0.55, 0.2, 0.3, 0.02, 0.05, 0.9, 0.45, 0.25, 0.2, 0.9, 0.0, 0.0, 0.2, 0.05, 0.0, 0.0, 0.2]));
|
|
|
|
|
iml.addExample([0.9, 0.1], padPreset([0.75, 0.7, 0.9, 0.05, 0.8, 0.7, 0.9, 0.95, 0.3, 0.2, 0.85, 0.7, 0.25, 0.55, 1.0, 0.92, 0.02, 0.95, 0.8, 0.85]));
|
|
|
|
|
iml.addExample([0.5, 0.5], padPreset([0.5, 0.5, 0.5, 0.3, 0.5, 0.5, 0.4, 0.5, 0.7, 0.6, 0.5, 0.45, 0.55, 0.9, 0.5, 0.65, 0.08, 0.45, 0.4, 0.5]));
|
2026-02-11 13:17:17 +01:00
|
|
|
} else if (name === 'rainbow-sweep') {
|
|
|
|
|
// Left to right sweeps through hues
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
iml.addExample([0.0, 0.5], padPreset([0.5, 0.5, 0.4, 0.0, 0.3, 0.4, 0.05, 0.3, 0.8, 0.55, 0.4, 0.3, 0.8, 0.0, 0.0, 0.45, 0.08, 0.2, 0.25, 0.45]));
|
|
|
|
|
iml.addExample([0.5, 0.5], padPreset([0.5, 0.5, 0.4, 0.5, 0.3, 0.4, 0.05, 0.3, 0.8, 0.55, 0.55, 0.35, 0.7, 0.0, 0.4, 0.45, 0.08, 0.45, 0.4, 0.6]));
|
|
|
|
|
iml.addExample([1.0, 0.5], padPreset([0.5, 0.5, 0.4, 1.0, 0.3, 0.4, 0.05, 0.3, 0.8, 0.55, 0.75, 0.45, 0.6, 0.0, 0.8, 0.45, 0.08, 0.7, 0.55, 0.75]));
|
2026-02-11 13:17:17 +01:00
|
|
|
} else if (name === 'vortex') {
|
|
|
|
|
// Center: tight spiral, edges: wide flow
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
iml.addExample([0.5, 0.5], padPreset([0.0, 0.8, 0.8, 0.6, 0.1, 0.15, 0.02, 1.0, 1.0, 0.3, 0.95, 0.85, 0.25, 1.0, 0.5, 0.95, 0.01, 1.0, 1.0, 1.0]));
|
|
|
|
|
iml.addExample([0.0, 0.0], padPreset([0.5, 0.2, 0.3, 0.8, 0.9, 0.6, 0.08, 0.1, 0.35, 0.8, 0.25, 0.15, 0.8, 0.5, 0.2, 0.35, 0.2, 0.15, 0.2, 0.25]));
|
|
|
|
|
iml.addExample([1.0, 1.0], padPreset([0.5, 0.2, 0.3, 0.2, 0.9, 0.6, 0.08, 0.1, 0.35, 0.8, 0.25, 0.15, 0.8, 0.5, 0.8, 0.35, 0.2, 0.15, 0.2, 0.25]));
|
|
|
|
|
iml.addExample([0.0, 1.0], padPreset([0.3, 0.4, 0.5, 0.4, 0.5, 0.4, 0.05, 0.5, 0.65, 0.5, 0.55, 0.45, 0.45, 0.2, 0.4, 0.7, 0.1, 0.5, 0.4, 0.55]));
|
|
|
|
|
iml.addExample([1.0, 0.0], padPreset([0.7, 0.4, 0.5, 0.0, 0.5, 0.4, 0.05, 0.5, 0.65, 0.5, 0.55, 0.45, 0.45, 0.2, 0.9, 0.7, 0.1, 0.5, 0.4, 0.55]));
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-11 16:44:33 +01:00
|
|
|
const loss = trainModel();
|
2026-02-11 13:17:17 +01:00
|
|
|
const outputs = iml.getOutputs();
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
routeOutputs(outputs);
|
2026-02-11 13:17:17 +01:00
|
|
|
paramDisplay.update(outputs);
|
|
|
|
|
controls.updateStatus(iml.exampleCount, loss, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
controls.updateLossPlot(iml.lossHistory);
|
|
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- Persistence ---
|
|
|
|
|
function saveState() {
|
|
|
|
|
try {
|
|
|
|
|
const state = {
|
|
|
|
|
features: iml.dataset.features,
|
|
|
|
|
labels: iml.dataset.labels,
|
|
|
|
|
};
|
|
|
|
|
localStorage.setItem('nisps-playground', JSON.stringify(state));
|
|
|
|
|
} catch (e) { /* ignore */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadState() {
|
|
|
|
|
try {
|
|
|
|
|
const data = JSON.parse(localStorage.getItem('nisps-playground'));
|
|
|
|
|
if (data && data.features && data.features.length > 0) {
|
|
|
|
|
for (let i = 0; i < data.features.length; i++) {
|
feat(playground): expand C15 param map from 20 to 126 synth parameters
Control all sonically meaningful C15 parameters through NISPS ML outputs.
Excludes hardware routing, macros, scale/tuning, key tracking, velocity,
envelope mod depths, discrete switches, and dangerous volume/pitch params.
- Expand param-map.js with 126 curated params grouped by synthesis section
- Widen MLP architecture from [3,10,10,14,20] to [3,32,48,64,126]
- Add ParamDisplay.rebuild() for mode-dependent bar count (20 visual, 126 synth)
- Add scrollable compact layout for synth mode param bars
- Pad visual presets and old saves to 126 outputs for backward compatibility
2026-03-21 21:58:48 +01:00
|
|
|
// Pad old saves (20 outputs) to current N_OUTPUTS with 0.5 defaults
|
|
|
|
|
const labels = data.labels[i];
|
|
|
|
|
if (labels.length < N_OUTPUTS) {
|
|
|
|
|
while (labels.length < N_OUTPUTS) labels.push(0.5);
|
|
|
|
|
}
|
|
|
|
|
iml.addExample(data.features[i], labels);
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
2026-02-11 16:44:33 +01:00
|
|
|
trainModel();
|
2026-02-11 13:17:17 +01:00
|
|
|
const outputs = iml.getOutputs();
|
feat(playground): add C15 synth mode with NISPS-controlled parameters and arpeggiator
Adds a secondary Synth mode (toggled via collapsible left side panel)
where the NISPS ML engine's 20 outputs control curated C15 synthesizer
parameters (oscillator PM, shapers, filters, reverb, echo, flanger,
output mixer) through a WASM AudioWorklet bridge.
Includes a chord progression arpeggiator with controls for tempo (BPM),
octave range, octave offset, and 4 selectable progressions. The C15
engine runs in an AudioWorklet with lock-free SharedArrayBuffer ring
buffer communication.
New files: c15/ (WASM assets), js/synth/ (bridge, arpeggiator, param map),
serve-coop.py (COOP/COEP headers for SharedArrayBuffer support).
2026-03-21 21:26:37 +01:00
|
|
|
routeOutputs(outputs);
|
2026-02-11 13:17:17 +01:00
|
|
|
paramDisplay.update(outputs);
|
|
|
|
|
controls.updateStatus(iml.exampleCount, iml.lastLoss, noiseLevel);
|
2026-02-11 16:44:33 +01:00
|
|
|
controls.updateLossPlot(iml.lossHistory);
|
|
|
|
|
refreshDashboard();
|
2026-02-11 13:17:17 +01:00
|
|
|
}
|
|
|
|
|
} catch (e) { /* ignore */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearState() {
|
|
|
|
|
try { localStorage.removeItem('nisps-playground'); } catch (e) { /* ignore */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto-save periodically
|
|
|
|
|
setInterval(saveState, 10000);
|
|
|
|
|
|
|
|
|
|
// Visual feedback flash
|
|
|
|
|
function flash(id) {
|
|
|
|
|
const el = document.getElementById(id);
|
|
|
|
|
if (!el) return;
|
|
|
|
|
el.classList.add('flash');
|
|
|
|
|
setTimeout(() => el.classList.remove('flash'), 200);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 16:44:33 +01:00
|
|
|
function trainModel() {
|
|
|
|
|
let lastPlotUpdate = 0;
|
|
|
|
|
const loss = iml.train({
|
|
|
|
|
onIteration: (iter, iterLoss) => {
|
|
|
|
|
if (iter - lastPlotUpdate < 8) return;
|
|
|
|
|
lastPlotUpdate = iter;
|
|
|
|
|
controls.updateLossPlot([...iml.lossHistory, iterLoss]);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
return loss;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function refreshDashboard() {
|
|
|
|
|
const outputs = iml.getOutputs();
|
|
|
|
|
let mean = 0;
|
|
|
|
|
for (let i = 0; i < outputs.length; i++) mean += outputs[i];
|
|
|
|
|
mean /= Math.max(outputs.length, 1);
|
|
|
|
|
|
|
|
|
|
let variance = 0;
|
|
|
|
|
for (let i = 0; i < outputs.length; i++) {
|
|
|
|
|
const diff = outputs[i] - mean;
|
|
|
|
|
variance += diff * diff;
|
|
|
|
|
}
|
|
|
|
|
variance /= Math.max(outputs.length, 1);
|
|
|
|
|
|
|
|
|
|
controls.updateMetrics({
|
|
|
|
|
mode: learningMode,
|
|
|
|
|
joystickX: joystick.x,
|
|
|
|
|
joystickY: joystick.y,
|
|
|
|
|
outputMean: mean,
|
|
|
|
|
outputSpread: Math.sqrt(variance),
|
|
|
|
|
bestLoss: iml.bestLoss,
|
|
|
|
|
totalTrainingIterations: iml.totalTrainingIterations,
|
|
|
|
|
gamepadConnected,
|
2026-02-11 16:56:19 +01:00
|
|
|
followMode,
|
2026-02-11 16:44:33 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pollGamepad() {
|
|
|
|
|
if (!navigator.getGamepads) return;
|
|
|
|
|
const gamepads = navigator.getGamepads();
|
|
|
|
|
let gp = null;
|
|
|
|
|
|
|
|
|
|
if (gamepadIndex >= 0 && gamepads[gamepadIndex] && gamepads[gamepadIndex].connected) {
|
|
|
|
|
gp = gamepads[gamepadIndex];
|
|
|
|
|
} else {
|
|
|
|
|
gamepadIndex = -1;
|
|
|
|
|
for (let i = 0; i < gamepads.length; i++) {
|
|
|
|
|
if (gamepads[i] && gamepads[i].connected) {
|
|
|
|
|
gp = gamepads[i];
|
|
|
|
|
gamepadIndex = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const wasConnected = gamepadConnected;
|
|
|
|
|
gamepadConnected = !!gp;
|
|
|
|
|
if (wasConnected !== gamepadConnected) refreshDashboard();
|
|
|
|
|
if (!gp) {
|
|
|
|
|
gamepadButtonsPrev = [];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deadzone = 0.08;
|
|
|
|
|
const rawX = gp.axes[0] || 0;
|
|
|
|
|
const rawY = gp.axes[1] || 0;
|
|
|
|
|
const axisX = Math.abs(rawX) < deadzone ? 0 : rawX;
|
|
|
|
|
const axisY = Math.abs(rawY) < deadzone ? 0 : rawY;
|
|
|
|
|
const mappedX = (axisX + 1) * 0.5;
|
|
|
|
|
const mappedY = (axisY + 1) * 0.5;
|
|
|
|
|
|
|
|
|
|
const moved = Math.abs(mappedX - gamepadLastAxes[0]) > 0.002 || Math.abs(mappedY - gamepadLastAxes[1]) > 0.002;
|
|
|
|
|
if (moved && paramDisplay.activeBar < 0) {
|
|
|
|
|
gamepadLastAxes = [mappedX, mappedY];
|
|
|
|
|
joystick.setPosition(mappedX, mappedY, { emit: true, touching: true });
|
|
|
|
|
} else if (!moved && joystick.touching) {
|
|
|
|
|
joystick.setPosition(joystick.x, joystick.y, { emit: false, touching: false });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Standard gamepad mapping: LB=4, RB=5
|
|
|
|
|
const lbPressed = !!gp.buttons[4]?.pressed;
|
|
|
|
|
const rbPressed = !!gp.buttons[5]?.pressed;
|
|
|
|
|
const lbPrev = !!gamepadButtonsPrev[4];
|
|
|
|
|
const rbPrev = !!gamepadButtonsPrev[5];
|
|
|
|
|
|
|
|
|
|
if (learningMode === 'rl') {
|
|
|
|
|
if (rbPressed && !rbPrev) onThumbsUp();
|
|
|
|
|
if (lbPressed && !lbPrev) onThumbsDown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gamepadButtonsPrev[4] = lbPressed;
|
|
|
|
|
gamepadButtonsPrev[5] = rbPressed;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 13:17:17 +01:00
|
|
|
// --- Start ---
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
init();
|
|
|
|
|
// Start in examples mode with draggable params
|
|
|
|
|
paramDisplay.setDraggable(true);
|
|
|
|
|
});
|