diff --git a/playground/a-immersive.html b/playground/a-immersive.html index 1877987..ae3f78e 100644 --- a/playground/a-immersive.html +++ b/playground/a-immersive.html @@ -6,6 +6,9 @@ NISPS Immersive + + + @@ -49,8 +52,8 @@
- - + +
diff --git a/playground/b-workbench.html b/playground/b-workbench.html index f599850..a19d595 100644 --- a/playground/b-workbench.html +++ b/playground/b-workbench.html @@ -6,6 +6,9 @@ NISPS Workbench + + + @@ -98,10 +101,8 @@
- - 1 - - 2 + +
diff --git a/playground/c-journey.html b/playground/c-journey.html index 6f71fa2..73aa72b 100644 --- a/playground/c-journey.html +++ b/playground/c-journey.html @@ -6,6 +6,9 @@ NISPS Journey + + + @@ -122,12 +125,8 @@

Like what you see? 👍 Want something different? 👎

- - -
-
- 1 - 2 + +
diff --git a/playground/css/a-immersive.css b/playground/css/a-immersive.css index 697d68b..af0045a 100644 --- a/playground/css/a-immersive.css +++ b/playground/css/a-immersive.css @@ -12,8 +12,8 @@ --glass-bg: rgba(13, 13, 13, 0.65); --glass-border: rgba(255, 255, 255, 0.08); --glass-blur: 16px; - --accent: #00ff88; - --accent-dim: rgba(0, 255, 136, 0.25); + --accent: #ff6a00; + --accent-dim: rgba(255, 106, 0, 0.25); --danger: #ff4466; --text: #e0e0e0; --text-dim: #888; @@ -28,8 +28,8 @@ html, body { overflow: hidden; background: #0d0d0d; color: var(--text); - font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', system-ui, sans-serif; - font-size: 14px; + font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + font-size: 13px; touch-action: none; user-select: none; -webkit-user-select: none; @@ -47,7 +47,7 @@ html, body { align-items: center; justify-content: center; padding: 0; - border-radius: 18px; + border-radius: 6px; background: rgba(13, 13, 13, 0.65); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); @@ -55,11 +55,12 @@ html, body { color: var(--text); text-decoration: none; line-height: 1; - transition: background 0.15s; + transition: all 0.15s; } -.back-btn:active { - background: rgba(255, 255, 255, 0.12); +.back-btn:hover, .back-btn:active { + border-color: var(--accent); + color: var(--accent); } /* ---- Fullscreen canvas ---- */ @@ -191,7 +192,7 @@ html, body { inset: -8px; border-radius: 50%; pointer-events: none; - background: radial-gradient(circle, rgba(0,255,136,0.06) 0%, transparent 70%); + background: radial-gradient(circle, rgba(255,106,0,0.06) 0%, transparent 70%); z-index: -1; } @@ -223,8 +224,8 @@ html, body { transform: translateX(-50%); padding: 2px 10px; border-radius: 10px; - background: rgba(0, 255, 136, 0.15); - border: 1px solid rgba(0, 255, 136, 0.4); + background: rgba(255, 106, 0, 0.15); + border: 1px solid rgba(255, 106, 0, 0.4); color: var(--accent); font-size: 10px; font-weight: 700; @@ -239,34 +240,27 @@ html, body { /* Joystick follow-mode pulsing border on canvas */ .joystick-container.follow-active #joy-map { - box-shadow: 0 0 0 2px rgba(0, 255, 136, 0.5); + box-shadow: 0 0 0 2px rgba(255, 106, 0, 0.5); animation: follow-pulse 1.5s ease-in-out infinite; } @keyframes follow-pulse { - 0%, 100% { box-shadow: 0 0 0 2px rgba(0, 255, 136, 0.3); } - 50% { box-shadow: 0 0 12px 3px rgba(0, 255, 136, 0.5); } + 0%, 100% { box-shadow: 0 0 0 2px rgba(255, 106, 0, 0.3); } + 50% { box-shadow: 0 0 12px 3px rgba(255, 106, 0, 0.5); } } /* ---- RL floating buttons ---- */ .rl-buttons { position: fixed; - bottom: 170px; + bottom: 92px; /* just above 88px collapsed sheet */ left: 50%; transform: translateX(-50%); display: flex; - gap: 24px; - z-index: 25; + gap: 32px; + z-index: 45; transition: opacity 0.3s; } -.key-num { - font-size: 10px; - opacity: 0.5; - margin-left: 4px; - font-family: monospace; -} - .rl-buttons.hidden { opacity: 0; pointer-events: none; @@ -281,13 +275,28 @@ html, body { backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); color: var(--text); - font-size: 28px; - font-weight: 700; cursor: pointer; transition: all 0.15s; display: flex; + flex-direction: column; align-items: center; justify-content: center; + padding-top: 2px; + gap: 0; +} + +.rl-icon { + font-size: 28px; + font-weight: 700; + line-height: 1; +} + +.key-num { + font-size: 12px; + color: rgba(255, 255, 255, 0.5); + font-family: inherit; + line-height: 1; + margin-top: -1px; } .rl-btn:active { @@ -295,23 +304,25 @@ html, body { } .rl-up { - border-color: rgba(0, 255, 136, 0.3); - color: var(--accent); + border-color: rgba(0, 200, 160, 0.4); + color: #00c8a0; + background: rgba(0, 200, 160, 0.08); } .rl-up:active { - background: rgba(0, 255, 136, 0.15); - box-shadow: 0 0 20px rgba(0, 255, 136, 0.3); + background: rgba(0, 200, 160, 0.2); + box-shadow: 0 0 20px rgba(0, 200, 160, 0.3); } .rl-down { - border-color: rgba(255, 68, 102, 0.3); - color: var(--danger); + border-color: rgba(220, 60, 20, 0.4); + color: #dc3c14; + background: rgba(220, 60, 20, 0.08); } .rl-down:active { - background: rgba(255, 68, 102, 0.15); - box-shadow: 0 0 20px rgba(255, 68, 102, 0.3); + background: rgba(220, 60, 20, 0.2); + box-shadow: 0 0 20px rgba(220, 60, 20, 0.3); } .rl-btn.flash { @@ -342,7 +353,7 @@ html, body { /* Float button (randomize in floating bar) */ .float-btn { padding: 4px 12px; - border-radius: 14px; + border-radius: 6px; border: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.04); color: var(--text-dim); @@ -352,13 +363,13 @@ html, body { } .float-btn:active { - background: rgba(0, 255, 136, 0.15); + background: rgba(255, 106, 0, 0.15); color: var(--accent); } .follow-pill { padding: 4px 12px; - border-radius: 14px; + border-radius: 6px; border: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.04); color: var(--text-dim); @@ -368,8 +379,8 @@ html, body { } .follow-pill.active { - background: rgba(0, 255, 136, 0.15); - border-color: rgba(0, 255, 136, 0.4); + background: rgba(255, 106, 0, 0.15); + border-color: rgba(255, 106, 0, 0.4); color: var(--accent); } @@ -451,7 +462,7 @@ html, body { /* Pill toggle */ .pill-toggle { display: inline-flex; - border-radius: 20px; + border-radius: 6px; overflow: hidden; border: 1px solid var(--glass-border); background: rgba(255, 255, 255, 0.04); @@ -484,7 +495,7 @@ html, body { .action-btn { padding: 8px 16px; - border-radius: 20px; + border-radius: 6px; border: 1px solid var(--glass-border); background: rgba(255, 255, 255, 0.06); color: var(--text); @@ -499,13 +510,13 @@ html, body { } .action-btn.accent { - border-color: rgba(0, 255, 136, 0.3); + border-color: rgba(255, 106, 0, 0.3); color: var(--accent); - background: rgba(0, 255, 136, 0.08); + background: rgba(255, 106, 0, 0.08); } .action-btn.accent:active { - background: rgba(0, 255, 136, 0.2); + background: rgba(255, 106, 0, 0.2); } .action-btn.dim { @@ -518,7 +529,7 @@ html, body { } @keyframes btn-flash { - 0% { background: rgba(0, 255, 136, 0.3); } + 0% { background: rgba(255, 106, 0, 0.3); } 100% { background: rgba(255, 255, 255, 0.06); } } @@ -542,7 +553,7 @@ html, body { .preset-chip { padding: 4px 12px; - border-radius: 14px; + border-radius: 6px; border: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.04); color: var(--text-dim); @@ -553,7 +564,7 @@ html, body { } .preset-chip:active { - background: rgba(0, 255, 136, 0.15); + background: rgba(255, 106, 0, 0.15); color: var(--accent); } @@ -709,9 +720,9 @@ html, body { } .play-btn.playing { - border-color: rgba(0, 255, 136, 0.4); + border-color: rgba(255, 106, 0, 0.4); color: var(--accent); - background: rgba(0, 255, 136, 0.12); + background: rgba(255, 106, 0, 0.12); } .play-drawer { diff --git a/playground/css/b-workbench.css b/playground/css/b-workbench.css index 3c90bae..d549a1f 100644 --- a/playground/css/b-workbench.css +++ b/playground/css/b-workbench.css @@ -16,12 +16,12 @@ --text: #e0e0e0; --text-dim: #888; --text-muted: #555; - --accent-visual: #00ff88; - --accent-synth: #ff6b35; + --accent-visual: #ff6a00; + --accent-synth: #ff8c00; --accent: var(--accent-visual); --danger: #ff4444; - --good: #00cc66; - --bad: #ff6644; + --good: #ff8c00; + --bad: #ff4444; --radius: 8px; --radius-sm: 4px; --transition: 0.2s ease; @@ -43,7 +43,7 @@ body { background: var(--bg); color: var(--text); - font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace; + font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; font-size: 13px; line-height: 1.4; overflow-x: hidden; @@ -575,29 +575,48 @@ body.synth-mode { .wb-btn-danger:hover { background: var(--danger); color: var(--bg); } .wb-btn-good { - background: var(--good); - color: var(--bg); - border-color: var(--good); + background: rgba(0, 200, 160, 0.12); + color: #00c8a0; + border-color: rgba(0, 200, 160, 0.4); font-weight: 600; } +.wb-btn-good:hover { + background: rgba(0, 200, 160, 0.25); +} .wb-btn-bad { - background: var(--bad); - color: var(--bg); - border-color: var(--bad); + background: rgba(220, 60, 20, 0.12); + color: #dc3c14; + border-color: rgba(220, 60, 20, 0.4); font-weight: 600; } +.wb-btn-bad:hover { + background: rgba(220, 60, 20, 0.25); +} .wb-btn.flash { filter: brightness(1.8); } +.wb-btn-bad, +.wb-btn-good { + display: flex; + flex-direction: column; + align-items: center; + gap: 1px; + padding-top: 4px; + padding-bottom: 3px; +} + +.wb-btn-label { + font-weight: 600; +} + .wb-key-hint { - font-size: 10px; - font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace; - color: #555; + font-size: 11px; + font-family: inherit; + color: rgba(255, 255, 255, 0.5); text-align: center; line-height: 1; - flex-shrink: 0; } /* Synth controls */ diff --git a/playground/css/c-journey.css b/playground/css/c-journey.css index 3c98870..701dd2f 100644 --- a/playground/css/c-journey.css +++ b/playground/css/c-journey.css @@ -13,16 +13,16 @@ --glass-bg: rgba(13, 13, 13, 0.6); --glass-border: rgba(255, 255, 255, 0.08); --glass-blur: 16px; - --accent: #00ff88; - --accent-dim: rgba(0, 255, 136, 0.2); - --accent-glow: rgba(0, 255, 136, 0.4); + --accent: #ff6a00; + --accent-dim: rgba(255, 106, 0, 0.2); + --accent-glow: rgba(255, 106, 0, 0.4); --danger: #ff4466; --text: #e0e0e0; --text-dim: rgba(255, 255, 255, 0.45); --text-muted: rgba(255, 255, 255, 0.25); --radius: 12px; --radius-sm: 8px; - --radius-pill: 20px; + --radius-pill: 6px; --transition: 400ms cubic-bezier(0.22, 1, 0.36, 1); --transition-fast: 200ms ease-out; --safe-bottom: env(safe-area-inset-bottom, 0px); @@ -34,8 +34,8 @@ html, body { overflow: hidden; background: var(--bg); color: var(--text); - font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', system-ui, sans-serif; - font-size: 14px; + font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + font-size: 13px; touch-action: none; user-select: none; -webkit-user-select: none; @@ -292,7 +292,7 @@ html, body { } .cta-pill.primary:hover, .cta-pill.primary:active { - background: rgba(0, 255, 136, 0.3); + background: rgba(255, 106, 0, 0.3); } /* ============================================================ @@ -565,7 +565,7 @@ html, body { .rl-buttons { display: flex; justify-content: center; - gap: 40px; + gap: 48px; margin-top: 16px; } @@ -577,22 +577,41 @@ html, body { background: var(--glass-bg); backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); - font-size: 28px; cursor: pointer; transition: all var(--transition-fast); display: flex; + flex-direction: column; align-items: center; justify-content: center; + padding-top: 2px; + gap: 0; +} + +.rl-icon { + font-size: 28px; + line-height: 1; +} + +.rl-btn.up { + border-color: rgba(0, 200, 160, 0.4); + color: #00c8a0; + background: rgba(0, 200, 160, 0.08); } .rl-btn.up:hover, .rl-btn.up:active { - border-color: var(--accent); - background: var(--accent-dim); + background: rgba(0, 200, 160, 0.2); + box-shadow: 0 0 20px rgba(0, 200, 160, 0.3); +} + +.rl-btn.down { + border-color: rgba(220, 60, 20, 0.4); + color: #dc3c14; + background: rgba(220, 60, 20, 0.08); } .rl-btn.down:hover, .rl-btn.down:active { - border-color: var(--danger); - background: rgba(255, 68, 102, 0.15); + background: rgba(220, 60, 20, 0.2); + box-shadow: 0 0 20px rgba(220, 60, 20, 0.3); } /* Synth section */ @@ -680,7 +699,7 @@ html, body { } .done-btn:hover, .done-btn:active { - background: #33ffaa; + background: #ff8833; } /* ============================================================ @@ -710,17 +729,17 @@ html, body { } .action-btn.primary:hover { - background: rgba(0, 255, 136, 0.3); + background: rgba(255, 106, 0, 0.3); } .action-btn.accent { - background: rgba(0, 255, 136, 0.12); - border-color: rgba(0, 255, 136, 0.3); + background: rgba(255, 106, 0, 0.12); + border-color: rgba(255, 106, 0, 0.3); color: var(--accent); } .action-btn.accent:hover { - background: rgba(0, 255, 136, 0.2); + background: rgba(255, 106, 0, 0.2); } .action-btn.dim { @@ -882,7 +901,7 @@ html, body { backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); border: 1px solid var(--glass-border); - border-radius: 50%; + border-radius: 6px; color: var(--text); text-decoration: none; font-size: 18px; @@ -891,26 +910,20 @@ html, body { } .back-btn:hover { - border-color: var(--text-dim); - background: rgba(255, 255, 255, 0.08); + border-color: var(--accent); + color: var(--accent); } /* ============================================================ RL key hints ============================================================ */ -.rl-key-hints { - display: flex; - justify-content: center; - gap: 40px; - margin-top: 6px; -} - .key-hint { - font-family: 'SF Mono', 'Fira Code', monospace; - font-size: 10px; - color: #555; - width: 64px; + font-family: inherit; + font-size: 12px; + color: rgba(255, 255, 255, 0.5); text-align: center; + line-height: 1; + margin-top: -1px; } /* ============================================================ diff --git a/playground/css/style.css b/playground/css/style.css index 182f72d..bacf701 100644 --- a/playground/css/style.css +++ b/playground/css/style.css @@ -6,12 +6,12 @@ --bg-elevated: #252525; --text: #ccc; --text-dim: #666; - --accent: #00ff88; - --accent-dim: #00cc6a; + --accent: #ff6a00; + --accent-dim: #cc5500; --danger: #ff3366; - --good: #00ff88; - --bad: #ff6644; - --synth-accent: #ff6b35; + --good: #ff8c00; + --bad: #ff4444; + --synth-accent: #ff8c00; } * { box-sizing: border-box; margin: 0; padding: 0; } @@ -19,8 +19,8 @@ html, body { background: var(--bg); color: var(--text); - font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', system-ui, sans-serif; - font-size: 14px; + font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + font-size: 13px; height: 100%; overflow: hidden; touch-action: manipulation; diff --git a/playground/designs.html b/playground/designs.html index 58c0ffa..02a9d7f 100644 --- a/playground/designs.html +++ b/playground/designs.html @@ -5,79 +5,115 @@ NISPS Playground
-

