2026-03-21 23:57:58 +01:00
<!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 Workbench< / title >
2026-03-22 00:36:00 +01:00
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin >
< link href = "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel = "stylesheet" >
2026-03-21 23:57:58 +01:00
< link rel = "stylesheet" href = "css/b-workbench.css" >
< / head >
< body >
< div class = "workbench" >
<!-- Header -->
< header class = "wb-header" >
< div class = "wb-header-left" >
< a href = "designs.html" class = "wb-back-btn" title = "Back" >
< svg width = "16" height = "16" viewBox = "0 0 16 16" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" > < path d = "M10 2L4 8l6 6" / > < / svg >
< / a >
< h1 class = "wb-title" > NISPS < span class = "wb-title-accent" > Workbench< / span > < / h1 >
< / div >
< div class = "wb-header-right" >
< span class = "wb-mode-badge" id = "mode-badge" > Visual< / span >
< button class = "wb-help-btn" id = "help-btn" title = "Help" > ?< / button >
< / div >
< / header >
<!-- Main grid -->
< div class = "wb-grid" >
<!-- Left column: Canvas -->
< section class = "wb-panel wb-canvas-panel" >
< div class = "wb-panel-header" >
< span class = "wb-panel-title" > Flow Field< / span >
< div class = "presets" id = "presets-visual" >
< button class = "preset-pill" data-preset = "calm-to-chaotic" > Calm/Chaos< / button >
< button class = "preset-pill" data-preset = "rainbow-sweep" > Rainbow< / button >
< button class = "preset-pill" data-preset = "vortex" > Vortex< / button >
< / div >
< / div >
< div class = "wb-canvas-wrap" >
< canvas id = "visual-canvas" > < / canvas >
< canvas id = "synth-vis-canvas" class = "hidden" > < / canvas >
< / div >
< / section >
<!-- Right column: Input Space (merged joystick + mapping) -->
< section class = "wb-panel wb-mapping-panel" >
< div class = "wb-panel-header" >
< span class = "wb-panel-title" > Input Space< / span >
< button class = "wb-follow-toggle-btn" id = "follow-toggle-btn" > Follow< / button >
< / div >
< div class = "wb-mapping-area" >
< div class = "wb-heatmap-wrap" >
< canvas id = "mapping-canvas" width = "280" height = "280" > < / canvas >
< div class = "wb-follow-badge hidden" id = "follow-badge" > FOLLOW< / div >
< div class = "wb-heatmap-label" > Drag to explore · double-click or < kbd > f< / kbd > to toggle follow< / div >
< / div >
< / div >
<!-- Constellation -->
< div class = "wb-constellation-wrap" >
< canvas id = "constellation-canvas" width = "120" height = "120" > < / canvas >
< span class = "wb-constellation-label" id = "constellation-label" > 20 parameters< / span >
< / div >
< / section >
<!-- Bottom left: Param Groups -->
< section class = "wb-panel wb-params-panel" >
< div class = "wb-panel-header" >
< span class = "wb-panel-title" > Parameters< / span >
< div class = "wb-output-toggle" >
< button class = "wb-toggle-btn active" id = "toggle-visual" data-mode = "visual" > Visual< / button >
< button class = "wb-toggle-btn" id = "toggle-synth" data-mode = "synth" > Synth< / button >
< / div >
< / div >
< div class = "wb-param-groups" id = "param-groups" > < / div >
< / section >
<!-- Bottom right: Controls + Metrics -->
< section class = "wb-panel wb-controls-panel" >
< div class = "wb-panel-header" >
< span class = "wb-panel-title" > Controls< / span >
< / div >
2026-03-22 10:01:50 +01:00
<!-- Unified controls -->
< div class = "wb-action-group" id = "controls" >
< button class = "wb-btn wb-btn-bad" id = "btn-thumbsdown" > < span class = "wb-btn-label" > − Explore< / span > < span class = "wb-key-hint" > 1< / span > < / button >
< button class = "wb-btn wb-btn-good" id = "btn-thumbsup" > < span class = "wb-btn-label" > + Good< / span > < span class = "wb-key-hint" > 2< / span > < / button >
2026-03-21 23:57:58 +01:00
< button class = "wb-btn wb-btn-primary" id = "btn-add" > Add Example< / button >
< button class = "wb-btn wb-btn-accent" id = "btn-train" > Train< / button >
< button class = "wb-btn" id = "btn-randomize" > Randomize< / button >
2026-03-22 10:01:50 +01:00
< button class = "wb-btn" id = "btn-clear-examples" > Clear Examples< / button >
< button class = "wb-btn wb-btn-danger" id = "btn-clear" > Clear All< / button >
2026-03-21 23:57:58 +01:00
< / div >
<!-- Synth controls (shown in synth mode) -->
< div class = "wb-synth-controls hidden" id = "synth-controls" >
< div class = "wb-synth-row" >
< button class = "wb-btn wb-btn-primary" id = "synth-start" > Start Audio< / button >
< span class = "wb-synth-status" id = "synth-status" > Stopped< / span >
< / div >
< div class = "wb-synth-row" >
< label > Volume< / label >
< input type = "range" id = "synth-volume" min = "0" max = "1" step = "0.01" value = "0.5" >
< / div >
< div class = "wb-synth-row" >
< button class = "wb-btn" id = "arp-toggle" > Arp: Play< / button >
< 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 = "wb-synth-row" >
< label > Tempo < span id = "tempo-val" > 120< / span > < / label >
< input type = "range" id = "arp-tempo" min = "40" max = "240" step = "1" value = "120" >
< / div >
< div class = "wb-synth-row" >
< label > Octaves < span id = "octaves-val" > 2< / span > < / label >
< input type = "range" id = "arp-octaves" min = "1" max = "5" step = "1" value = "2" >
< / div >
< div class = "wb-synth-row" >
< label > Offset < span id = "offset-val" > 0< / span > < / label >
< input type = "range" id = "arp-offset" min = "-2" max = "3" step = "1" value = "0" >
< / div >
2026-03-22 10:01:50 +01:00
< div class = "wb-synth-row" id = "midi-row" style = "display:none" >
< label > MIDI< / label >
< button class = "wb-btn" id = "midi-toggle" > Enable< / button >
< select id = "midi-select" style = "flex:1;min-width:0" > < / select >
< / div >
< div class = "wb-synth-row" id = "midi-status-row" style = "display:none" >
< span class = "wb-synth-status" id = "midi-status" > < / span >
< / div >
2026-03-21 23:57:58 +01:00
< / div >
<!-- Loss plot -->
< div class = "wb-loss-wrap" >
< div class = "wb-loss-label" > Loss< / div >
< canvas id = "loss-canvas" width = "280" height = "60" > < / canvas >
< / div >
<!-- Status metrics -->
< div class = "wb-metrics" id = "metrics" >
< div class = "wb-metric" > < span class = "wb-metric-label" > Examples< / span > < span class = "wb-metric-value" id = "metric-examples" > 0< / span > < / div >
< div class = "wb-metric" > < span class = "wb-metric-label" > Loss< / span > < span class = "wb-metric-value" id = "metric-loss" > — < / span > < / div >
< div class = "wb-metric" > < span class = "wb-metric-label" > Noise< / span > < span class = "wb-metric-value" id = "metric-noise" > 0.050< / span > < / div >
< div class = "wb-metric" > < span class = "wb-metric-label" > Joy X,Y< / span > < span class = "wb-metric-value" id = "metric-joy" > 0.50, 0.50< / span > < / div >
2026-03-22 10:01:50 +01:00
< div class = "wb-metric" > < span class = "wb-metric-value" id = "gamepad-status" style = "color: #ff6a00; font-size: 0.7rem;" > < / span > < / div >
2026-03-21 23:57:58 +01:00
< / div >
< / section >
< / div > <!-- /wb - grid -->
<!-- Toast notification -->
< div class = "wb-toast hidden" id = "toast" > < / div >
<!-- Help overlay -->
< div class = "wb-help-overlay hidden" id = "help-overlay" >
< div class = "wb-help-content" onclick = "event.stopPropagation()" >
< h2 > NISPS Workbench< / h2 >
< p > Train a neural network to map 2D joystick positions to output parameters using interactive machine learning.< / p >
< h3 > Input Space< / h3 >
< p > The heatmap shows what the network has learned across the input space. Bright dots show training examples. Drag anywhere on the map to move the input position. Double-click or press Follow to toggle follow mode.< / p >
2026-03-22 10:01:50 +01:00
< h3 > Learning< / h3 >
< p > Both approaches can be freely mixed:< / p >
< ul >
< li > < strong > RL Feedback< / strong > : Press < strong > + Good< / strong > to reinforce the current mapping, or < strong > − Explore< / strong > to add noise and try variations.< / li >
< li > < strong > Supervised< / strong > : Drag param bars to set desired outputs, then < strong > Add Example< / strong > and < strong > Train< / strong > .< / li >
< / ul >
2026-03-21 23:57:58 +01:00
< h3 > Output Modes< / h3 >
< p > < strong > Visual< / strong > : 20 params control the flow-field particle system. < strong > Synth< / strong > : 126 params control a C15 synthesizer engine.< / p >
< p style = "margin-top:16px;text-align:center;color:#555;" > Tap outside to close< / p >
< / div >
< / div >
< / div >
< script type = "module" src = "js/b-app.js" > < / script >
< / body >
< / html >