memlnaut-nisps/playground/index.html
w1n5t0n 6f6a334529 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 22:26:37 +02:00

153 lines
6.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<title>NISPS Playground</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="app">
<!-- Side Panel (collapsible) -->
<div class="side-panel" id="side-panel">
<div class="side-panel-header">
<span class="side-panel-title">Mode</span>
<button class="side-panel-close" id="side-panel-close">&times;</button>
</div>
<div class="side-panel-tabs">
<button class="sp-tab active" data-mode="visual">Visual</button>
<button class="sp-tab" data-mode="synth">Synth</button>
</div>
<!-- Synth controls (shown when synth mode active) -->
<div class="synth-controls hidden" id="synth-controls">
<div class="synth-section">
<label class="synth-label">Engine</label>
<button class="btn btn-primary synth-start-btn" id="synth-start">Start Audio</button>
<div class="synth-status" id="synth-status">Stopped</div>
</div>
<div class="synth-section">
<label class="synth-label">Volume</label>
<input type="range" class="synth-slider" id="synth-volume" min="0" max="1" step="0.01" value="0.5">
</div>
<div class="synth-section">
<label class="synth-label">Arpeggiator</label>
<button class="btn synth-arp-btn" id="arp-toggle">Play</button>
</div>
<div class="synth-section">
<label class="synth-label">Progression</label>
<select class="synth-select" id="arp-progression">
<option value="I-vi-IV-V">I-vi-IV-V</option>
<option value="I-IV-vi-V">I-IV-vi-V</option>
<option value="i-VI-III-VII">i-VI-III-VII</option>
<option value="I-V-vi-IV">I-V-vi-IV</option>
</select>
</div>
<div class="synth-section">
<label class="synth-label">Tempo <span id="tempo-val">120</span> BPM</label>
<input type="range" class="synth-slider" id="arp-tempo" min="40" max="240" step="1" value="120">
</div>
<div class="synth-section">
<label class="synth-label">Octaves <span id="octaves-val">2</span></label>
<input type="range" class="synth-slider" id="arp-octaves" min="1" max="5" step="1" value="2">
</div>
<div class="synth-section">
<label class="synth-label">Oct Offset <span id="offset-val">0</span></label>
<input type="range" class="synth-slider" id="arp-offset" min="-2" max="3" step="1" value="0">
</div>
</div>
<!-- Visual mode info (shown when visual mode active) -->
<div class="visual-info" id="visual-info">
<p class="sp-hint">Move the joystick and train the neural network to map positions to visual parameters.</p>
</div>
</div>
<!-- Side panel toggle tab (always visible) -->
<button class="side-panel-toggle" id="side-panel-toggle" title="Open mode panel">&#9776;</button>
<!-- Header -->
<div class="header">
<h1>NISPS Playground</h1>
<div class="header-right">
<span class="mode-badge" id="mode-badge">Visual</span>
<button id="help-btn" title="Help">?</button>
</div>
</div>
<!-- Visual output -->
<div class="visual-container">
<canvas id="visual-canvas"></canvas>
<div class="presets" id="presets-visual">
<button class="preset-pill" onclick="loadPreset('calm-to-chaotic')">Calm/Chaos</button>
<button class="preset-pill" onclick="loadPreset('rainbow-sweep')">Rainbow</button>
<button class="preset-pill" onclick="loadPreset('vortex')">Vortex</button>
</div>
<button id="expand-visual-btn" class="expand-visual-btn" type="button" title="Expand visual area" aria-pressed="false">Expand</button>
</div>
<!-- Parameter bars -->
<div class="param-container" id="param-display"></div>
<!-- Joystick + Controls -->
<div class="controls-area">
<div id="joystick-container"></div>
<div id="controls-container"></div>
</div>
<!-- Side panel backdrop -->
<div class="side-panel-backdrop hidden" id="side-panel-backdrop"></div>
<!-- Help overlay -->
<div class="help-overlay hidden" id="help-overlay">
<div class="help-content" onclick="event.stopPropagation()">
<h2>NISPS Playground</h2>
<p>Train a neural network to map joystick positions to output parameters using interactive machine learning.</p>
<h3>Modes</h3>
<p>Open the side panel (&#9776;) to switch between <strong>Visual</strong> and <strong>Synth</strong> modes.</p>
<h3>Visual Mode</h3>
<p>20 outputs control a flow-field particle system.</p>
<h3>Synth Mode</h3>
<p>20 outputs control a C15 synthesizer engine. Use the arpeggiator for automatic note playback.</p>
<h3>Examples Mode</h3>
<ol>
<li>Move the joystick to a position</li>
<li>Drag the parameter bars to set desired output</li>
<li>Press <strong>Add Example</strong> to save this mapping</li>
<li>Repeat for different joystick positions</li>
<li>Press <strong>Train</strong> &mdash; the network learns to interpolate between your examples</li>
</ol>
<h3>RL Feedback Mode</h3>
<ol>
<li>Move the joystick &mdash; the network produces outputs</li>
<li>See something you like? Press <strong>+</strong> (thumbs up)</li>
<li>Don't like it? Press <strong>&minus;</strong> (thumbs down) to explore more</li>
<li>Double-click the joystick to toggle <strong>Follow</strong> mode</li>
</ol>
<h3>Tips</h3>
<p>Your training data auto-saves to your browser. Gamepad supported (LB/RB for RL feedback).</p>
<p style="margin-top: 16px; text-align: center; color: #444;">Tap anywhere outside to close</p>
</div>
</div>
</div>
<script type="module" src="js/app.js"></script>
</body>
</html>