NISPS Playground

-

Neural Interactive Shaping of Parameter Spaces

- - +
+

NISPS Playground + Neural Interactive Shaping of Parameter Spaces +

+

Map a 2D joystick to 126 synthesizer parameters through a neural network. Two learning modes: show examples of what you want, or give thumbs up/down feedback. The network learns to interpolate the space between them.

+
- + - + - +
diff --git a/playground/index.html b/playground/index.html index f30410d..02a9d7f 100644 --- a/playground/index.html +++ b/playground/index.html @@ -2,152 +2,210 @@ - - - + NISPS Playground - + -
- - -
-
- Mode - -
- -
- - -
- - - - - -
-

Move the joystick and train the neural network to map positions to visual parameters.

-
-
- - - - - +
-

NISPS Playground

-
- Visual - -
+

NISPS Playground + Neural Interactive Shaping of Parameter Spaces +

+

Map a 2D joystick to 126 synthesizer parameters through a neural network. Two learning modes: show examples of what you want, or give thumbs up/down feedback. The network learns to interpolate the space between them.

- -
- -
- - - -
- + - - -
- - -
-
-
-
- - - - - - -
- - diff --git a/playground/js/a-app.js b/playground/js/a-app.js index bbf687e..cc2c0f5 100644 --- a/playground/js/a-app.js +++ b/playground/js/a-app.js @@ -20,7 +20,7 @@ const VISUAL_PARAM_NAMES = [ 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate', ]; const VISUAL_PARAM_COLORS = [ - '#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', + '#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d', @@ -67,6 +67,7 @@ let arpeggiator = null; let learningMode = 'rl'; let outputMode = 'visual'; let tameLevel = 0; +let spreadLevel = 0.6; let noiseLevel = 0.05; const rlExplorationDecay = 0.97; @@ -254,7 +255,7 @@ class SynthVisualizer { const dpr = window.devicePixelRatio || 1; const fontSize = 9 * dpr; ctx.save(); - ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, sans-serif`; + ctx.font = `${fontSize}px 'JetBrains Mono', monospace`; ctx.fillStyle = color + '88'; ctx.textAlign = 'center'; ctx.textBaseline = 'bottom'; @@ -360,6 +361,9 @@ function init() { // Parse ?tame URL param const urlParams = new URLSearchParams(window.location.search); tameLevel = parseFloat(urlParams.get('tame') ?? '0'); + spreadLevel = parseFloat(urlParams.get('spread') ?? '0'); + if (isNaN(spreadLevel)) spreadLevel = 0; + spreadLevel = Math.max(0, Math.min(1, spreadLevel)); // IML — fresh random weights each boot, no state restoration iml = new IML(N_INPUTS, N_OUTPUTS, [32, 48, 64], 1000, 1.0, 0.00001); @@ -557,9 +561,9 @@ function drawJoyMap() { const py = (1 - joyY) * h; // Glow - ctx.shadowColor = 'rgba(0, 255, 136, 0.6)'; + ctx.shadowColor = 'rgba(255, 106, 0, 0.6)'; ctx.shadowBlur = 12; - ctx.fillStyle = 'rgba(0, 255, 136, 0.9)'; + ctx.fillStyle = 'rgba(255, 106, 0, 0.9)'; ctx.beginPath(); ctx.arc(px, py, 8, 0, Math.PI * 2); ctx.fill(); @@ -572,7 +576,7 @@ function drawJoyMap() { ctx.fill(); // Crosshair - ctx.strokeStyle = 'rgba(0, 255, 136, 0.2)'; + ctx.strokeStyle = 'rgba(255, 106, 0, 0.2)'; ctx.lineWidth = 0.5; ctx.shadowBlur = 0; ctx.beginPath(); @@ -864,7 +868,7 @@ function onTrain() { } function onRandomize() { - iml.randomiseWeights(); + iml.randomiseWeights(spreadLevel); const outputs = iml.getOutputs(); routeOutputs(outputs); updateHeatmap(outputs); @@ -982,7 +986,7 @@ function drawLossPlot() { const maxLoss = Math.max(...history.slice(-200)) || 1; const points = history.slice(-200); - ctx.strokeStyle = 'rgba(0, 255, 136, 0.6)'; + ctx.strokeStyle = 'rgba(255, 106, 0, 0.6)'; ctx.lineWidth = 1.5; ctx.beginPath(); diff --git a/playground/js/app.js b/playground/js/app.js index 16c9516..3970176 100644 --- a/playground/js/app.js +++ b/playground/js/app.js @@ -17,7 +17,7 @@ const N_OUTPUTS = N_SYNTH_OUTPUTS; // MLP always produces full output; visual us // 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']; +const VISUAL_PARAM_COLORS = ['#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d']; // --- State --- let iml; @@ -45,9 +45,13 @@ let arpeggiator = null; // 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'); +const _urlParams = new URLSearchParams(location.search); +let tameLevel = parseFloat(_urlParams.get('tame') ?? '0.7'); if (isNaN(tameLevel)) tameLevel = 0.7; tameLevel = Math.max(0, Math.min(1, tameLevel)); +let spreadLevel = parseFloat(_urlParams.get('spread') ?? '0'); +if (isNaN(spreadLevel)) spreadLevel = 0.6; +spreadLevel = Math.max(0, Math.min(1, spreadLevel)); window.setTameLevel = (v) => { tameLevel = Math.max(0, Math.min(1, v)); console.log(`[NISPS] tame=${tameLevel}`); }; window.getTameLevel = () => tameLevel; @@ -340,7 +344,7 @@ function onTrain() { } function onRandomize() { - iml.randomiseWeights(); + iml.randomiseWeights(spreadLevel); const outputs = iml.getOutputs(); routeOutputs(outputs); paramDisplay.update(outputs); diff --git a/playground/js/b-app.js b/playground/js/b-app.js index 665ada2..ee5ee8e 100644 --- a/playground/js/b-app.js +++ b/playground/js/b-app.js @@ -20,7 +20,7 @@ const VISUAL_PARAM_NAMES = [ 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate' ]; const VISUAL_PARAM_COLORS = [ - '#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', + '#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d' @@ -91,6 +91,7 @@ function heatmapColor(t) { // --- Tame URL param --- const _urlParams = new URLSearchParams(window.location.search); const tameLevel = parseFloat(_urlParams.get('tame') ?? '0'); +const spreadLevel = Math.max(0, Math.min(1, parseFloat(_urlParams.get('spread') ?? '0') || 0)); // --- State --- let iml; @@ -402,7 +403,7 @@ class MappingView { // Current joystick position as crosshair const px = joyX * w; const py = joyY * h; - const accent = outputMode === 'synth' ? '#ff6b35' : '#00ff88'; + const accent = outputMode === 'synth' ? '#ff8c00' : '#ff6a00'; ctx.strokeStyle = accent; ctx.lineWidth = 1; @@ -498,7 +499,7 @@ function drawLossPlot(canvas, history) { for (const v of history) if (v > maxLoss) maxLoss = v; if (maxLoss === 0) maxLoss = 1; - const accent = outputMode === 'synth' ? '#ff6b35' : '#00ff88'; + const accent = outputMode === 'synth' ? '#ff8c00' : '#ff6a00'; // Draw line ctx.strokeStyle = accent; @@ -734,7 +735,7 @@ function onTrain() { } function onRandomize() { - iml.randomiseWeights(); + iml.randomiseWeights(spreadLevel); const outputs = iml.getOutputs(); routeOutputs(outputs); updateAllParamBars(outputs); diff --git a/playground/js/c-app.js b/playground/js/c-app.js index f0c2c15..6039cde 100644 --- a/playground/js/c-app.js +++ b/playground/js/c-app.js @@ -24,7 +24,7 @@ const VISUAL_PARAM_NAMES = [ 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate', ]; const VISUAL_PARAM_COLORS = [ - '#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', + '#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d', @@ -49,6 +49,7 @@ const PRESETS = [ // --- Tame URL param --- const _urlParams = new URLSearchParams(window.location.search); const tameLevel = parseFloat(_urlParams.get('tame') ?? '0'); +const spreadLevel = Math.max(0, Math.min(1, parseFloat(_urlParams.get('spread') ?? '0') || 0)); // ============================================================ // State @@ -180,7 +181,7 @@ class SynthVisualizer { // Section label const sectionWidth = barWidth * section.count; ctx.fillStyle = 'rgba(255, 255, 255, 0.25)'; - ctx.font = '9px -apple-system, sans-serif'; + ctx.font = '9px 'JetBrains Mono', monospace'; ctx.textAlign = 'center'; ctx.fillText(section.name, x + sectionWidth / 2, h - 6); @@ -410,7 +411,7 @@ function buildHeatmap() { cell.dataset.index = i; const bar = document.createElement('div'); bar.className = 'heatmap-bar'; - bar.style.backgroundColor = currentParamColors[i] || '#00ff88'; + bar.style.backgroundColor = currentParamColors[i] || '#ff6a00'; bar.style.width = '50%'; cell.appendChild(bar); container.appendChild(cell); @@ -628,7 +629,7 @@ function wireTeachActions() { document.getElementById('btn-clear')?.addEventListener('click', () => { iml.clearDataset(); - iml.randomiseWeights(); + iml.randomiseWeights(spreadLevel); selectedPreset = -1; document.querySelectorAll('.preset-thumb').forEach(b => b.classList.remove('selected')); updateTeachStatus(); @@ -637,7 +638,7 @@ function wireTeachActions() { }); document.getElementById('btn-randomize')?.addEventListener('click', () => { - iml.randomiseWeights(); + iml.randomiseWeights(spreadLevel); iml.setInput(0, joystickX); iml.setInput(1, joystickY); iml.process(); @@ -912,16 +913,16 @@ function drawJoystick() { ctx.beginPath(); ctx.arc(knobX, knobY, knobRadius, 0, TWO_PI); - ctx.fillStyle = 'rgba(0, 255, 136, 0.7)'; + ctx.fillStyle = 'rgba(255, 106, 0, 0.7)'; ctx.fill(); - ctx.strokeStyle = 'rgba(0, 255, 136, 0.4)'; + ctx.strokeStyle = 'rgba(255, 106, 0, 0.4)'; ctx.lineWidth = 2; ctx.stroke(); // Glow const glow = ctx.createRadialGradient(knobX, knobY, 0, knobX, knobY, knobRadius * 2.5); - glow.addColorStop(0, 'rgba(0, 255, 136, 0.15)'); - glow.addColorStop(1, 'rgba(0, 255, 136, 0)'); + glow.addColorStop(0, 'rgba(255, 106, 0, 0.15)'); + glow.addColorStop(1, 'rgba(255, 106, 0, 0)'); ctx.fillStyle = glow; ctx.beginPath(); ctx.arc(knobX, knobY, knobRadius * 2.5, 0, TWO_PI); @@ -999,7 +1000,7 @@ function drawMinimap() { if (!f || f.length < 2) continue; const x = f[0] * w; const y = f[1] * h; - ctx.fillStyle = 'rgba(0, 255, 136, 0.8)'; + ctx.fillStyle = 'rgba(255, 106, 0, 0.8)'; ctx.beginPath(); ctx.arc(x, y, 3, 0, TWO_PI); ctx.fill(); diff --git a/playground/js/nisps/iml.js b/playground/js/nisps/iml.js index d03509e..0e0acad 100644 --- a/playground/js/nisps/iml.js +++ b/playground/js/nisps/iml.js @@ -144,9 +144,9 @@ export class IML { this.log('Dataset cleared.'); } - randomiseWeights() { + randomiseWeights(spread = 0) { this.storedWeights = this.mlp.getWeights(); - this.mlp.drawWeights(); + this.mlp.drawWeights(spread); this.weightsRandomised = true; // Run inference to show effect diff --git a/playground/js/nisps/mlp.js b/playground/js/nisps/mlp.js index f5bc2b7..743d54a 100644 --- a/playground/js/nisps/mlp.js +++ b/playground/js/nisps/mlp.js @@ -204,13 +204,18 @@ export class MLP { } } - // DrawWeights - randomize all weights uniformly in [-1, 1] - drawWeights(scale = 1) { - for (const layer of this.layers) { - for (const node of layer.nodes) { + // DrawWeights - randomize all weights + // spread: 0 = uniform [-1,1] (polarised sigmoid outputs), 1 = Xavier-scaled (centered outputs) + drawWeights(spread = 0) { + for (let l = 0; l < this.layers.length; l++) { + const fanIn = this.layersNodes[l]; + const xavierScale = 1 / Math.sqrt(fanIn); + const scale = 1 * (1 - spread) + xavierScale * spread; + for (const node of this.layers[l].nodes) { for (let j = 0; j < node.weights.length; j++) { node.weights[j] = (Math.random() * 2 - 1) * scale; } + node.bias = 0; } } } diff --git a/playground/js/ui/controls.js b/playground/js/ui/controls.js index fe698d2..b728765 100644 --- a/playground/js/ui/controls.js +++ b/playground/js/ui/controls.js @@ -160,14 +160,14 @@ export class Controls { const plotW = width - leftPad - rightPad; const plotH = height - topPad - bottomPad; - ctx.strokeStyle = '#243523'; + ctx.strokeStyle = '#352418'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(leftPad, topPad + plotH / 2); ctx.lineTo(width - rightPad, topPad + plotH / 2); ctx.stroke(); - ctx.strokeStyle = '#00ff88'; + ctx.strokeStyle = '#ff6a00'; ctx.lineWidth = 1.6; ctx.beginPath(); for (let i = 0; i < points.length; i++) { diff --git a/playground/js/ui/joystick.js b/playground/js/ui/joystick.js index 072e973..1129540 100644 --- a/playground/js/ui/joystick.js +++ b/playground/js/ui/joystick.js @@ -129,7 +129,7 @@ export class VirtualJoystick { // Background circle ctx.beginPath(); ctx.arc(r, r, r - 4, 0, Math.PI * 2); - ctx.strokeStyle = this.followMode ? '#00cc6a' : '#333'; + ctx.strokeStyle = this.followMode ? '#cc5500' : '#333'; ctx.lineWidth = 1.5; ctx.stroke(); @@ -159,21 +159,21 @@ export class VirtualJoystick { // Glow if (this.touching) { const grad = ctx.createRadialGradient(tx, ty, 0, tx, ty, 24); - grad.addColorStop(0, 'rgba(0, 255, 136, 0.3)'); - grad.addColorStop(1, 'rgba(0, 255, 136, 0)'); + grad.addColorStop(0, 'rgba(255, 106, 0, 0.3)'); + grad.addColorStop(1, 'rgba(255, 106, 0, 0)'); ctx.fillStyle = grad; ctx.beginPath(); ctx.arc(tx, ty, 24, 0, Math.PI * 2); ctx.fill(); } - ctx.fillStyle = this.touching ? '#00ff88' : '#666'; + ctx.fillStyle = this.touching ? '#ff6a00' : '#666'; ctx.beginPath(); ctx.arc(tx, ty, 14, 0, Math.PI * 2); ctx.fill(); // Position text - ctx.fillStyle = this.followMode ? '#00cc6a' : '#555'; + ctx.fillStyle = this.followMode ? '#cc5500' : '#555'; ctx.font = '10px monospace'; ctx.textAlign = 'left'; ctx.fillText(this.followMode ? 'FOLLOW' : 'HOLD', 8, s - 6); diff --git a/playground/js/ui/param-display.js b/playground/js/ui/param-display.js index cb94542..bb79123 100644 --- a/playground/js/ui/param-display.js +++ b/playground/js/ui/param-display.js @@ -2,7 +2,7 @@ // Shows output parameters as horizontal bars, draggable in examples mode const DEFAULT_PARAM_NAMES = ['Flow', 'Scale', 'Speed', 'Hue', 'Spread', 'Size', 'Trail', 'Turb', 'Attract', 'Radius', 'DispRate', 'DispAmt', 'Lifetime', 'Respawn', 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate']; -const DEFAULT_PARAM_COLORS = ['#00ff88', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d']; +const DEFAULT_PARAM_COLORS = ['#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', '#ffc15f', '#ff8a3d']; export class ParamDisplay { constructor(container, numParams = 20) {