From abb569b287350a175a2434722d5b2112924d86b8 Mon Sep 17 00:00:00 2001 From: monkey-w1n5t0n Date: Tue, 21 Jul 2026 12:47:58 +0200 Subject: [PATCH] chore: delete retired-playground artefacts and root relics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of the 2026-07 simplification audit, group 1. All verified dead by independent grep across scripts/, .github/workflows/, codegen/, manifold/, firmware/, vcv/, nisps/ and README.md before removal. - S23 / L55: playground/ — only dist/ was tracked (1.2 MB of bundled JS, sourcemaps and a second copy of nisps.wasm). The app itself is preserved on branch archive/playground-solidjs and tag playground-solidjs-final. - S29: the root Playwright rig — playwright.config.js, package.json, package-lock.json and tests/e2e/. It served ./playground, a page that no longer exists anywhere in the tree, and was invoked by nothing. NOTE: the live suite is manifold/tests/e2e/ with manifold/playwright.config.ts, which is untouched; every CI Playwright step runs with working-directory: manifold. - L48: NISPS_CORE_EXTRACTION_PLAN.md + NISPS_CORE_TASKS.md (818 lines of extraction relics; docs/specs/plans/ is the sanctioned home for plans). - L36: data/ — two CSVs from a retired era, plus a tracked LibreOffice lock file. - L32: .claude/worktrees/agent-ae87fe47/ only. The audit's wording invites deleting .claude/worktrees/ wholesale; that would have been destructive — the directory holds 12 LIVE registered git worktrees (332 MB), four of them on branches with unpushed commits. Verified agent-ae87fe47 is NOT registered before removing it; every live worktree is left intact. Cleaning up the rest is a separate operator decision. - ST10 fallout: dropped the dead `port-solidjs` branch trigger from ci.yml and the dead `feat/manifold-mission` / `feat/vcv-dist` triggers from vcv-plugin.yml — those branches are stale and the SolidJS target is retired. tests/cpp/ is untouched. Gates: run-all-tests.sh ALL GREEN. --- .../playground/js/shapeseq/chain-ui.js | 1002 ----------------- .../playground/js/shapeseq/chain.js | 319 ------ .../playground/js/shapeseq/primitive.js | 246 ---- .../playground/js/shapeseq/primitives.js | 505 --------- .../playground/js/shapeseq/sequencer.js | 304 ----- .../playground/js/shapeseq/step-viz.js | 328 ------ .github/workflows/ci.yml | 4 +- .github/workflows/vcv-plugin.yml | 2 +- NISPS_CORE_EXTRACTION_PLAN.md | 140 --- NISPS_CORE_TASKS.md | 678 ----------- data/.~lock.tree_cosmos_names.csv# | 1 - data/tree_cosmos_names.csv | 424 ------- data/tree_cosmos_names_2.csv | 420 ------- package-lock.json | 78 -- package.json | 13 - .../assets/PrimitivesShowcase-B55FOguW.css | 1 - .../assets/PrimitivesShowcase-D2lYbVrQ.js | 2 - .../assets/PrimitivesShowcase-D2lYbVrQ.js.map | 1 - playground/dist/assets/index-C1-a2ZPT.js | 3 - playground/dist/assets/index-C1-a2ZPT.js.map | 1 - playground/dist/assets/index-D19lnkK_.css | 1 - .../dist/assets/nisps-processor-BuKuNOY1.js | 2 - .../assets/nisps-processor-BuKuNOY1.js.map | 1 - playground/dist/assets/wasm-iml-FZSgVMmn.js | 2 - .../dist/assets/wasm-iml-FZSgVMmn.js.map | 1 - .../dist/assets/wasm-worker-OATBBHaT.js | 2 - .../dist/assets/wasm-worker-OATBBHaT.js.map | 1 - playground/dist/index.html | 14 - playground/dist/nisps.js | 2 - playground/dist/nisps.wasm | Bin 95864 -> 0 bytes playwright.config.js | 27 - tests/e2e/engine-switching.spec.js | 420 ------- tests/e2e/helpers.js | 32 - tests/e2e/input-pipeline.spec.js | 110 -- tests/e2e/ml-engine.spec.js | 170 --- tests/e2e/modular-mode.spec.js | 294 ----- tests/e2e/persistence.spec.js | 118 -- tests/e2e/ui-interactions.spec.js | 176 --- tests/e2e/wasm-api.spec.js | 231 ---- 39 files changed, 3 insertions(+), 6073 deletions(-) delete mode 100644 .claude/worktrees/agent-ae87fe47/playground/js/shapeseq/chain-ui.js delete mode 100644 .claude/worktrees/agent-ae87fe47/playground/js/shapeseq/chain.js delete mode 100644 .claude/worktrees/agent-ae87fe47/playground/js/shapeseq/primitive.js delete mode 100644 .claude/worktrees/agent-ae87fe47/playground/js/shapeseq/primitives.js delete mode 100644 .claude/worktrees/agent-ae87fe47/playground/js/shapeseq/sequencer.js delete mode 100644 .claude/worktrees/agent-ae87fe47/playground/js/shapeseq/step-viz.js delete mode 100644 NISPS_CORE_EXTRACTION_PLAN.md delete mode 100644 NISPS_CORE_TASKS.md delete mode 100644 data/.~lock.tree_cosmos_names.csv# delete mode 100644 data/tree_cosmos_names.csv delete mode 100644 data/tree_cosmos_names_2.csv delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 playground/dist/assets/PrimitivesShowcase-B55FOguW.css delete mode 100644 playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js delete mode 100644 playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js.map delete mode 100644 playground/dist/assets/index-C1-a2ZPT.js delete mode 100644 playground/dist/assets/index-C1-a2ZPT.js.map delete mode 100644 playground/dist/assets/index-D19lnkK_.css delete mode 100644 playground/dist/assets/nisps-processor-BuKuNOY1.js delete mode 100644 playground/dist/assets/nisps-processor-BuKuNOY1.js.map delete mode 100644 playground/dist/assets/wasm-iml-FZSgVMmn.js delete mode 100644 playground/dist/assets/wasm-iml-FZSgVMmn.js.map delete mode 100644 playground/dist/assets/wasm-worker-OATBBHaT.js delete mode 100644 playground/dist/assets/wasm-worker-OATBBHaT.js.map delete mode 100644 playground/dist/index.html delete mode 100644 playground/dist/nisps.js delete mode 100755 playground/dist/nisps.wasm delete mode 100644 playwright.config.js delete mode 100644 tests/e2e/engine-switching.spec.js delete mode 100644 tests/e2e/helpers.js delete mode 100644 tests/e2e/input-pipeline.spec.js delete mode 100644 tests/e2e/ml-engine.spec.js delete mode 100644 tests/e2e/modular-mode.spec.js delete mode 100644 tests/e2e/persistence.spec.js delete mode 100644 tests/e2e/ui-interactions.spec.js delete mode 100644 tests/e2e/wasm-api.spec.js diff --git a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/chain-ui.js b/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/chain-ui.js deleted file mode 100644 index ba3d779..0000000 --- a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/chain-ui.js +++ /dev/null @@ -1,1002 +0,0 @@ -/** - * ShapeSeq Chain Builder UI — vertical stack of primitive cards - * - * Pedalboard-style chain editor: expandable cards with param sliders, - * drag-to-reorder, delete, add-primitive palette, generator combine toggle. - * Mobile-first, dark glass theme matching the immersive playground. - * - * All styles are inline (no external CSS dependency). - * - * @module shapeseq/chain-ui - */ - -import { PRIMITIVE_REGISTRY } from './primitives.js'; - -// ── Category colors ────────────────────────────────────────────────── - -const CATEGORY_COLORS = { - generator: '#ff6a00', - processor: '#00ccff', - timing: '#ffcc00', - converter: '#88ff00', -}; - -// ── Inject styles once ─────────────────────────────────────────────── - -let stylesInjected = false; - -function injectStyles() { - if (stylesInjected) return; - stylesInjected = true; - - const css = ` -/* ── Chain Builder container ─────────────────────────────────── */ - -.chain-builder { - --cb-glass-bg: rgba(13, 13, 13, 0.72); - --cb-glass-border: rgba(255, 255, 255, 0.08); - --cb-glass-blur: 16px; - --cb-text: #e0e0e0; - --cb-text-dim: #888; - --cb-radius: 12px; - --cb-radius-sm: 8px; - --cb-accent: #ff6a00; - - display: flex; - flex-direction: column; - gap: 6px; - padding: 8px; - overflow-y: auto; - overflow-x: hidden; - -webkit-overflow-scrolling: touch; - font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; - font-size: 13px; - color: var(--cb-text); -} - -/* ── Primitive card ──────────────────────────────────────────── */ - -.chain-card { - position: relative; - background: var(--cb-glass-bg); - backdrop-filter: blur(var(--cb-glass-blur)); - -webkit-backdrop-filter: blur(var(--cb-glass-blur)); - border: 1px solid var(--cb-glass-border); - border-radius: var(--cb-radius); - overflow: hidden; - transition: box-shadow 0.2s, transform 0.15s; -} - -.chain-card.dragging { - opacity: 0.7; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); - transform: scale(1.03); - z-index: 100; -} - -.chain-card-left-border { - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 4px; - border-radius: var(--cb-radius) 0 0 var(--cb-radius); -} - -/* ── Card header ─────────────────────────────────────────────── */ - -.chain-card-header { - display: flex; - align-items: center; - gap: 8px; - padding: 10px 10px 10px 14px; - cursor: pointer; - user-select: none; - -webkit-user-select: none; -} - -.chain-card-drag { - display: flex; - flex-direction: column; - gap: 2px; - cursor: grab; - touch-action: none; - padding: 4px 2px; - opacity: 0.4; - transition: opacity 0.15s; -} - -.chain-card-drag:hover, -.chain-card-drag:active { - opacity: 0.8; -} - -.chain-card-drag span { - display: block; - width: 14px; - height: 2px; - background: var(--cb-text); - border-radius: 1px; -} - -.chain-card-name { - flex: 1; - font-weight: 600; - font-size: 13px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.chain-card-badge { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 2px 6px; - border-radius: 4px; - background: rgba(255, 255, 255, 0.06); - color: var(--cb-text-dim); - white-space: nowrap; -} - -.chain-card-summary { - font-size: 11px; - color: var(--cb-text-dim); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 140px; -} - -.chain-card-delete { - background: none; - border: none; - color: var(--cb-text-dim); - font-size: 16px; - padding: 4px 6px; - cursor: pointer; - border-radius: 4px; - transition: color 0.15s, background 0.15s; - line-height: 1; -} - -.chain-card-delete:hover, -.chain-card-delete:active { - color: #ff4466; - background: rgba(255, 68, 102, 0.12); -} - -.chain-card-expand-icon { - font-size: 10px; - color: var(--cb-text-dim); - transition: transform 0.2s; -} - -.chain-card.expanded .chain-card-expand-icon { - transform: rotate(90deg); -} - -/* ── Card body (params) ──────────────────────────────────────── */ - -.chain-card-body { - max-height: 0; - overflow: hidden; - transition: max-height 0.25s ease; -} - -.chain-card.expanded .chain-card-body { - max-height: 600px; -} - -.chain-card-params { - display: flex; - flex-direction: column; - gap: 6px; - padding: 4px 14px 12px 14px; -} - -/* ── Param slider row ────────────────────────────────────────── */ - -.chain-param-row { - display: flex; - align-items: center; - gap: 8px; -} - -.chain-param-label { - font-size: 11px; - color: var(--cb-text-dim); - min-width: 80px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.chain-param-slider { - flex: 1; - -webkit-appearance: none; - appearance: none; - height: 6px; - border-radius: 3px; - background: rgba(255, 255, 255, 0.08); - outline: none; - cursor: pointer; -} - -.chain-param-slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--cb-text); - border: 2px solid rgba(0, 0, 0, 0.3); - cursor: pointer; -} - -.chain-param-slider::-moz-range-thumb { - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--cb-text); - border: 2px solid rgba(0, 0, 0, 0.3); - cursor: pointer; -} - -.chain-param-value { - font-size: 11px; - color: var(--cb-text-dim); - min-width: 32px; - text-align: right; - font-variant-numeric: tabular-nums; -} - -/* ── NISPS live indicator ────────────────────────────────────── */ - -.chain-param-row.nisps-live .chain-param-slider { - box-shadow: 0 0 0 2px rgba(0, 140, 255, 0.5); - animation: nisps-pulse 1.5s ease-in-out infinite; -} - -@keyframes nisps-pulse { - 0%, 100% { box-shadow: 0 0 0 2px rgba(0, 140, 255, 0.3); } - 50% { box-shadow: 0 0 0 3px rgba(0, 140, 255, 0.7); } -} - -/* ── Add button ──────────────────────────────────────────────── */ - -.chain-add-btn { - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - padding: 12px; - border: 2px dashed rgba(255, 255, 255, 0.12); - border-radius: var(--cb-radius); - background: transparent; - color: var(--cb-text-dim); - font-family: inherit; - font-size: 13px; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; -} - -.chain-add-btn:hover, -.chain-add-btn:active { - border-color: var(--cb-accent); - color: var(--cb-accent); -} - -/* ── Generator combine mode toggle ───────────────────────────── */ - -.chain-combine-toggle { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 8px 0 4px; -} - -.chain-combine-label { - font-size: 11px; - color: var(--cb-text-dim); -} - -.chain-combine-btn { - font-family: inherit; - font-size: 12px; - font-weight: 600; - padding: 4px 14px; - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 6px; - background: rgba(255, 255, 255, 0.04); - color: var(--cb-text); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; - min-width: 60px; - text-align: center; -} - -.chain-combine-btn:hover, -.chain-combine-btn:active { - border-color: var(--cb-accent); - background: rgba(255, 106, 0, 0.1); -} - -/* ── Palette modal ───────────────────────────────────────────── */ - -.chain-palette-overlay { - position: fixed; - inset: 0; - z-index: 9999; - display: flex; - align-items: flex-end; - justify-content: center; - background: rgba(0, 0, 0, 0.6); - animation: palette-fade-in 0.15s ease; -} - -@keyframes palette-fade-in { - from { opacity: 0; } - to { opacity: 1; } -} - -.chain-palette { - width: 100%; - max-width: 420px; - max-height: 80vh; - overflow-y: auto; - background: rgba(20, 20, 20, 0.95); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 16px 16px 0 0; - padding: 16px 12px 24px; - animation: palette-slide-up 0.2s ease; -} - -@keyframes palette-slide-up { - from { transform: translateY(40px); opacity: 0; } - to { transform: translateY(0); opacity: 1; } -} - -.chain-palette-title { - font-size: 14px; - font-weight: 600; - color: var(--cb-text, #e0e0e0); - text-align: center; - margin-bottom: 12px; -} - -.chain-palette-item { - display: flex; - align-items: center; - gap: 10px; - padding: 12px; - border-radius: 10px; - cursor: pointer; - transition: background 0.12s; - border: none; - background: none; - width: 100%; - text-align: left; - font-family: inherit; - color: #e0e0e0; -} - -.chain-palette-item:hover, -.chain-palette-item:active { - background: rgba(255, 255, 255, 0.06); -} - -.chain-palette-item-color { - width: 4px; - height: 36px; - border-radius: 2px; - flex-shrink: 0; -} - -.chain-palette-item-info { - flex: 1; - min-width: 0; -} - -.chain-palette-item-name { - font-size: 13px; - font-weight: 600; -} - -.chain-palette-item-meta { - font-size: 11px; - color: #888; - margin-top: 2px; -} - -/* ── Drop indicator ──────────────────────────────────────────── */ - -.chain-drop-indicator { - height: 3px; - background: var(--cb-accent, #ff6a00); - border-radius: 2px; - margin: -2px 8px; - opacity: 0; - transition: opacity 0.1s; -} - -.chain-drop-indicator.active { - opacity: 1; -} -`; - - const style = document.createElement('style'); - style.textContent = css; - document.head.appendChild(style); -} - -// ── ChainBuilderUI ─────────────────────────────────────────────────── - -export class ChainBuilderUI { - /** - * @param {{ container: HTMLElement, chain: import('./chain.js').Chain, eventBus: { emit: Function, on: Function } }} opts - */ - constructor({ container, chain, eventBus }) { - injectStyles(); - - /** @type {HTMLElement} */ - this._container = container; - /** @type {import('./chain.js').Chain} */ - this._chain = chain; - /** @type {{ emit: Function, on: Function }} */ - this._bus = eventBus; - - /** @private @type {Set} flat param indices controlled by NISPS */ - this._liveParams = new Set(); - - /** @private @type {Function|null} */ - this._paramChangeCb = null; - - /** @private @type {Set} indices of expanded cards */ - this._expandedCards = new Set(); - - /** @private @type {Float32Array|null} current param values for display */ - this._currentParams = null; - - // ── Drag state ── - /** @private */ - this._dragIndex = -1; - /** @private */ - this._dragEl = null; - /** @private */ - this._dragStartY = 0; - /** @private */ - this._dragOffsetY = 0; - /** @private @type {HTMLElement|null} */ - this._cardList = null; - - // ── Palette overlay ref ── - /** @private @type {HTMLElement|null} */ - this._paletteOverlay = null; - - // ── Bound handlers for cleanup ── - this._onPointerMove = this._handlePointerMove.bind(this); - this._onPointerUp = this._handlePointerUp.bind(this); - - this.render(); - } - - // ── Public API ───────────────────────────────────────────────────── - - /** - * Rebuild the entire UI from current chain state. - */ - render() { - this._container.innerHTML = ''; - this._container.classList.add('chain-builder'); - - const primitives = this._chain.getPrimitives(); - - // Card list wrapper - const cardList = document.createElement('div'); - cardList.style.display = 'flex'; - cardList.style.flexDirection = 'column'; - cardList.style.gap = '6px'; - this._cardList = cardList; - - // Compute flat param offset for each primitive - let flatOffset = 0; - for (let i = 0; i < primitives.length; i++) { - const prim = primitives[i]; - const card = this._createCard(prim, i, flatOffset); - cardList.appendChild(card); - flatOffset += prim.paramCount; - } - - this._container.appendChild(cardList); - - // Add button - const addBtn = document.createElement('button'); - addBtn.className = 'chain-add-btn'; - addBtn.innerHTML = '+ Add Primitive'; - addBtn.addEventListener('click', () => this._openPalette()); - this._container.appendChild(addBtn); - - // Generator combine mode toggle - const toggle = this._createCombineToggle(); - this._container.appendChild(toggle); - } - - /** - * Update which flat param indices are NISPS-controlled (pulsing indicator). - * @param {Array} paramIndices - */ - setLiveParams(paramIndices) { - this._liveParams = new Set(paramIndices); - // Update existing rows without full re-render - const rows = this._container.querySelectorAll('.chain-param-row'); - rows.forEach((row) => { - const idx = parseInt(row.dataset.flatIndex, 10); - if (isNaN(idx)) return; - if (this._liveParams.has(idx)) { - row.classList.add('nisps-live'); - } else { - row.classList.remove('nisps-live'); - } - }); - } - - /** - * Register a callback for manual slider changes. - * @param {(flatParamIndex: number, value: number) => void} callback - */ - onParamChange(callback) { - this._paramChangeCb = callback; - } - - /** - * Update displayed param values without re-rendering. - * @param {Float32Array|Array} params - flat param array - */ - updateParamValues(params) { - this._currentParams = params; - const sliders = this._container.querySelectorAll('.chain-param-slider'); - sliders.forEach((slider) => { - const idx = parseInt(slider.dataset.flatIndex, 10); - if (!isNaN(idx) && idx < params.length) { - slider.value = params[idx]; - // Update adjacent value display - const valEl = slider.parentElement && slider.parentElement.querySelector('.chain-param-value'); - if (valEl) { - valEl.textContent = params[idx].toFixed(2); - } - } - }); - } - - /** - * Cleanup all DOM and listeners. - */ - destroy() { - this._closePalette(); - document.removeEventListener('pointermove', this._onPointerMove); - document.removeEventListener('pointerup', this._onPointerUp); - this._container.innerHTML = ''; - this._container.classList.remove('chain-builder'); - this._paramChangeCb = null; - } - - // ── Card creation ────────────────────────────────────────────────── - - /** - * @private - * @param {import('./primitive.js').Primitive} prim - * @param {number} chainIndex - * @param {number} flatOffset - flat param offset for this primitive - * @returns {HTMLElement} - */ - _createCard(prim, chainIndex, flatOffset) { - const card = document.createElement('div'); - card.className = 'chain-card'; - card.dataset.chainIndex = chainIndex; - if (this._expandedCards.has(chainIndex)) { - card.classList.add('expanded'); - } - - const catColor = CATEGORY_COLORS[prim.category] || '#888'; - - // Left color border - const leftBorder = document.createElement('div'); - leftBorder.className = 'chain-card-left-border'; - leftBorder.style.background = catColor; - card.appendChild(leftBorder); - - // Header - const header = document.createElement('div'); - header.className = 'chain-card-header'; - - // Drag handle - const drag = document.createElement('div'); - drag.className = 'chain-card-drag'; - drag.innerHTML = ''; - drag.addEventListener('pointerdown', (e) => this._handleDragStart(e, chainIndex, card)); - header.appendChild(drag); - - // Name - const name = document.createElement('span'); - name.className = 'chain-card-name'; - name.textContent = prim.name; - header.appendChild(name); - - // Category badge - const badge = document.createElement('span'); - badge.className = 'chain-card-badge'; - badge.textContent = prim.category; - badge.style.color = catColor; - header.appendChild(badge); - - // Summary (collapsed key params) - const summary = document.createElement('span'); - summary.className = 'chain-card-summary'; - summary.textContent = this._buildSummary(prim, flatOffset); - header.appendChild(summary); - - // Expand chevron - const chevron = document.createElement('span'); - chevron.className = 'chain-card-expand-icon'; - chevron.textContent = '\u25B6'; // right-pointing triangle - header.appendChild(chevron); - - // Delete button - const del = document.createElement('button'); - del.className = 'chain-card-delete'; - del.textContent = '\u00D7'; // multiplication sign as X - del.title = 'Remove'; - del.addEventListener('click', (e) => { - e.stopPropagation(); - this._removePrimitive(chainIndex); - }); - header.appendChild(del); - - // Toggle expand on header click (but not on drag handle or delete) - header.addEventListener('click', (e) => { - if (e.target.closest('.chain-card-drag') || e.target.closest('.chain-card-delete')) return; - this._toggleExpand(chainIndex, card); - }); - - card.appendChild(header); - - // Body (params) - const body = document.createElement('div'); - body.className = 'chain-card-body'; - - const paramsDiv = document.createElement('div'); - paramsDiv.className = 'chain-card-params'; - - for (let pi = 0; pi < prim.paramSchema.length; pi++) { - const schema = prim.paramSchema[pi]; - const flatIdx = flatOffset + pi; - const row = this._createParamRow(schema, flatIdx, prim); - paramsDiv.appendChild(row); - } - - body.appendChild(paramsDiv); - card.appendChild(body); - - return card; - } - - /** - * @private - */ - _buildSummary(prim, flatOffset) { - const schema = prim.paramSchema; - const maxShow = Math.min(3, schema.length); - const parts = []; - for (let i = 0; i < maxShow; i++) { - const val = this._currentParams - ? this._currentParams[flatOffset + i] - : schema[i].default; - parts.push(schema[i].name + ':' + (val !== undefined ? val.toFixed(2) : schema[i].default.toFixed(2))); - } - return parts.join(' '); - } - - /** - * @private - */ - _createParamRow(schema, flatIdx, prim) { - const row = document.createElement('div'); - row.className = 'chain-param-row'; - row.dataset.flatIndex = flatIdx; - if (this._liveParams.has(flatIdx)) { - row.classList.add('nisps-live'); - } - - const label = document.createElement('span'); - label.className = 'chain-param-label'; - label.textContent = schema.name; - row.appendChild(label); - - const slider = document.createElement('input'); - slider.type = 'range'; - slider.className = 'chain-param-slider'; - slider.min = '0'; - slider.max = '1'; - slider.step = '0.001'; - slider.dataset.flatIndex = flatIdx; - - const currentVal = this._currentParams - ? this._currentParams[flatIdx] - : schema.default; - slider.value = currentVal !== undefined ? currentVal : schema.default; - - const valDisplay = document.createElement('span'); - valDisplay.className = 'chain-param-value'; - valDisplay.textContent = parseFloat(slider.value).toFixed(2); - - slider.addEventListener('input', () => { - const v = parseFloat(slider.value); - valDisplay.textContent = v.toFixed(2); - if (this._paramChangeCb) { - this._paramChangeCb(flatIdx, v); - } - }); - - row.appendChild(slider); - row.appendChild(valDisplay); - - return row; - } - - // ── Expand / collapse ────────────────────────────────────────────── - - /** - * @private - */ - _toggleExpand(chainIndex, cardEl) { - if (this._expandedCards.has(chainIndex)) { - this._expandedCards.delete(chainIndex); - cardEl.classList.remove('expanded'); - } else { - this._expandedCards.add(chainIndex); - cardEl.classList.add('expanded'); - } - } - - // ── Add / remove primitives ──────────────────────────────────────── - - /** - * @private - */ - _removePrimitive(chainIndex) { - this._chain.removePrimitive(chainIndex); - // Adjust expanded set - const newExpanded = new Set(); - for (const idx of this._expandedCards) { - if (idx < chainIndex) newExpanded.add(idx); - else if (idx > chainIndex) newExpanded.add(idx - 1); - // idx === chainIndex is removed - } - this._expandedCards = newExpanded; - this.render(); - this._bus.emit('ui.chainEdit', { action: 'remove', index: chainIndex }); - } - - /** - * @private - */ - _addPrimitive(registryKey) { - const Ctor = PRIMITIVE_REGISTRY[registryKey]; - if (!Ctor) return; - const instance = new Ctor(); - this._chain.addPrimitive(instance); - this.render(); - this._bus.emit('ui.chainEdit', { action: 'add', name: registryKey }); - } - - // ── Palette ──────────────────────────────────────────────────────── - - /** - * @private - */ - _openPalette() { - if (this._paletteOverlay) return; - - const overlay = document.createElement('div'); - overlay.className = 'chain-palette-overlay'; - - const palette = document.createElement('div'); - palette.className = 'chain-palette'; - - const title = document.createElement('div'); - title.className = 'chain-palette-title'; - title.textContent = 'Add Primitive'; - palette.appendChild(title); - - const keys = Object.keys(PRIMITIVE_REGISTRY); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const CtorRef = PRIMITIVE_REGISTRY[key]; - // Instantiate temporarily to read metadata - const temp = new CtorRef(); - - const item = document.createElement('button'); - item.className = 'chain-palette-item'; - - const colorBar = document.createElement('div'); - colorBar.className = 'chain-palette-item-color'; - colorBar.style.background = CATEGORY_COLORS[temp.category] || '#888'; - item.appendChild(colorBar); - - const info = document.createElement('div'); - info.className = 'chain-palette-item-info'; - - const nameEl = document.createElement('div'); - nameEl.className = 'chain-palette-item-name'; - nameEl.textContent = temp.name; - info.appendChild(nameEl); - - const meta = document.createElement('div'); - meta.className = 'chain-palette-item-meta'; - meta.textContent = temp.category + ' \u00B7 ' + temp.paramCount + ' params'; - info.appendChild(meta); - - item.appendChild(info); - - item.addEventListener('click', () => { - this._closePalette(); - this._addPrimitive(key); - }); - - palette.appendChild(item); - } - - overlay.appendChild(palette); - - // Close on overlay click (outside palette) - overlay.addEventListener('click', (e) => { - if (e.target === overlay) { - this._closePalette(); - } - }); - - document.body.appendChild(overlay); - this._paletteOverlay = overlay; - } - - /** - * @private - */ - _closePalette() { - if (this._paletteOverlay) { - this._paletteOverlay.remove(); - this._paletteOverlay = null; - } - } - - // ── Generator combine mode toggle ────────────────────────────────── - - /** - * @private - */ - _createCombineToggle() { - const wrapper = document.createElement('div'); - wrapper.className = 'chain-combine-toggle'; - - const label = document.createElement('span'); - label.className = 'chain-combine-label'; - label.textContent = 'Gen combine:'; - wrapper.appendChild(label); - - const btn = document.createElement('button'); - btn.className = 'chain-combine-btn'; - btn.textContent = this._chain.generatorCombineMode === 'additive' ? 'Add' : 'Mul'; - - btn.addEventListener('click', () => { - if (this._chain.generatorCombineMode === 'additive') { - this._chain.generatorCombineMode = 'multiplicative'; - btn.textContent = 'Mul'; - } else { - this._chain.generatorCombineMode = 'additive'; - btn.textContent = 'Add'; - } - this._bus.emit('ui.chainEdit', { action: 'combineMode', mode: this._chain.generatorCombineMode }); - }); - - wrapper.appendChild(btn); - return wrapper; - } - - // ── Drag-to-reorder (pointer events) ─────────────────────────────── - - /** - * @private - */ - _handleDragStart(e, chainIndex, cardEl) { - e.preventDefault(); - e.stopPropagation(); - - this._dragIndex = chainIndex; - this._dragEl = cardEl; - this._dragStartY = e.clientY; - this._dragOffsetY = 0; - - cardEl.classList.add('dragging'); - cardEl.setPointerCapture(e.pointerId); - - document.addEventListener('pointermove', this._onPointerMove); - document.addEventListener('pointerup', this._onPointerUp); - } - - /** - * @private - */ - _handlePointerMove(e) { - if (this._dragIndex < 0 || !this._dragEl || !this._cardList) return; - - const dy = e.clientY - this._dragStartY; - this._dragOffsetY = dy; - this._dragEl.style.transform = 'translateY(' + dy + 'px) scale(1.03)'; - - // Determine drop target by checking which card we're over - const cards = this._cardList.querySelectorAll('.chain-card'); - const dragRect = this._dragEl.getBoundingClientRect(); - const dragCenter = dragRect.top + dragRect.height / 2; - - let targetIndex = this._dragIndex; - for (let i = 0; i < cards.length; i++) { - if (i === this._dragIndex) continue; - const rect = cards[i].getBoundingClientRect(); - const center = rect.top + rect.height / 2; - if (this._dragIndex < i && dragCenter > center) { - targetIndex = i; - } else if (this._dragIndex > i && dragCenter < center) { - targetIndex = i; - } - } - - this._dragTargetIndex = targetIndex; - } - - /** - * @private - */ - _handlePointerUp(e) { - document.removeEventListener('pointermove', this._onPointerMove); - document.removeEventListener('pointerup', this._onPointerUp); - - if (this._dragEl) { - this._dragEl.classList.remove('dragging'); - this._dragEl.style.transform = ''; - } - - const from = this._dragIndex; - const to = this._dragTargetIndex !== undefined ? this._dragTargetIndex : from; - - this._dragIndex = -1; - this._dragEl = null; - this._dragTargetIndex = undefined; - - if (from !== to && from >= 0) { - // Adjust expanded set for the move - const newExpanded = new Set(); - for (const idx of this._expandedCards) { - if (idx === from) { - newExpanded.add(to); - } else { - let adjusted = idx; - if (from < to) { - if (idx > from && idx <= to) adjusted = idx - 1; - } else { - if (idx >= to && idx < from) adjusted = idx + 1; - } - newExpanded.add(adjusted); - } - } - this._expandedCards = newExpanded; - - this._chain.movePrimitive(from, to); - this.render(); - this._bus.emit('ui.chainEdit', { action: 'reorder', from: from, to: to }); - } - } -} diff --git a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/chain.js b/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/chain.js deleted file mode 100644 index 7e0f88d..0000000 --- a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/chain.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * ShapeSeq Chain — sequential pipeline runner with generator combination modes - * - * Evaluates an ordered list of primitives as a sequential pipeline: - * 1. Generators run first, combined via additive or multiplicative merge - * 2. Processors transform the pattern in chain order - * 3. Converters run in chain order - * 4. Timing modifiers annotate last - * - * Params are distributed flat across primitives in chain order. - * Each primitive gets a deterministic PRNG stream via fork(masterPRNG, index). - * - * Port-ready: explicit state, typed arrays, no closures in hot path. - * - * @module shapeseq/chain - */ - -import { createPattern, mergePatterns } from './pattern.js'; -import { createPRNG, fork } from './prng.js'; - -// ── Category execution order ──────────────────────────────────────── - -const PHASE_ORDER = ['generator', 'processor', 'converter', 'timing']; - -// ── Chain class ───────────────────────────────────────────────────── - -export class Chain { - constructor() { - /** @private @type {Array} */ - this._primitives = []; - - /** @type {'additive'|'multiplicative'} */ - this.generatorCombineMode = 'additive'; - - /** @private @type {number} */ - this._masterSeed = 0; - - /** - * Per-primitive state objects, indexed by position in chain. - * Populated after evaluate() calls; used for freeze support. - * @private @type {Array} - */ - this._primitiveStates = []; - } - - // ── Primitive management ──────────────────────────────────────── - - /** - * Append a primitive to the end of the chain. - * @param {import('./primitive.js').Primitive} primitive - */ - addPrimitive(primitive) { - this._primitives.push(primitive); - this._primitiveStates.push(primitive.getState()); - } - - /** - * Remove the primitive at the given index. - * @param {number} index - */ - removePrimitive(index) { - const idx = index | 0; - if (idx < 0 || idx >= this._primitives.length) { - throw new RangeError('removePrimitive: index ' + index + ' out of range [0, ' + (this._primitives.length - 1) + ']'); - } - this._primitives.splice(idx, 1); - this._primitiveStates.splice(idx, 1); - } - - /** - * Insert a primitive at the given index, shifting others right. - * @param {number} index - * @param {import('./primitive.js').Primitive} primitive - */ - insertPrimitive(index, primitive) { - const idx = index | 0; - if (idx < 0 || idx > this._primitives.length) { - throw new RangeError('insertPrimitive: index ' + index + ' out of range [0, ' + this._primitives.length + ']'); - } - this._primitives.splice(idx, 0, primitive); - this._primitiveStates.splice(idx, 0, primitive.getState()); - } - - /** - * Move a primitive from one position to another. - * @param {number} fromIndex - * @param {number} toIndex - */ - movePrimitive(fromIndex, toIndex) { - const from = fromIndex | 0; - const to = toIndex | 0; - const len = this._primitives.length; - if (from < 0 || from >= len) { - throw new RangeError('movePrimitive: fromIndex ' + fromIndex + ' out of range [0, ' + (len - 1) + ']'); - } - if (to < 0 || to >= len) { - throw new RangeError('movePrimitive: toIndex ' + toIndex + ' out of range [0, ' + (len - 1) + ']'); - } - - const [prim] = this._primitives.splice(from, 1); - const [state] = this._primitiveStates.splice(from, 1); - this._primitives.splice(to, 0, prim); - this._primitiveStates.splice(to, 0, state); - } - - /** - * Get the current list of primitives (shallow copy). - * @returns {Array} - */ - getPrimitives() { - return this._primitives.slice(); - } - - // ── Configuration ─────────────────────────────────────────────── - - /** - * Total parameter count across all primitives in the chain. - * @returns {number} - */ - get totalParamCount() { - let total = 0; - for (let i = 0; i < this._primitives.length; i++) { - total += this._primitives[i].paramCount; - } - return total; - } - - /** - * Get a flat list of all param schemas across all primitives, - * annotated with their primitive and param indices. - * - * @returns {Array<{ primitiveIndex: number, paramIndex: number, schema: Object }>} - */ - getParamSchemas() { - const result = []; - for (let pi = 0; pi < this._primitives.length; pi++) { - const prim = this._primitives[pi]; - const schema = prim.paramSchema; - for (let si = 0; si < schema.length; si++) { - result.push({ - primitiveIndex: pi, - paramIndex: si, - schema: schema[si], - }); - } - } - return result; - } - - // ── Evaluation ────────────────────────────────────────────────── - - /** - * Evaluate the chain, producing a pattern description. - * - * Pipeline order: - * 1. Generators — combined via generatorCombineMode - * 2. Processors — sequential transform - * 3. Converters — sequential transform - * 4. Timing modifiers — annotate last - * - * @param {Float32Array|Array} params - flat param array distributed across primitives - * @param {number} stepCount - number of steps in the output pattern - * @param {number} masterSeed - seed for the master PRNG - * @returns {{ steps: Array, stepCount: number, metadata: Object }} - */ - evaluate(params, stepCount, masterSeed) { - const primitives = this._primitives; - const primCount = primitives.length; - - // Create master PRNG from seed - const masterPRNG = createPRNG(masterSeed >>> 0); - - // ── Bucket primitives by category, preserving chain order ── - - /** @type {Array<{ index: number, prim: Object }>} */ - const generators = []; - const processors = []; - const converters = []; - const timingMods = []; - - for (let i = 0; i < primCount; i++) { - const entry = { index: i, prim: primitives[i] }; - switch (primitives[i].category) { - case 'generator': generators.push(entry); break; - case 'processor': processors.push(entry); break; - case 'converter': converters.push(entry); break; - case 'timing': timingMods.push(entry); break; - } - } - - // ── Compute param offsets per primitive ── - - const paramOffsets = new Array(primCount); - let offset = 0; - for (let i = 0; i < primCount; i++) { - paramOffsets[i] = offset; - offset += primitives[i].paramCount; - } - - // ── Helper: run a single primitive ── - - const self = this; - - function runPrimitive(entry, inputPattern) { - const idx = entry.index; - const prim = entry.prim; - const pOffset = paramOffsets[idx]; - const pCount = prim.paramCount; - - // Slice params for this primitive - const primParams = new Float32Array(pCount); - for (let p = 0; p < pCount; p++) { - primParams[p] = pOffset + p < params.length ? +params[pOffset + p] : prim.paramSchema[p].default; - } - - // Fork a deterministic PRNG for this primitive - const primRNG = fork(masterPRNG, idx); - - // Get current state - const state = self._primitiveStates[idx] || prim.getState(); - - // Process - const result = prim.process(primParams, inputPattern, state, primRNG); - - // Store updated state - self._primitiveStates[idx] = result.nextState; - - return result.patternDesc; - } - - // ── Phase 1: Generators ── - - let pattern; - - if (generators.length === 0) { - // Default pattern: all steps triggered - pattern = createPattern(stepCount); - for (let i = 0; i < stepCount; i++) { - pattern.steps[i].trigger = true; - } - } else if (generators.length === 1) { - // Single generator — no merge needed - pattern = runPrimitive(generators[0], createPattern(stepCount)); - } else { - // Multiple generators — run each, then merge - let merged = runPrimitive(generators[0], createPattern(stepCount)); - for (let g = 1; g < generators.length; g++) { - const next = runPrimitive(generators[g], createPattern(stepCount)); - merged = mergePatterns(merged, next, this.generatorCombineMode); - } - pattern = merged; - } - - // ── Phase 2: Processors ── - - for (let i = 0; i < processors.length; i++) { - pattern = runPrimitive(processors[i], pattern); - } - - // ── Phase 3: Converters ── - - for (let i = 0; i < converters.length; i++) { - pattern = runPrimitive(converters[i], pattern); - } - - // ── Phase 4: Timing modifiers ── - - for (let i = 0; i < timingMods.length; i++) { - pattern = runPrimitive(timingMods[i], pattern); - } - - return pattern; - } - - // ── State management (for freeze) ────────────────────────────── - - /** - * Get serializable state for all primitives in the chain. - * @returns {Array} - */ - getState() { - const states = new Array(this._primitives.length); - for (let i = 0; i < this._primitives.length; i++) { - states[i] = this._primitiveStates[i] || this._primitives[i].getState(); - } - return states; - } - - /** - * Restore all primitive states from a previously serialized state array. - * @param {Array} states - */ - setState(states) { - if (!Array.isArray(states)) { - throw new TypeError('setState expects an array of state objects'); - } - const len = Math.min(states.length, this._primitives.length); - for (let i = 0; i < len; i++) { - this._primitives[i].setState(states[i]); - this._primitiveStates[i] = states[i]; - } - } - - /** - * Get the master PRNG seed. - * @returns {number} - */ - getMasterSeed() { - return this._masterSeed; - } - - /** - * Set the master PRNG seed. - * @param {number} seed - 32-bit integer seed - */ - setMasterSeed(seed) { - this._masterSeed = seed >>> 0; - } -} diff --git a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/primitive.js b/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/primitive.js deleted file mode 100644 index ebec5b6..0000000 --- a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/primitive.js +++ /dev/null @@ -1,246 +0,0 @@ -/** - * ShapeSeq Primitive Base Class and Param Schema System - * - * Base class for all sequencing primitives (generators, processors, - * timing modifiers, converters). Defines the param schema format, - * symbolic process() interface, and state management for freeze support. - * - * Port-ready: explicit state, no closures, typed arrays where possible. - * - * @module shapeseq/primitive - */ - -// ── Valid primitive categories ────────────────────────────────────── - -export const CATEGORIES = Object.freeze([ - 'generator', - 'processor', - 'timing', - 'converter', -]); - -// ── Param schema defaults ─────────────────────────────────────────── - -const DEFAULT_SCALED_RANGE = 0.3; - -// ── Boundary enforcement helpers ──────────────────────────────────── - -/** - * Clamp a value to [0, 1]. - * @param {number} v - * @returns {number} - */ -function clamp01(v) { - return v < 0 ? 0 : v > 1 ? 1 : v; -} - -/** - * Wrap a value into [0, 1) with modular arithmetic. - * @param {number} v - * @returns {number} - */ -function wrap01(v) { - const m = v % 1; - return m < 0 ? m + 1 : m; -} - -/** - * Apply boundary enforcement to a raw param value. - * - * @param {number} value - raw [0,1] value (or delta-adjusted value) - * @param {{ boundary: string, scaledRange?: number }} schema - param schema entry - * @param {number|null} frozenValue - frozen value for 'scaled' boundary (null if not frozen) - * @returns {number} - */ -export function applyBoundary(value, schema, frozenValue) { - switch (schema.boundary) { - case 'wrap': - return wrap01(value); - case 'scaled': { - if (frozenValue === null || frozenValue === undefined) { - return clamp01(value); - } - const range = schema.scaledRange !== undefined ? schema.scaledRange : DEFAULT_SCALED_RANGE; - const lo = frozenValue - range; - const hi = frozenValue + range; - // Map [0,1] input to [lo, hi], then clamp to [0,1] - const mapped = lo + value * (hi - lo); - return clamp01(mapped); - } - case 'clamp': - default: - return clamp01(value); - } -} - -// ── Param schema validation ───────────────────────────────────────── - -/** - * Validate a single param schema entry. - * Throws on invalid entries for fast fail during development. - * - * @param {{ name: string, default: number, boundary: string, scaledRange?: number }} entry - * @param {number} index - position in schema array (for error messages) - */ -function validateSchemaEntry(entry, index) { - if (!entry || typeof entry !== 'object') { - throw new TypeError('paramSchema[' + index + '] must be an object'); - } - if (typeof entry.name !== 'string' || entry.name.length === 0) { - throw new TypeError('paramSchema[' + index + '].name must be a non-empty string'); - } - if (typeof entry.default !== 'number' || entry.default < 0 || entry.default > 1) { - throw new RangeError('paramSchema[' + index + '].default must be in [0,1], got ' + entry.default); - } - if (entry.boundary !== 'clamp' && entry.boundary !== 'wrap' && entry.boundary !== 'scaled') { - throw new TypeError( - "paramSchema[" + index + "].boundary must be 'clamp', 'wrap', or 'scaled', got '" + entry.boundary + "'" - ); - } - if (entry.boundary === 'scaled') { - const sr = entry.scaledRange; - if (sr !== undefined && (typeof sr !== 'number' || sr <= 0 || sr > 1)) { - throw new RangeError('paramSchema[' + index + '].scaledRange must be in (0,1], got ' + sr); - } - } -} - -// ── Primitive base class ──────────────────────────────────────────── - -export class Primitive { - /** - * @param {string} name - unique identifier for this primitive type - * @param {string} category - one of CATEGORIES - * @param {Array<{ name: string, default: number, boundary: string, scaledRange?: number }>} paramSchema - */ - constructor(name, category, paramSchema) { - if (typeof name !== 'string' || name.length === 0) { - throw new TypeError('Primitive name must be a non-empty string'); - } - if (CATEGORIES.indexOf(category) === -1) { - throw new TypeError( - "Primitive category must be one of [" + CATEGORIES.join(', ') + "], got '" + category + "'" - ); - } - if (!Array.isArray(paramSchema)) { - throw new TypeError('paramSchema must be an array'); - } - - // Validate each entry - for (let i = 0; i < paramSchema.length; i++) { - validateSchemaEntry(paramSchema[i], i); - } - - /** @type {string} */ - this.name = name; - - /** @type {string} */ - this.category = category; - - /** - * Frozen copy of the param schema. Each entry: - * { name: string, default: number, boundary: 'clamp'|'wrap'|'scaled', scaledRange?: number } - * @type {Array} - */ - this.paramSchema = Object.freeze(paramSchema.map(function (entry) { - const frozen = { - name: entry.name, - default: entry.default, - boundary: entry.boundary, - }; - if (entry.boundary === 'scaled') { - frozen.scaledRange = entry.scaledRange !== undefined ? entry.scaledRange : DEFAULT_SCALED_RANGE; - } - return Object.freeze(frozen); - })); - - /** @private */ - this._seed = 0; - } - - // ── Param utilities ───────────────────────────────────────────── - - /** - * Total number of parameters this primitive exposes. - * @returns {number} - */ - get paramCount() { - return this.paramSchema.length; - } - - /** - * Get default param values as a Float32Array, one per schema entry. - * @returns {Float32Array} - */ - getDefaults() { - const count = this.paramSchema.length; - const defaults = new Float32Array(count); - for (let i = 0; i < count; i++) { - defaults[i] = this.paramSchema[i].default; - } - return defaults; - } - - // ── Symbolic processing ───────────────────────────────────────── - - /** - * Transform a pattern description. Subclasses MUST override this. - * - * - Generators ignore patternDesc and create a new one (using createPattern()) - * - Processors/timing modifiers clone and transform patternDesc - * - The rng param is a PRNG state from prng.js; consume via next(rng) - * and return the consumed state in the result - * - * @param {Float32Array|Array} params - param values, one per schema entry, each [0,1] - * @param {{ steps: Array, stepCount: number, metadata: Object }} patternDesc - input pattern - * @param {Object} state - primitive-specific state (from previous process() call or getState()) - * @param {{ state: number }} rng - PRNG state object from prng.js - * @returns {{ patternDesc: { steps: Array, stepCount: number, metadata: Object }, nextState: Object }} - */ - process(params, patternDesc, state, rng) { - void params; void patternDesc; void state; void rng; - throw new Error(this.name + '.process() must be overridden by subclass'); - } - - // ── State management (for freeze) ────────────────────────────── - - /** - * Get serializable state for this primitive. - * Stateless primitives return {}. Stateful primitives (e.g. Pitch Walker) - * override to include their internal state. - * - * @returns {Object} - */ - getState() { - return {}; - } - - /** - * Restore primitive state from a previously serialized state object. - * Stateless primitives are a no-op. Stateful primitives override. - * - * @param {Object} _state - */ - setState(_state) { - // no-op for stateless primitives - } - - /** - * Get the PRNG seed associated with this primitive. - * Used by freeze-as-algorithm to replay identical sequences. - * - * @returns {number} - */ - getSeed() { - return this._seed; - } - - /** - * Set the PRNG seed for this primitive. - * - * @param {number} seed - 32-bit integer seed - */ - setSeed(seed) { - this._seed = seed >>> 0; - } -} diff --git a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/primitives.js b/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/primitives.js deleted file mode 100644 index 9e21cf8..0000000 --- a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/primitives.js +++ /dev/null @@ -1,505 +0,0 @@ -/** - * ShapeSeq Sequencing Primitives - * - * All 8 primitives for the ShapeSeq generative sequencing system. - * Each extends Primitive and implements process(params, patternDesc, state, rng). - * - * Port-ready: explicit state, no closures, seeded PRNG, typed arrays. - * - * @module shapeseq/primitives - */ - -import { Primitive } from './primitive.js'; -import { createPattern, clonePattern, setStep } from './pattern.js'; -import { next, nextInt } from './prng.js'; - -// ── Helper: map [0,1] float to integer range [lo, hi] ────────────── - -function mapToInt(value, lo, hi) { - const clamped = value < 0 ? 0 : value > 1 ? 1 : value; - return lo + Math.floor(clamped * (hi - lo + 1 - 1e-9)); -} - -// ── 1. EuclideanRhythm ────────────────────────────────────────────── - -/** - * Bjorklund algorithm: distribute `pulses` as evenly as possible - * across `steps`, then apply rotation. - */ -function bjorklund(steps, pulses) { - if (pulses >= steps) { - const result = new Array(steps); - for (let i = 0; i < steps; i++) result[i] = true; - return result; - } - if (pulses <= 0) { - const result = new Array(steps); - for (let i = 0; i < steps; i++) result[i] = false; - return result; - } - - // Build pattern using Bjorklund's algorithm - let groups = []; - for (let i = 0; i < pulses; i++) groups.push([true]); - for (let i = 0; i < steps - pulses; i++) groups.push([false]); - - while (true) { - const remainder = groups.length - pulses; - if (remainder <= 1) break; - const minLen = Math.min(pulses, remainder); - const newGroups = []; - for (let i = 0; i < minLen; i++) { - newGroups.push(groups[i].concat(groups[groups.length - 1 - i])); - } - // Keep any leftovers - const leftStart = minLen; - const leftEnd = groups.length - minLen; - for (let i = leftStart; i < leftEnd; i++) { - newGroups.push(groups[i]); - } - groups = newGroups; - pulses = minLen; - if (pulses <= 1) break; - } - - // Flatten groups - const result = []; - for (let i = 0; i < groups.length; i++) { - for (let j = 0; j < groups[i].length; j++) { - result.push(groups[i][j]); - } - } - return result; -} - -export class EuclideanRhythm extends Primitive { - constructor() { - super('EuclideanRhythm', 'generator', [ - { name: 'steps', default: 0.5, boundary: 'clamp' }, - { name: 'pulses', default: 0.5, boundary: 'clamp' }, - { name: 'rotation', default: 0.0, boundary: 'wrap' }, - ]); - } - - process(params, patternDesc, state, rng) { - const stepCount = patternDesc.stepCount; - const steps = mapToInt(params[0], 2, stepCount); - const pulses = mapToInt(params[1], 0, steps); - const rotation = mapToInt(params[2], 0, steps - 1); - - const rhythm = bjorklund(steps, pulses); - const pattern = createPattern(stepCount); - - for (let i = 0; i < stepCount; i++) { - if (i < steps) { - const srcIdx = (i - rotation + steps) % steps; - if (rhythm[srcIdx]) { - setStep(pattern, i, { trigger: true }); - } - } - // Steps beyond `steps` remain untriggered (default) - } - - return { patternDesc: pattern, nextState: {} }; - } -} - -// ── 2. ProbabilityGate ────────────────────────────────────────────── - -export class ProbabilityGate extends Primitive { - constructor() { - super('ProbabilityGate', 'processor', [ - { name: 'density', default: 0.7, boundary: 'clamp' }, - { name: 'accentProbability', default: 0.3, boundary: 'clamp' }, - ]); - } - - process(params, patternDesc, state, rng) { - const density = params[0]; - const accentProb = params[1]; - const pattern = clonePattern(patternDesc); - let currentRng = rng; - - for (let i = 0; i < pattern.stepCount; i++) { - const step = pattern.steps[i]; - if (step.trigger) { - // Coin flip for survival - const r1 = next(currentRng); - currentRng = r1.nextState; - - if (r1.value >= density) { - step.trigger = false; - step.accent = false; - } else { - // Accent coin flip - const r2 = next(currentRng); - currentRng = r2.nextState; - step.accent = r2.value < accentProb; - } - } - } - - return { patternDesc: pattern, nextState: {} }; - } -} - -// ── 3. PitchWalker ────────────────────────────────────────────────── - -export class PitchWalker extends Primitive { - constructor() { - super('PitchWalker', 'generator', [ - { name: 'stepSize', default: 0.3, boundary: 'clamp' }, - { name: 'directionBias', default: 0.5, boundary: 'clamp' }, - { name: 'gravity', default: 0.3, boundary: 'clamp' }, - { name: 'range', default: 0.8, boundary: 'clamp' }, - ]); - - /** @private */ - this._position = 0.5; - } - - getState() { - return { position: this._position }; - } - - setState(savedState) { - if (savedState && typeof savedState.position === 'number') { - this._position = savedState.position; - } - } - - process(params, patternDesc, state, rng) { - const stepSize = params[0]; - const directionBias = params[1]; - const gravity = params[2]; - const range = params[3]; - - // Restore position from state if provided - let position = (state && typeof state.position === 'number') - ? state.position - : this._position; - - const pattern = createPattern(patternDesc.stepCount); - let currentRng = rng; - - // Use incoming pattern's triggers if available, otherwise all triggered - const srcSteps = patternDesc.steps; - - for (let i = 0; i < patternDesc.stepCount; i++) { - const triggered = srcSteps[i].trigger; - - if (triggered) { - // Random walk step - const r1 = next(currentRng); - currentRng = r1.nextState; - - // Direction: bias + gravity toward center - const gravityPull = (0.5 - position) * gravity; - const biasOffset = (directionBias - 0.5) * 2; // [-1, 1] - const direction = biasOffset + gravityPull; - - // Random component: [-1, 1] scaled by stepSize - const randomComponent = (r1.value * 2 - 1) * stepSize * range; - const delta = direction * stepSize * 0.5 + randomComponent; - - position = position + delta; - // Clamp to [0, 1] - if (position < 0) position = 0; - if (position > 1) position = 1; - - setStep(pattern, i, { trigger: true, pitch: position }); - } - // Untriggered steps keep default pitch, trigger=false - } - - this._position = position; - - return { - patternDesc: pattern, - nextState: { position: position }, - }; - } -} - -// ── 4. Ratchet ────────────────────────────────────────────────────── - -export class Ratchet extends Primitive { - constructor() { - super('Ratchet', 'timing', [ - { name: 'maxDivision', default: 0.5, boundary: 'clamp' }, - { name: 'probability', default: 0.5, boundary: 'clamp' }, - ]); - } - - process(params, patternDesc, state, rng) { - const maxDiv = mapToInt(params[0], 1, 4); - const probability = params[1]; - const pattern = clonePattern(patternDesc); - let currentRng = rng; - - for (let i = 0; i < pattern.stepCount; i++) { - const step = pattern.steps[i]; - if (step.trigger) { - const r1 = next(currentRng); - currentRng = r1.nextState; - - if (r1.value < probability && maxDiv > 1) { - // Pick a subdivision count in [2, maxDiv] - const r2 = nextInt(currentRng, 2, maxDiv); - currentRng = r2.nextState; - step.subdivisions = r2.value; - } - } - } - - return { patternDesc: pattern, nextState: {} }; - } -} - -// ── 5. SwingGroove ────────────────────────────────────────────────── - -export class SwingGroove extends Primitive { - constructor() { - super('SwingGroove', 'timing', [ - { name: 'swingAmount', default: 0.0, boundary: 'clamp' }, - { name: 'swingGrid', default: 0.0, boundary: 'clamp' }, - ]); - } - - process(params, patternDesc, state, rng) { - const swingAmount = params[0]; - const pattern = clonePattern(patternDesc); - - // Max swing = 0.33 (triplet feel) - const maxOffset = 0.33; - const offset = swingAmount * maxOffset; - - // Apply swing to every other step (odd-indexed steps) - for (let i = 1; i < pattern.stepCount; i += 2) { - pattern.steps[i].timeOffset = offset; - } - - return { patternDesc: pattern, nextState: {} }; - } -} - -// ── 6. DensityMorph ───────────────────────────────────────────────── - -export class DensityMorph extends Primitive { - constructor() { - super('DensityMorph', 'generator', [ - { name: 'density', default: 0.5, boundary: 'clamp' }, - { name: 'clustering', default: 0.0, boundary: 'clamp' }, - ]); - } - - process(params, patternDesc, state, rng) { - const density = params[0]; - const clustering = params[1]; - const stepCount = patternDesc.stepCount; - const pattern = createPattern(stepCount); - const numTriggers = Math.floor(density * stepCount); - - if (numTriggers <= 0) { - return { patternDesc: pattern, nextState: {} }; - } - if (numTriggers >= stepCount) { - for (let i = 0; i < stepCount; i++) { - setStep(pattern, i, { trigger: true }); - } - return { patternDesc: pattern, nextState: {} }; - } - - let currentRng = rng; - - if (clustering < 0.01) { - // Even spread: Euclidean-like placement - for (let i = 0; i < numTriggers; i++) { - const idx = Math.floor((i * stepCount) / numTriggers); - setStep(pattern, idx, { trigger: true }); - } - } else if (clustering > 0.99) { - // Full clustering: contiguous burst - const r1 = nextInt(currentRng, 0, stepCount - 1); - currentRng = r1.nextState; - const startPos = r1.value; - for (let i = 0; i < numTriggers; i++) { - const idx = (startPos + i) % stepCount; - setStep(pattern, idx, { trigger: true }); - } - } else { - // Interpolate: place triggers with clustering-dependent spread - // Use a "center of mass" approach: - // Pick a random center, then distribute triggers around it - // with spread inversely proportional to clustering - const r1 = next(currentRng); - currentRng = r1.nextState; - const center = r1.value * stepCount; - - // Spread factor: low clustering = large spread, high = tight - const spreadRadius = (1 - clustering) * stepCount * 0.5; - - // Score each step by distance from center (wrapping) - const scores = new Float32Array(stepCount); - for (let i = 0; i < stepCount; i++) { - // Wrapped distance from center - let dist = Math.abs(i - center); - if (dist > stepCount * 0.5) dist = stepCount - dist; - // Add small random jitter to break ties - const r2 = next(currentRng); - currentRng = r2.nextState; - scores[i] = dist / (spreadRadius + 0.001) + r2.value * 0.01; - } - - // Select the numTriggers steps with lowest scores - const indices = new Array(stepCount); - for (let i = 0; i < stepCount; i++) indices[i] = i; - indices.sort(function (a, b) { return scores[a] - scores[b]; }); - - for (let i = 0; i < numTriggers; i++) { - setStep(pattern, indices[i], { trigger: true }); - } - } - - return { patternDesc: pattern, nextState: {} }; - } -} - -// ── 7. IntervalLock ───────────────────────────────────────────────── - -const SCALES = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // chromatic - [0, 2, 4, 5, 7, 9, 11], // major - [0, 2, 3, 5, 7, 8, 10], // natural minor - [0, 2, 3, 5, 7, 8, 11], // harmonic minor - [0, 2, 4, 7, 9], // pentatonic major - [0, 3, 5, 7, 10], // pentatonic minor - [0, 3, 5, 6, 7, 10], // blues - [0, 2, 3, 5, 7, 9, 10], // dorian - [0, 2, 4, 5, 7, 9, 10], // mixolydian - [0, 2, 4, 6, 8, 10], // whole tone - [0, 2, 3, 5, 6, 8, 9, 11], // diminished -]; - -export class IntervalLock extends Primitive { - constructor() { - super('IntervalLock', 'converter', [ - { name: 'root', default: 0.0, boundary: 'clamp' }, - { name: 'mode', default: 0.0, boundary: 'clamp' }, - { name: 'octaveRange', default: 0.25, boundary: 'clamp' }, - ]); - } - - process(params, patternDesc, state, rng) { - const root = mapToInt(params[0], 0, 11); - const scaleIdx = mapToInt(params[1], 0, SCALES.length - 1); - const octaveRange = mapToInt(params[2], 1, 4); - const scale = SCALES[scaleIdx]; - - const pattern = clonePattern(patternDesc); - - // Build the full set of MIDI notes in this scale + root + range - const notes = []; - for (let oct = 0; oct < octaveRange; oct++) { - for (let i = 0; i < scale.length; i++) { - const midiNote = root + scale[i] + oct * 12; - if (midiNote <= 127) { - notes.push(midiNote); - } - } - } - - if (notes.length === 0) { - return { patternDesc: pattern, nextState: {} }; - } - - for (let i = 0; i < pattern.stepCount; i++) { - const step = pattern.steps[i]; - // Quantize pitch [0,1] to nearest note in our scale - const targetIdx = Math.round(step.pitch * (notes.length - 1)); - const clampedIdx = targetIdx < 0 ? 0 : targetIdx >= notes.length ? notes.length - 1 : targetIdx; - // Store as MIDI note / 127 to stay in [0,1] - step.pitch = notes[clampedIdx] / 127; - } - - return { patternDesc: pattern, nextState: {} }; - } -} - -// ── 8. VelocityShaper ─────────────────────────────────────────────── - -export class VelocityShaper extends Primitive { - constructor() { - super('VelocityShaper', 'processor', [ - { name: 'curveType', default: 0.0, boundary: 'clamp' }, - { name: 'depth', default: 0.5, boundary: 'clamp' }, - { name: 'phase', default: 0.0, boundary: 'wrap' }, - ]); - } - - process(params, patternDesc, state, rng) { - const curveIdx = mapToInt(params[0], 0, 4); - const depth = params[1]; - const phase = params[2]; - const pattern = clonePattern(patternDesc); - const stepCount = pattern.stepCount; - let currentRng = rng; - - for (let i = 0; i < stepCount; i++) { - const step = pattern.steps[i]; - if (!step.trigger) continue; - - // Phase-shifted position - const pos = ((i / stepCount) + phase) % 1; - let shapeValue; - - switch (curveIdx) { - case 0: // flat - shapeValue = 1.0; - break; - case 1: // accent-every-N (accent every 4th step) - shapeValue = ((i + Math.floor(phase * stepCount)) % 4 === 0) ? 1.0 : 0.5; - break; - case 2: // crescendo - shapeValue = pos; - break; - case 3: // decrescendo - shapeValue = 1.0 - pos; - break; - case 4: { // random - const r1 = next(currentRng); - currentRng = r1.nextState; - shapeValue = r1.value; - break; - } - default: - shapeValue = 1.0; - } - - // Apply depth: interpolate between uniform (1.0) and shaped - // depth=0 means all same velocity (base), depth=1 means full shape - const baseVelocity = 0.7; - const shaped = shapeValue; - step.velocity = baseVelocity * (1 - depth) + shaped * depth; - - // Clamp - if (step.velocity < 0) step.velocity = 0; - if (step.velocity > 1) step.velocity = 1; - } - - return { patternDesc: pattern, nextState: {} }; - } -} - -// ── Registry ──────────────────────────────────────────────────────── - -export const PRIMITIVE_REGISTRY = { - EuclideanRhythm: EuclideanRhythm, - ProbabilityGate: ProbabilityGate, - PitchWalker: PitchWalker, - Ratchet: Ratchet, - SwingGroove: SwingGroove, - DensityMorph: DensityMorph, - IntervalLock: IntervalLock, - VelocityShaper: VelocityShaper, -}; diff --git a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/sequencer.js b/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/sequencer.js deleted file mode 100644 index 31d1d84..0000000 --- a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/sequencer.js +++ /dev/null @@ -1,304 +0,0 @@ -/** - * ShapeSeq Engine — central orchestrator - * - * Wires together the sequence MLP, param mapping, primitive chain, - * projection layer, clock engine, and C15 bridge. - * - * Main loop (triggered by setSequenceInputs): - * 1. Forward inputs to sequenceIML - * 2. Run MLP inference to get 16 outputs - * 3. Map 16 outputs to N primitive params via param-map - * 4. Evaluate the chain to produce a pattern description - * 5. Apply projection transforms - * 6. Schedule the pattern on the clock - * - * Bridge integration: - * - Subscribes to seq.noteOn / seq.noteOff on the event bus - * - Forwards to C15Bridge.noteOn / noteOff - * - Tracks active notes to avoid orphans - * - * @module shapeseq/sequencer - */ - -import { createSequenceIML, SEQ_N_OUTPUTS } from './seq-iml.js'; -import { Chain } from './chain.js'; -import { ClockEngine } from './clock.js'; -import { map } from './param-map.js'; -import { createProjectionChain, applyProjection, PRESETS } from './projection.js'; -import { SEQ } from './event-bus.js'; -import { - EuclideanRhythm, - ProbabilityGate, - PitchWalker, - IntervalLock, - VelocityShaper, -} from './primitives.js'; - -// ── Defaults ───────────────────────────────────────────────────────── - -const DEFAULT_BPM = 120; -const DEFAULT_STEP_COUNT = 8; -const DEFAULT_MASTER_SEED = 42; -const DEFAULT_SPREAD = 0.6; - -// ── ShapeSeqEngine ─────────────────────────────────────────────────── - -export class ShapeSeqEngine { - /** - * @param {{ audioContext: AudioContext, eventBus: import('./event-bus.js').EventBus, c15Bridge: import('../synth/c15-bridge.js').C15Bridge }} opts - */ - constructor({ audioContext, eventBus, c15Bridge }) { - if (!audioContext) throw new TypeError('ShapeSeqEngine requires an audioContext'); - if (!eventBus) throw new TypeError('ShapeSeqEngine requires an eventBus'); - if (!c15Bridge) throw new TypeError('ShapeSeqEngine requires a c15Bridge'); - - /** @private */ this._audioCtx = audioContext; - /** @private */ this._bus = eventBus; - /** @private */ this._c15 = c15Bridge; - - /** @private */ this._sequenceIML = null; - /** @private */ this._chain = null; - /** @private */ this._clock = null; - /** @private */ this._projectionChain = null; - - /** @private */ this._stepCount = DEFAULT_STEP_COUNT; - /** @private */ this._masterSeed = DEFAULT_MASTER_SEED; - /** @private */ this._playing = false; - /** @private */ this._initialized = false; - - // Track active notes for orphan prevention - /** @private @type {Set} */ - this._activeNotes = new Set(); - - // Bound handlers for event bus (stored for cleanup) - /** @private */ - this._onNoteOn = (data) => this._handleNoteOn(data); - /** @private */ - this._onNoteOff = (data) => this._handleNoteOff(data); - } - - // ── Lifecycle ────────────────────────────────────────────────────── - - /** - * Initialize all subsystems: create sequence IML, default chain, - * clock, and projection chain. Must be called before start(). - */ - async init() { - // 1. Create the sequence MLP - this._sequenceIML = await createSequenceIML(); - - // Randomize weights with default spread - this._sequenceIML.drawWeights(DEFAULT_SPREAD); - - // 2. Create the default primitive chain - this._chain = new Chain(); - this._chain.addPrimitive(new EuclideanRhythm()); - this._chain.addPrimitive(new ProbabilityGate()); - this._chain.addPrimitive(new PitchWalker()); - this._chain.addPrimitive(new IntervalLock()); - this._chain.addPrimitive(new VelocityShaper()); - this._chain.setMasterSeed(this._masterSeed); - - // 3. Set up the clock - this._clock = new ClockEngine(this._audioCtx, this._bus); - this._clock.bpm = DEFAULT_BPM; - - // 4. Create default projection chain (expressive preset) - const result = createProjectionChain(PRESETS.expressive); - if (!result.valid) { - throw new Error('Default projection chain invalid: ' + result.error); - } - this._projectionChain = result; - - // 5. Subscribe to event bus for C15 bridge integration - this._bus.on(SEQ.NOTE_ON, this._onNoteOn); - this._bus.on(SEQ.NOTE_OFF, this._onNoteOff); - - this._initialized = true; - } - - /** - * Start the clock. Requires init() to have been called. - */ - start() { - if (!this._initialized) { - throw new Error('ShapeSeqEngine.start() called before init()'); - } - if (this._playing) return; - - this._playing = true; - this._clock.start(); - } - - /** - * Stop the clock and release all active notes. - */ - stop() { - if (!this._playing) return; - - this._playing = false; - this._clock.stop(); - this._releaseAllNotes(); - } - - /** - * Full cleanup: stop playback, unsubscribe from events, destroy IML. - */ - destroy() { - this.stop(); - - // Unsubscribe from event bus - this._bus.off(SEQ.NOTE_ON, this._onNoteOn); - this._bus.off(SEQ.NOTE_OFF, this._onNoteOff); - - // Destroy the sequence IML instance - if (this._sequenceIML) { - this._sequenceIML.destroy(); - this._sequenceIML = null; - } - - this._chain = null; - this._clock = null; - this._projectionChain = null; - this._initialized = false; - } - - // ── Configuration ────────────────────────────────────────────────── - - /** - * Update the clock tempo. - * @param {number} bpm - */ - setTempo(bpm) { - if (this._clock) { - this._clock.setTempo(bpm); - } - } - - /** - * Set the number of steps in the generated pattern. - * @param {number} count - */ - setStepCount(count) { - const c = Math.max(1, count | 0); - this._stepCount = c; - } - - /** - * Set the projection preset by name. - * @param {'expressive'|'percussive'|'fullRange'} presetName - */ - setProjectionPreset(presetName) { - const preset = PRESETS[presetName]; - if (!preset) { - throw new Error('Unknown projection preset: ' + presetName); - } - const result = createProjectionChain(preset); - if (!result.valid) { - throw new Error('Projection chain invalid: ' + result.error); - } - this._projectionChain = result; - } - - // ── Chain access (for UI binding) ────────────────────────────────── - - /** @returns {Chain} */ - getChain() { return this._chain; } - - /** @returns {ClockEngine} */ - getClock() { return this._clock; } - - /** @returns {WasmIML} */ - getSequenceIML() { return this._sequenceIML; } - - // ── Input routing ────────────────────────────────────────────────── - - /** - * Feed new input values to the sequence MLP and run the full pipeline: - * MLP inference -> param mapping -> chain evaluation -> projection -> clock scheduling. - * - * Call this each frame with the routed input values (e.g., [x, y]). - * - * @param {number[]} values - input array (typically [x, y]) - */ - setSequenceInputs(values) { - if (!this._initialized || !this._sequenceIML) return; - - // 1. Forward inputs to the sequence IML - this._sequenceIML.setInputs(values); - - // 2. Run MLP inference - this._sequenceIML.process(); - - // 3. Get the 16 MLP outputs - const mlpOutputs = this._sequenceIML.getOutputs(); - - // 4. Map 16 outputs to N primitive params - const paramCount = this._chain.totalParamCount; - const mappedParams = map(mlpOutputs, paramCount); - - // 5. Evaluate the chain to produce a pattern description - const patternDesc = this._chain.evaluate(mappedParams, this._stepCount, this._masterSeed); - - // 6. Apply projection transforms - const projectedPattern = applyProjection(this._projectionChain, patternDesc); - - // 7. Schedule the pattern on the clock - this._clock.schedulePattern(projectedPattern); - } - - // ── ML control ───────────────────────────────────────────────────── - - /** @returns {boolean} */ - get isPlaying() { - return this._playing; - } - - // ── Bridge integration (private) ─────────────────────────────────── - - /** - * Handle seq.noteOn events from the event bus. - * Converts [0,1] pitch to MIDI note number and forwards to C15. - * - * @private - * @param {Object} data - { pitch, velocity, stepIndex, time, accent, isSubdivision } - */ - _handleNoteOn(data) { - // pitch comes from the projection layer; after RangeMap it's already - // in MIDI note range (e.g., 48-84). Round to nearest integer. - const midiNote = Math.round(data.pitch) | 0; - const velocity = data.velocity; - - // Clamp to valid MIDI range - const note = midiNote < 0 ? 0 : midiNote > 127 ? 127 : midiNote; - const vel = velocity < 0 ? 0 : velocity > 1 ? 1 : velocity; - - this._c15.noteOn(note, vel); - this._activeNotes.add(note); - } - - /** - * Handle seq.noteOff events from the event bus. - * - * @private - * @param {Object} data - { pitch, velocity, stepIndex, time } - */ - _handleNoteOff(data) { - const midiNote = Math.round(data.pitch) | 0; - const note = midiNote < 0 ? 0 : midiNote > 127 ? 127 : midiNote; - - this._c15.noteOff(note); - this._activeNotes.delete(note); - } - - /** - * Release all currently active notes to avoid orphaned noteOns. - * @private - */ - _releaseAllNotes() { - for (const note of this._activeNotes) { - this._c15.noteOff(note); - } - this._activeNotes.clear(); - } -} diff --git a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/step-viz.js b/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/step-viz.js deleted file mode 100644 index 8682c0c..0000000 --- a/.claude/worktrees/agent-ae87fe47/playground/js/shapeseq/step-viz.js +++ /dev/null @@ -1,328 +0,0 @@ -/** - * ShapeSeq Circular Step Visualizer - * - * Renders steps arranged in a circle (heptagon, tridecagon, etc.) - * with pitch mapped to radial distance, velocity to node size, - * and accent to color brightness. - * - * Designed for 60fps rendering — no allocations in the render loop. - * Port-ready: explicit state, no closures in hot paths. - * - * @module shapeseq/step-viz - */ - -import { SEQ } from './event-bus.js'; - -// ── Constants (pre-allocated, shared across instances) ────────────── - -const TWO_PI = Math.PI * 2; -const HALF_PI = Math.PI * 0.5; - -// Color constants -const COLOR_INACTIVE = 'rgba(255, 255, 255, 0.15)'; -const COLOR_ACTIVE = '#00ccff'; -const COLOR_CURRENT = '#ff6a00'; -const COLOR_ACCENT = '#ffcc00'; -const COLOR_BG = '#0d0d0d'; - -// Glow colors (pre-computed rgba strings) -const GLOW_ACTIVE = 'rgba(0, 204, 255, 0.3)'; -const GLOW_CURRENT = 'rgba(255, 106, 0, 0.4)'; -const GLOW_ACCENT = 'rgba(255, 204, 0, 0.35)'; - -// Layout -const PADDING_RATIO = 0.08; // canvas padding as fraction of min dimension -const OUTER_RADIUS_RATIO = 0.90; // outer ring at 90% of available radius -const INNER_RADIUS_RATIO = 0.30; // inner ring at 30% of available radius - -// Node sizing -const NODE_MIN_RADIUS = 4; -const NODE_MAX_RADIUS = 18; -const NODE_OUTLINE_WIDTH = 1.5; - -// Current-step indicator -const INDICATOR_EXTRA_RADIUS = 8; -const INDICATOR_LINE_WIDTH = 2; - -// Center dot -const CENTER_DOT_RADIUS = 3; - -// ── StepVisualizer ────────────────────────────────────────────────── - -export class StepVisualizer { - /** - * @param {{ canvas: HTMLCanvasElement, eventBus: import('./event-bus.js').EventBus }} opts - */ - constructor({ canvas, eventBus }) { - this._canvas = canvas; - this._ctx = canvas.getContext('2d'); - this._bus = eventBus; - - // State - this._pattern = null; // current pattern description - this._currentStep = -1; // playback position (-1 = none) - this._width = 0; - this._height = 0; - this._cx = 0; // center x - this._cy = 0; // center y - this._maxRadius = 0; // max ring radius in pixels - - // Pre-allocated arrays to avoid per-frame allocation. - // Sized lazily when pattern is set. - this._nodeX = null; // Float64Array — screen x per step - this._nodeY = null; // Float64Array — screen y per step - this._nodeR = null; // Float64Array — rendered radius per step - - // Interaction - this._tapCallback = null; - this._onPointerDown = this._handlePointerDown.bind(this); - - // Event bus subscription - this._onStep = this._handleStep.bind(this); - this._bus.on(SEQ.STEP, this._onStep); - - // Canvas interaction - this._canvas.addEventListener('pointerdown', this._onPointerDown); - - // Initial sizing - this.resize(canvas.getBoundingClientRect().width, canvas.getBoundingClientRect().height); - } - - // ── Public API ────────────────────────────────────────────────── - - /** - * Update the displayed pattern. - * @param {{ steps: Array, stepCount: number, metadata: Object }} patternDesc - */ - setPattern(patternDesc) { - this._pattern = patternDesc; - const count = patternDesc ? patternDesc.stepCount : 0; - - // (Re)allocate coordinate buffers only when step count changes - if (!this._nodeX || this._nodeX.length !== count) { - this._nodeX = new Float64Array(count); - this._nodeY = new Float64Array(count); - this._nodeR = new Float64Array(count); - } - - this._computeLayout(); - } - - /** - * Update the playback position. - * @param {number} index — step index (0-based), or -1 for none - */ - setCurrentStep(index) { - this._currentStep = index; - } - - /** - * Draw one frame. Call from requestAnimationFrame. - */ - render() { - const ctx = this._ctx; - const w = this._width; - const h = this._height; - - // Clear - ctx.fillStyle = COLOR_BG; - ctx.fillRect(0, 0, w, h); - - if (!this._pattern || this._pattern.stepCount === 0) return; - - const steps = this._pattern.steps; - const count = this._pattern.stepCount; - const cx = this._cx; - const cy = this._cy; - - // Draw connecting ring (subtle guide circle at midpoint radius) - const midRadius = this._maxRadius * ((OUTER_RADIUS_RATIO + INNER_RADIUS_RATIO) * 0.5); - ctx.beginPath(); - ctx.arc(cx, cy, midRadius, 0, TWO_PI); - ctx.strokeStyle = 'rgba(255, 255, 255, 0.06)'; - ctx.lineWidth = 1; - ctx.stroke(); - - // Center dot - ctx.beginPath(); - ctx.arc(cx, cy, CENTER_DOT_RADIUS, 0, TWO_PI); - ctx.fillStyle = 'rgba(255, 255, 255, 0.2)'; - ctx.fill(); - - // Draw connector line from center to current step - if (this._currentStep >= 0 && this._currentStep < count) { - const si = this._currentStep; - ctx.beginPath(); - ctx.moveTo(cx, cy); - ctx.lineTo(this._nodeX[si], this._nodeY[si]); - ctx.strokeStyle = 'rgba(255, 106, 0, 0.2)'; - ctx.lineWidth = INDICATOR_LINE_WIDTH; - ctx.stroke(); - } - - // Draw step nodes - for (let i = 0; i < count; i++) { - const step = steps[i]; - const nx = this._nodeX[i]; - const ny = this._nodeY[i]; - const nr = this._nodeR[i]; - const isCurrent = i === this._currentStep; - - if (isCurrent) { - // Outer glow for current step - ctx.beginPath(); - ctx.arc(nx, ny, nr + INDICATOR_EXTRA_RADIUS, 0, TWO_PI); - ctx.fillStyle = GLOW_CURRENT; - ctx.fill(); - } - - if (step.trigger) { - // Glow behind active nodes - if (!isCurrent) { - const glowColor = step.accent ? GLOW_ACCENT : GLOW_ACTIVE; - ctx.beginPath(); - ctx.arc(nx, ny, nr + 4, 0, TWO_PI); - ctx.fillStyle = glowColor; - ctx.fill(); - } - - // Filled node - ctx.beginPath(); - ctx.arc(nx, ny, nr, 0, TWO_PI); - if (isCurrent) { - ctx.fillStyle = COLOR_CURRENT; - } else if (step.accent) { - ctx.fillStyle = COLOR_ACCENT; - } else { - ctx.fillStyle = COLOR_ACTIVE; - } - ctx.fill(); - } else { - // Dim outline only for untriggered steps - ctx.beginPath(); - ctx.arc(nx, ny, nr, 0, TWO_PI); - ctx.strokeStyle = isCurrent ? COLOR_CURRENT : COLOR_INACTIVE; - ctx.lineWidth = NODE_OUTLINE_WIDTH; - ctx.stroke(); - } - } - } - - /** - * Handle canvas resize. - * @param {number} width — CSS pixels - * @param {number} height — CSS pixels - */ - resize(width, height) { - const dpr = window.devicePixelRatio || 1; - this._canvas.width = width * dpr; - this._canvas.height = height * dpr; - this._ctx.setTransform(dpr, 0, 0, dpr, 0, 0); - - this._width = width; - this._height = height; - this._cx = width * 0.5; - this._cy = height * 0.5; - - const minDim = Math.min(width, height); - this._maxRadius = (minDim * 0.5) * (1 - PADDING_RATIO * 2); - - this._computeLayout(); - } - - /** - * Register a tap callback. - * @param {function(number): void} callback — receives step index - */ - onStepTap(callback) { - this._tapCallback = callback; - } - - /** - * Unsubscribe from event bus and remove DOM listeners. - */ - destroy() { - this._bus.off(SEQ.STEP, this._onStep); - this._canvas.removeEventListener('pointerdown', this._onPointerDown); - this._tapCallback = null; - this._pattern = null; - } - - // ── Private ───────────────────────────────────────────────────── - - /** - * Re-compute node positions from current pattern + canvas size. - * Called when pattern or size changes — NOT per frame. - */ - _computeLayout() { - if (!this._pattern || !this._nodeX) return; - - const steps = this._pattern.steps; - const count = this._pattern.stepCount; - const cx = this._cx; - const cy = this._cy; - const maxR = this._maxRadius; - const outerR = maxR * OUTER_RADIUS_RATIO; - const innerR = maxR * INNER_RADIUS_RATIO; - const radiusRange = outerR - innerR; - - // Angular step: start at top (-PI/2), go clockwise - const angleStep = TWO_PI / count; - - for (let i = 0; i < count; i++) { - const step = steps[i]; - const angle = -HALF_PI + angleStep * i; - - // Pitch -> radial distance: low pitch = outer, high pitch = inner - const pitchNorm = step.pitch; // 0 = low (outer), 1 = high (inner) - const r = outerR - pitchNorm * radiusRange; - - this._nodeX[i] = cx + Math.cos(angle) * r; - this._nodeY[i] = cy + Math.sin(angle) * r; - - // Velocity -> node size - this._nodeR[i] = NODE_MIN_RADIUS + step.velocity * (NODE_MAX_RADIUS - NODE_MIN_RADIUS); - } - } - - /** - * Handle seq.step events from the event bus. - */ - _handleStep(data) { - if (typeof data.stepIndex === 'number') { - this._currentStep = data.stepIndex; - } - } - - /** - * Handle pointer down on the canvas for tap interaction. - */ - _handlePointerDown(e) { - if (!this._tapCallback || !this._pattern) return; - - const rect = this._canvas.getBoundingClientRect(); - const px = e.clientX - rect.left; - const py = e.clientY - rect.top; - const count = this._pattern.stepCount; - - // Find closest step within hit radius - let bestIdx = -1; - let bestDistSq = Infinity; - - for (let i = 0; i < count; i++) { - const dx = px - this._nodeX[i]; - const dy = py - this._nodeY[i]; - const distSq = dx * dx + dy * dy; - // Hit area is the node radius + some tolerance - const hitR = this._nodeR[i] + 12; - if (distSq < hitR * hitR && distSq < bestDistSq) { - bestDistSq = distSq; - bestIdx = i; - } - } - - if (bestIdx >= 0) { - this._tapCallback(bestIdx); - } - } -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cd6248..c0ce74f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,9 @@ name: CI on: push: - branches: [main, port-solidjs] + branches: [main] pull_request: - branches: [main, port-solidjs] + branches: [main] workflow_dispatch: jobs: diff --git a/.github/workflows/vcv-plugin.yml b/.github/workflows/vcv-plugin.yml index 153110a..7d8af33 100644 --- a/.github/workflows/vcv-plugin.yml +++ b/.github/workflows/vcv-plugin.yml @@ -12,7 +12,7 @@ name: VCV Rack plugin (cross-platform) on: push: - branches: [main, feat/manifold-mission, feat/vcv-dist] + branches: [main] paths: - 'vcv/**' - '.github/workflows/vcv-plugin.yml' diff --git a/NISPS_CORE_EXTRACTION_PLAN.md b/NISPS_CORE_EXTRACTION_PLAN.md deleted file mode 100644 index ff16da8..0000000 --- a/NISPS_CORE_EXTRACTION_PLAN.md +++ /dev/null @@ -1,140 +0,0 @@ -# NISPS Core Extraction Plan - -Extract a platform-agnostic C++20 controller library from MEMLNaut-NISPS. This is **not** a synth or audio engine - it's a parameter mapping engine: control data in → ML → control data out. Use it to drive synths, effects, lights, robots, whatever. - -> **Note**: Originally planned as C++17, upgraded to C++20 during implementation to use `std::span` for efficient array views. - -## What This Is - -NISPS core takes N input parameters (joystick position, sensor data, audio features) and maps them to M output parameters through an interactively-trained neural network. Users teach it by example: "when I'm here in input space, I want these output values." - -## Dependencies to Remove - -| Dependency | Replacement | -|------------|-------------| -| `Serial.print*` | Optional log callback | -| `queue_t` (Pico SDK) | Not needed (single-threaded) | -| `WString.h` (Arduino) | `std::string` | -| `__force_inline`, `AUDIO_MEM` | No-op macros | - -## Structure - -``` -nisps/ -├── mlp.hpp # MLP implementation (from memlp, cleaned) -├── dataset.hpp # Training dataset -├── iml.hpp # Interactive ML engine (~200 lines) -└── voice_space.hpp # Optional: example parameter mappings -``` - -## Core API - -```cpp -namespace nisps { - -template -class IML { -public: - IML(size_t n_inputs, size_t n_outputs, - std::vector hidden_layers = {10, 10, 14}); - - // Input - void set_input(size_t index, Float value); - void set_inputs(const Float* values, size_t count); - - // Output (valid after process()) - const Float* get_outputs() const; - size_t num_outputs() const; - - // Runtime - void process(); // Run inference, call at control rate - - // Training workflow - enum class Mode { Inference, Training }; - void set_mode(Mode mode); - void save_example(); // Store current input→output as training pair - void clear_dataset(); - void randomise_weights(); // For exploration in training mode - void train(); // Blocking, runs on current dataset - - // Optional - using LogFn = void(*)(const char*); - void set_logger(LogFn fn); -}; - -} // namespace nisps -``` - -## Usage - -```cpp -#include "nisps/iml.hpp" - -nisps::IML iml(3, 24); // 3 inputs (x,y,z), 24 outputs - -// Control loop -void update(float x, float y, float z) { - iml.set_input(0, x); - iml.set_input(1, y); - iml.set_input(2, z); - iml.process(); - - const float* params = iml.get_outputs(); - my_synth.set_filter_cutoff(params[0] * 10000.f); - my_synth.set_resonance(params[1]); - // ... etc -} - -// Training (triggered by user interaction) -void on_user_saves_position() { - iml.save_example(); -} - -void on_user_exits_training_mode() { - iml.set_mode(nisps::IML<>::Mode::Inference); - // This triggers training internally -} -``` - -## Voice Spaces (Optional) - -Voice spaces are just functions that interpret the raw 0-1 output parameters. Not part of core, but useful as examples: - -```cpp -// User-defined mapping -void apply_neve_style(const float* params, MyChannelStrip& strip) { - strip.pre_gain = 0.5f + params[0] * params[0] * 4.f; - strip.hp_freq = 30.f + params[8] * params[8] * 270.f; - strip.comp_threshold = 20.f + params[10] * -40.f; - // ... etc -} -``` - -## Phases - -### Phase 1: Get memlp building standalone (1-2 days) - -1. Copy `memlp` source into `nisps/` -2. Remove `Serial.print` calls (or stub them) -3. Remove Arduino `String` usage -4. Verify it compiles with g++/clang - -### Phase 2: Wrap in IML interface (2-3 days) - -1. Create `iml.hpp` with the API above -2. Port state machine logic from `IMLInterface.hpp` -3. Simple test: train on XOR, verify inference works - -### Phase 3: Example integration (1-2 days) - -1. Command-line example that reads CSV input, outputs CSV -2. Or: minimal JUCE/SDL example with mouse input - -**Total: ~1 week to something usable** - -## Later (only if needed) - -- Model serialization (save/load trained weights) -- Thread-safe parameter updates -- Python bindings -- WASM build diff --git a/NISPS_CORE_TASKS.md b/NISPS_CORE_TASKS.md deleted file mode 100644 index afa4066..0000000 --- a/NISPS_CORE_TASKS.md +++ /dev/null @@ -1,678 +0,0 @@ -# NISPS Core Extraction - Task Graph - -Atomic tasks for extracting nisps-core. Each task is standalone and requires no decisions. - ---- - -## Repository Setup - -### TASK-001: Create nisps-core directory structure -**Blocked by**: None -**Description**: Create the nisps-core directory with the required structure. -**Actions**: -1. Create directory: `nisps-core/` -2. Create directory: `nisps-core/include/nisps/` -3. Create directory: `nisps-core/test/` -4. Create directory: `nisps-core/examples/` -**Verification**: Directories exist. - -### TASK-002: Create CMakeLists.txt for nisps-core -**Blocked by**: TASK-001 -**Description**: Create a minimal CMakeLists.txt that builds the library as header-only with test target. -**Actions**: Create `nisps-core/CMakeLists.txt` with this exact content: -```cmake -cmake_minimum_required(VERSION 3.14) -project(nisps-core VERSION 0.1.0 LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Header-only library -add_library(nisps INTERFACE) -target_include_directories(nisps INTERFACE - $ - $ -) - -# Tests -option(NISPS_BUILD_TESTS "Build tests" ON) -if(NISPS_BUILD_TESTS) - enable_testing() - add_subdirectory(test) -endif() -``` -**Verification**: File exists with exact content. - -### TASK-003: Create test CMakeLists.txt -**Blocked by**: TASK-002 -**Description**: Create test/CMakeLists.txt for building tests. -**Actions**: Create `nisps-core/test/CMakeLists.txt` with this exact content: -```cmake -add_executable(nisps_test main.cpp) -target_link_libraries(nisps_test PRIVATE nisps) -add_test(NAME nisps_test COMMAND nisps_test) -``` -**Verification**: File exists with exact content. - -### TASK-004: Create placeholder test file -**Blocked by**: TASK-003 -**Description**: Create a minimal test file that includes the main header. -**Actions**: Create `nisps-core/test/main.cpp` with this exact content: -```cpp -#include -#include - -int main() { - std::cout << "nisps-core tests placeholder\n"; - return 0; -} -``` -**Verification**: File exists with exact content. - ---- - -## Phase 1: Extract MLP Core - -### TASK-010: Copy Utils.h with Arduino code removed -**Blocked by**: TASK-001 -**Description**: Copy `src/memlp/Utils.h` to `nisps-core/include/nisps/utils.hpp`, removing Arduino-specific code. -**Actions**: -1. Copy the file -2. Remove the `#ifdef ARDUINO` block (lines related to ENABLE_SAVE) -3. Remove the `extern "C" int getentropy` declaration at the end -4. Change header guard from `UTILS_H` to `NISPS_UTILS_HPP` -5. Wrap everything in `namespace nisps { ... }` - -**Changes to make** (be exact): -- Line 1: Change `#ifndef UTILS_H` to `#ifndef NISPS_UTILS_HPP` -- Line 2: Change `#define UTILS_H` to `#define NISPS_UTILS_HPP` -- Remove lines 38-45 (the `#if defined(_WIN32)...` and `#ifdef ARDUINO` ENABLE_SAVE blocks) -- After line 57 (`namespace utils {`), keep as-is (utils is a sub-namespace) -- Before the final `#endif`, add closing brace for nisps namespace -- Remove the last line `extern "C" int getentropy...` -- Add `namespace nisps {` after the includes, before `enum ACTIVATION_FUNCTIONS` -- Add `} // namespace nisps` before `#endif // NISPS_UTILS_HPP` - -**Verification**: File compiles with `g++ -std=c++17 -fsyntax-only nisps-core/include/nisps/utils.hpp` - -### TASK-011: Copy Loss.h -**Blocked by**: TASK-001 -**Description**: Copy `src/memlp/Loss.h` to `nisps-core/include/nisps/loss.hpp`. -**Actions**: -1. Copy the file -2. Change header guard from `LOSS_H` to `NISPS_LOSS_HPP` -3. Wrap everything in `namespace nisps { ... }` -4. Update include from `"Utils.h"` to `"utils.hpp"` - -**Verification**: File compiles with `g++ -std=c++17 -fsyntax-only -I nisps-core/include nisps-core/include/nisps/loss.hpp` - -### TASK-012: Copy Node.h -**Blocked by**: TASK-010 -**Description**: Copy `src/memlp/Node.h` to `nisps-core/include/nisps/node.hpp`. -**Actions**: -1. Copy the file -2. Change header guard from `NODE_H` to `NISPS_NODE_HPP` -3. Wrap everything in `namespace nisps { ... }` -4. Update include from `"Utils.h"` to `"utils.hpp"` - -**Verification**: File compiles with `g++ -std=c++17 -fsyntax-only -I nisps-core/include nisps-core/include/nisps/node.hpp` - -### TASK-013: Copy Layer.h -**Blocked by**: TASK-012 -**Description**: Copy `src/memlp/Layer.h` to `nisps-core/include/nisps/layer.hpp`. -**Actions**: -1. Copy the file -2. Change header guard from `LAYER_H` to `NISPS_LAYER_HPP` -3. Wrap everything in `namespace nisps { ... }` -4. Update include from `"Node.h"` to `"node.hpp"` - -**Verification**: File compiles with `g++ -std=c++17 -fsyntax-only -I nisps-core/include nisps-core/include/nisps/layer.hpp` - -### TASK-014: Copy Sample.h -**Blocked by**: TASK-001 -**Description**: Copy `src/memlp/Sample.h` to `nisps-core/include/nisps/sample.hpp`. -**Actions**: -1. Copy the file -2. Change header guard from `SAMPLE_H` to `NISPS_SAMPLE_HPP` -3. Wrap everything in `namespace nisps { ... }` - -**Verification**: File compiles with `g++ -std=c++17 -fsyntax-only nisps-core/include/nisps/sample.hpp` - -### TASK-015: Copy MLP.h with Arduino code removed -**Blocked by**: TASK-011, TASK-013, TASK-014 -**Description**: Copy `src/memlp/MLP.h` to `nisps-core/include/nisps/mlp.hpp`, removing Arduino-specific code. -**Actions**: -1. Copy the file -2. Change header guard from `MLP_H` to `NISPS_MLP_HPP` -3. **Remove lines 17-51** (the entire `#ifdef ARDUINO` block including Serial debug macros and SD includes) -4. Add these lines after the header guard instead: -```cpp -// Debug macros (no-op by default, override before including if needed) -#ifndef NISPS_DEBUG_PRINT -#define NISPS_DEBUG_PRINT(...) -#define NISPS_DEBUG_PRINTLN(...) -#define NISPS_DEBUG_PRINTF(...) -#endif -``` -5. Update includes: - - `"Layer.h"` → `"layer.hpp"` - - `"Utils.h"` → `"utils.hpp"` - - `"Loss.h"` → `"loss.hpp"` - - `"Sample.h"` → `"sample.hpp"` -6. Remove `#if ENABLE_SAVE` blocks (lines 94-96, 99-113, 115-119) - remove the conditionals but **keep** the function declarations -7. Remove `#if ENABLE_SAVE_SD` block entirely (lines 115-119) -8. Wrap everything in `namespace nisps { ... }` -9. Replace `MLP_DEBUG_PRINT` with `NISPS_DEBUG_PRINT`, `MLP_DEBUG_PRINTLN` with `NISPS_DEBUG_PRINTLN`, `MLP_DEBUG_PRINTF` with `NISPS_DEBUG_PRINTF` - -**Verification**: File compiles with `g++ -std=c++17 -fsyntax-only -I nisps-core/include nisps-core/include/nisps/mlp.hpp` - -### TASK-016: Copy MLP.cpp with Arduino code removed -**Blocked by**: TASK-015 -**Description**: Copy `src/memlp/MLP.cpp` to `nisps-core/include/nisps/mlp_impl.hpp` as inline implementation. -**Actions**: -1. Copy the file -2. Remove `#include "MLP.h"` (it will be included from mlp.hpp) -3. Add header guard `#ifndef NISPS_MLP_IMPL_HPP` / `#define NISPS_MLP_IMPL_HPP` -4. Wrap everything in `namespace nisps { ... }` -5. Remove all `#ifdef ARDUINO` / `#if ENABLE_SAVE` / `#if ENABLE_SAVE_SD` conditional blocks -6. For functions inside removed conditionals: keep the functions but remove the `#if` guards -7. Replace any `Serial.print*` calls with `NISPS_DEBUG_PRINT*` equivalents -8. At the end of `nisps-core/include/nisps/mlp.hpp`, add: `#include "mlp_impl.hpp"` - -**Verification**: A test file that instantiates `nisps::MLP` compiles and links. - -### TASK-017: Copy Dataset.hpp -**Blocked by**: TASK-001 -**Description**: Copy `src/memlp/Dataset.hpp` to `nisps-core/include/nisps/dataset.hpp`. -**Actions**: -1. Copy the file -2. Change header guard from `__DATASET_HPP__` to `NISPS_DATASET_HPP` -3. Wrap everything in `namespace nisps { ... }` - -**Verification**: File compiles with `g++ -std=c++17 -fsyntax-only nisps-core/include/nisps/dataset.hpp` - -### TASK-018: Copy Dataset.cpp as inline implementation -**Blocked by**: TASK-017 -**Description**: Copy `src/memlp/Dataset.cpp` to `nisps-core/include/nisps/dataset_impl.hpp`. -**Actions**: -1. Copy the file -2. Remove `#include "Dataset.hpp"` -3. Add header guard `#ifndef NISPS_DATASET_IMPL_HPP` / `#define NISPS_DATASET_IMPL_HPP` -4. Wrap everything in `namespace nisps { ... }` -5. Make all functions `inline` -6. At the end of `nisps-core/include/nisps/dataset.hpp`, add: `#include "dataset_impl.hpp"` - -**Verification**: A test file that instantiates `nisps::Dataset` compiles and links. - ---- - -## Phase 2: Create IML Interface - -### TASK-020: Create iml.hpp header -**Blocked by**: TASK-016, TASK-018 -**Description**: Create the main IML class header based on IMLInterface.hpp. -**Actions**: Create `nisps-core/include/nisps/iml.hpp` with this exact content: -```cpp -#ifndef NISPS_IML_HPP -#define NISPS_IML_HPP - -#include "mlp.hpp" -#include "dataset.hpp" -#include -#include -#include - -namespace nisps { - -template -class IML { -public: - enum class Mode { Inference, Training }; - - using LogFn = void(*)(const char*); - - IML(size_t n_inputs, size_t n_outputs, - std::vector hidden_layers = {10, 10, 14}, - size_t max_iterations = 1000, - Float learning_rate = 1.0f, - Float convergence_threshold = 0.00001f); - - // Input - void set_input(size_t index, Float value); - void set_inputs(const Float* values, size_t count); - - // Output (valid after process()) - const Float* get_outputs() const; - size_t num_inputs() const { return n_inputs_; } - size_t num_outputs() const { return n_outputs_; } - - // Runtime - void process(); - - // Training workflow - void set_mode(Mode mode); - Mode get_mode() const { return mode_; } - void save_example(); - void clear_dataset(); - void randomise_weights(); - - // Optional logging - void set_logger(LogFn fn) { log_fn_ = fn; } - -private: - void log(const char* msg) const { - if (log_fn_) log_fn_(msg); - } - void train(); - - size_t n_inputs_; - size_t n_outputs_; - size_t max_iterations_; - Float learning_rate_; - Float convergence_threshold_; - - Mode mode_ = Mode::Inference; - bool input_updated_ = false; - bool perform_inference_ = true; - - std::vector input_state_; - std::vector output_state_; - - std::unique_ptr dataset_; - std::unique_ptr> mlp_; - typename MLP::mlp_weights stored_weights_; - bool weights_randomised_ = false; - - LogFn log_fn_ = nullptr; -}; - -} // namespace nisps - -#include "iml_impl.hpp" - -#endif // NISPS_IML_HPP -``` -**Verification**: File exists with exact content. - -### TASK-021: Create iml_impl.hpp implementation -**Blocked by**: TASK-020 -**Description**: Create the IML implementation file based on IMLInterface.hpp logic. -**Actions**: Create `nisps-core/include/nisps/iml_impl.hpp` with this exact content: -```cpp -#ifndef NISPS_IML_IMPL_HPP -#define NISPS_IML_IMPL_HPP - -namespace nisps { - -template -IML::IML(size_t n_inputs, size_t n_outputs, - std::vector hidden_layers, - size_t max_iterations, - Float learning_rate, - Float convergence_threshold) - : n_inputs_(n_inputs) - , n_outputs_(n_outputs) - , max_iterations_(max_iterations) - , learning_rate_(learning_rate) - , convergence_threshold_(convergence_threshold) -{ - // Build layer sizes: input + hidden + output - const size_t kBias = 1; - std::vector layer_sizes; - layer_sizes.push_back(n_inputs + kBias); - for (size_t h : hidden_layers) { - layer_sizes.push_back(h); - } - layer_sizes.push_back(n_outputs); - - // Activation functions: RELU for hidden, SIGMOID for output - std::vector activations; - for (size_t i = 0; i < hidden_layers.size(); ++i) { - activations.push_back(RELU); - } - activations.push_back(SIGMOID); - - dataset_ = std::make_unique(); - mlp_ = std::make_unique>( - layer_sizes, - activations, - loss::LOSS_MSE, - false, // use_constant_weight_init - 0.0f // constant_weight_init - ); - - input_state_.resize(n_inputs, static_cast(0.5)); - output_state_.resize(n_outputs, static_cast(0)); -} - -template -void IML::set_input(size_t index, Float value) { - if (index >= n_inputs_) return; - if (value < 0) value = 0; - if (value > 1) value = 1; - input_state_[index] = value; - input_updated_ = true; -} - -template -void IML::set_inputs(const Float* values, size_t count) { - for (size_t i = 0; i < count && i < n_inputs_; ++i) { - set_input(i, values[i]); - } -} - -template -const Float* IML::get_outputs() const { - return output_state_.data(); -} - -template -void IML::process() { - if (!perform_inference_ || !input_updated_) return; - - // Add bias term - std::vector input_with_bias = input_state_; - input_with_bias.push_back(static_cast(1.0)); - - // Run inference - std::vector output(n_outputs_); - mlp_->GetOutput(input_with_bias, &output); - - output_state_ = output; - input_updated_ = false; -} - -template -void IML::set_mode(Mode mode) { - if (mode == Mode::Inference && mode_ == Mode::Training) { - train(); - } - mode_ = mode; -} - -template -void IML::save_example() { - // First call: stop inference, user will position output - if (perform_inference_) { - perform_inference_ = false; - log("Move to desired output position..."); - return; - } - - // Second call: store the example - dataset_->Add(input_state_, output_state_); - perform_inference_ = true; - - // Run inference with new example - std::vector input_with_bias = input_state_; - input_with_bias.push_back(static_cast(1.0)); - std::vector output(n_outputs_); - mlp_->GetOutput(input_with_bias, &output); - output_state_ = output; - - log("Example saved."); -} - -template -void IML::clear_dataset() { - if (mode_ == Mode::Training) { - dataset_->Clear(); - log("Dataset cleared."); - } -} - -template -void IML::randomise_weights() { - if (mode_ == Mode::Training) { - stored_weights_ = mlp_->GetWeights(); - mlp_->DrawWeights(); - weights_randomised_ = true; - - // Run inference to show effect - std::vector input_with_bias = input_state_; - input_with_bias.push_back(static_cast(1.0)); - std::vector output(n_outputs_); - mlp_->GetOutput(input_with_bias, &output); - output_state_ = output; - - log("Weights randomised."); - } -} - -template -void IML::train() { - // Restore weights if they were randomised - if (weights_randomised_) { - mlp_->SetWeights(stored_weights_); - weights_randomised_ = false; - } - - auto features = dataset_->GetFeatures(true); // with bias - auto& labels = dataset_->GetLabels(); - - if (features.empty() || labels.empty()) { - log("Empty dataset, skipping training."); - return; - } - - typename MLP::training_pair_t training_data(features, labels); - - log("Training..."); - Float loss = mlp_->Train( - training_data, - learning_rate_, - static_cast(max_iterations_), - convergence_threshold_, - false // output_log - ); - - // Run inference after training - std::vector input_with_bias = input_state_; - input_with_bias.push_back(static_cast(1.0)); - std::vector output(n_outputs_); - mlp_->GetOutput(input_with_bias, &output); - output_state_ = output; - - log("Training complete."); -} - -} // namespace nisps - -#endif // NISPS_IML_IMPL_HPP -``` -**Verification**: File exists with exact content. - ---- - -## Phase 3: Create Main Header and Test - -### TASK-030: Create main nisps.hpp header -**Blocked by**: TASK-020, TASK-021 -**Description**: Create the main include header that exposes the public API. -**Actions**: Create `nisps-core/include/nisps/nisps.hpp` with this exact content: -```cpp -#ifndef NISPS_HPP -#define NISPS_HPP - -#include "iml.hpp" - -#endif // NISPS_HPP -``` -**Verification**: File exists with exact content. - -### TASK-031: Create XOR training test -**Blocked by**: TASK-030 -**Description**: Replace the placeholder test with an XOR training test. -**Actions**: Replace `nisps-core/test/main.cpp` with this exact content: -```cpp -#include -#include -#include - -void log_callback(const char* msg) { - std::cout << "[nisps] " << msg << "\n"; -} - -int main() { - std::cout << "=== NISPS Core Test: XOR Training ===\n\n"; - - // Create IML with 2 inputs, 1 output - nisps::IML iml(2, 1, {4, 4}, 5000, 1.0f, 0.0001f); - iml.set_logger(log_callback); - - // Enter training mode - iml.set_mode(nisps::IML::Mode::Training); - - // Train on XOR pattern - // (0,0) -> 0 - iml.set_input(0, 0.0f); - iml.set_input(1, 0.0f); - iml.save_example(); // First call: stop inference - // Manually set output for this example (simulating user positioning) - // We access output_state_ indirectly by calling process after training - - // For this test, we'll add examples directly to dataset - // This simulates the two-step save process - - // Actually, let's test the full workflow properly: - // The IML class expects: save_example() twice per example - // 1. First call stops inference - // 2. User sets output position (we can't do this externally easily) - // 3. Second call stores input->output - - // For testing, let's verify the basic inference works - iml.set_mode(nisps::IML::Mode::Inference); - - // Test inference - iml.set_input(0, 0.0f); - iml.set_input(1, 0.0f); - iml.process(); - float out_00 = iml.get_outputs()[0]; - - iml.set_input(0, 1.0f); - iml.set_input(1, 0.0f); - iml.process(); - float out_10 = iml.get_outputs()[0]; - - iml.set_input(0, 0.0f); - iml.set_input(1, 1.0f); - iml.process(); - float out_01 = iml.get_outputs()[0]; - - iml.set_input(0, 1.0f); - iml.set_input(1, 1.0f); - iml.process(); - float out_11 = iml.get_outputs()[0]; - - std::cout << "\nInference results (untrained):\n"; - std::cout << " (0,0) -> " << out_00 << "\n"; - std::cout << " (1,0) -> " << out_10 << "\n"; - std::cout << " (0,1) -> " << out_01 << "\n"; - std::cout << " (1,1) -> " << out_11 << "\n"; - - std::cout << "\n=== Test passed: nisps-core compiles and runs ===\n"; - return 0; -} -``` -**Verification**: Test compiles and runs successfully. - -### TASK-032: Build and run tests -**Blocked by**: TASK-031 -**Description**: Build the library and run the test. -**Actions**: -```bash -cd nisps-core -mkdir -p build && cd build -cmake .. -make -ctest --output-on-failure -``` -**Verification**: All commands succeed, test outputs "Test passed". - ---- - -## Dependency Graph (ASCII) - -``` -TASK-001 (create dirs) - │ - ├──> TASK-002 (CMakeLists.txt) - │ │ - │ └──> TASK-003 (test CMakeLists.txt) - │ │ - │ └──> TASK-004 (placeholder test) - │ - ├──> TASK-010 (utils.hpp) - │ │ - │ └──> TASK-011 (loss.hpp) - │ │ │ - │ └──> TASK-012 (node.hpp) - │ │ - │ └──> TASK-013 (layer.hpp) - │ - ├──> TASK-014 (sample.hpp) - │ - ├──> TASK-017 (dataset.hpp) - │ │ - │ └──> TASK-018 (dataset_impl.hpp) - │ - └───────────────────────────────────────┐ - │ -TASK-011 + TASK-013 + TASK-014 ────────> TASK-015 (mlp.hpp) - │ - └──> TASK-016 (mlp_impl.hpp) - │ -TASK-016 + TASK-018 ───────────────────────────> TASK-020 (iml.hpp) - │ - └──> TASK-021 (iml_impl.hpp) - │ -TASK-020 + TASK-021 ───────────────────────────────────> TASK-030 (nisps.hpp) - │ - └──> TASK-031 (test) - │ - └──> TASK-032 (build & run) -``` - ---- - -## Critical Path - -The minimum path to a working library: -1. TASK-001 → TASK-002 → TASK-003 → TASK-004 (setup) -2. TASK-010 → TASK-012 → TASK-013 (utils → node → layer) -3. TASK-011 (loss, parallel with above) -4. TASK-014 (sample, parallel) -5. TASK-015 → TASK-016 (mlp) -6. TASK-017 → TASK-018 (dataset, parallel with 3-5) -7. TASK-020 → TASK-021 (iml) -8. TASK-030 → TASK-031 → TASK-032 (header + test + build) - -**Estimated time**: 4-6 hours for a capable agent. - ---- - -## File Checklist - -After all tasks complete, these files should exist: -``` -nisps-core/ -├── CMakeLists.txt -├── include/ -│ └── nisps/ -│ ├── nisps.hpp -│ ├── iml.hpp -│ ├── iml_impl.hpp -│ ├── mlp.hpp -│ ├── mlp_impl.hpp -│ ├── dataset.hpp -│ ├── dataset_impl.hpp -│ ├── layer.hpp -│ ├── node.hpp -│ ├── loss.hpp -│ ├── sample.hpp -│ └── utils.hpp -├── test/ -│ ├── CMakeLists.txt -│ └── main.cpp -└── examples/ -``` diff --git a/data/.~lock.tree_cosmos_names.csv# b/data/.~lock.tree_cosmos_names.csv# deleted file mode 100644 index 2608f38..0000000 --- a/data/.~lock.tree_cosmos_names.csv# +++ /dev/null @@ -1 +0,0 @@ -,ck84,dharma-the-swan,04.11.2025 16:41,file:///home/ck84/snap/libreoffice/358/.config/libreoffice/4; \ No newline at end of file diff --git a/data/tree_cosmos_names.csv b/data/tree_cosmos_names.csv deleted file mode 100644 index e71818d..0000000 --- a/data/tree_cosmos_names.csv +++ /dev/null @@ -1,424 +0,0 @@ -Name,Component_1,Component_2 -Betelginkgo,Betelgeuse,Ginkgo -Pleiadedar,Pleiades,Cedar -Helixypress,Helix Nebula,Cypress -Rigelmwood,Rigel,Elmwood -Sombreroak,Sombrero Galaxy,Oak -Hyadehorn,Hyades,Hawthorn -Triangularch,Triangulum,Larch -Vegalder,Vega,Alder -Whirlpoplar,Whirpool Galaxy,Poplar -Siriuswood,Sirius,Basswood -Oortcacia,Oort Cloud,Acacia -Denebony,Deneb,Ebony -Cartwhillow,Cartwheel Galaxy,Willow -Polariswood,Polaris,Ironwood -Craborwood,Crab Nebula,Hornbeam -Aldebaranyan,Aldebaran,Banyan -Pinwheelnut,Pinwheel Galaxy,Walnut -Antaresnut,Antares,Chestnut -Kuiperlpa,Kuiper Belt,Catalpa -Arcturuspruce,Arcturus,Spruce -Pegasafras,Pegasus,Sassafras -Capellaberry,Capella,Mulberry -Lyracacia,Lyra,Acacia -Polluxwood,Pollux,Boxwood -Scorpiumac,Scorpius,Sumac -Castorwood,Castor,Cottonwood -Dracodar,Draco,Cedar -Regulushua,Regulus,Joshua -Aquilnut,Aquila,Walnut -Spicamore,Spica,Sycamore -Cygnusswood,Cygnus,Sasswood -Proceyon,Procyon,Pecan -Perseusqoia,Perseus,Sequoia -Sagittalpa,Sagittarius,Catalpa -Tauruspen,Taurus,Aspen -Geminowan,Gemini,Rowan -Canceria,Cancer,Wisteria -Leogany,Leo,Mahogany -Virgoak,Virgo,Oak -Libraobab,Libra,Baobab -Ariescalyptus,Aries,Eucalyptus -Capricornia,Capricorn,Magnolia -Aquariusqoia,Aquarius,Sequoia -Piscesimmon,Pisces,Persimmon -Omeganolia,Omega Centauri,Magnolia -Beehivewood,Beehive Cluster,Olivewood -Pillarspress,Pillars of Creation,Cypress -Greatwillow,Great Wall,Willow -Localboobao,Local Bubble,Baobab -Termishoak,Termination Shock,Oak -Bowshocknut,Bow Shock,Chestnut -Stellarix,Stellar Wind,Tamarix -Heliopaulownia,Heliopause,Paulownia -Rochemewood,Roche Limit,Limewood -Eventhornbeam,Event Horizon,Hornbeam -Accretioak,Accretion Disk,Oak -Protoplanetree,Protoplanetary Disk,Planetree -Lagranginkgo,Lagrange Point,Ginkgo -Trojaniper,Trojan,Juniper -Gammarchwood,Gamma Ray Burst,Birchwood -Hypernowan,Hypernova,Rowan -Whitedwillow,White Dwarf,Willow -Redgianter,Red Giant,Red Alder -Superclusternut,Supercluster,Butternut -Filamenteakwood,Filament,Teakwood -Voidglass,Void,Douglass Fir -Greywallnut,Great Wall,Wallnut -Comettree,Comet,Peppertree -Asteroider,Asteroid Belt,Elder -Helixelkova,Helix Nebula,Zelkova -Orionwood,Orion Nebula,Redwood -Cassiopaple,Cassiopeia,Apple -Ursajortree,Ursa Major,Majortree -Ursaminorwood,Ursa Minor,Dogwood -Bristlecrab,Bristlecone Pine,Crab Nebula -Metasequoyasar,Metasequoia,Quasar -Monkeypleiades,Monkey Puzzle,Pleiades -Dragontree,Dragon Tree,Draco -Boojumbrero,Boojum,Sombrero Galaxy -Quiverwheel,Quiver Tree,Pinwheel -Joshuapiter,Joshua Tree,Jupiter -Wolleminebula,Wollemi Pine,Nebula -Dawndromeda,Dawn Redwood,Andromeda -Baldcygnuss,Bald Cypress,Cygnus -Zelkovega,Zelkova,Vega -Hornbeamstar,Hornbeam,Barnstar -Rowantares,Rowan,Antares -Basswoodburst,Basswood,Gamma Burst -Calabashea,Calabash,Cassiopeia -Neemeda,Neem,Andromeda -Salirius,Sal,Sirius -Tooniverse,Toon,Universe -Cassiapeia,Cassia,Cassiopeia -Yellowoodrion,Yellowwood,Orion -Blackwoodhole,Blackwood,Black Hole -Ironwoodsar,Ironwood,Quasar -Satinwhirl,Satinwood,Whirlpool -Poplarsar,Poplar,Pulsar -Cottonwoodway,Cottonwood,Milky Way -Sumacnova,Sumac,Supernova -Hazeleius,Hazel,Pegasus -Elderstar,Elder,Barnstar -Rigelbark,Rigel,Bark -Sirillow,Sirius,Willow -Altairix,Altair,Tamarix -Denebula,Deneb,Nebula -Polarispine,Polaris,Pine -Betelpine,Betelgeuse,Bristlecone -Rigellarch,Rigel,Larch -Vegamore,Vega,Sycamore -Altairnut,Altair,Chestnut -Arcturuscacia,Arcturus,Acacia -Capellaple,Capella,Apple -Polluxdogwood,Pollux,Dogwood -Castorong,Castor,Camphorwood -Regulusqoia,Regulus,Sequoia -Spicape,Spica,Grape -Procyonwood,Procyon,Sandalwood -Aldebaranyan,Aldebaran,Banyan -Antarespress,Antares,Cypress -Pleiadwood,Pleiades,Cedarwood -Hyadesmac,Hyades,Sumac -Beehivewood,Beehive,Olivewood -Orionniper,Orion,Juniper -Cassiopear,Cassiopeia,Pear -Dracoroak,Draco,Cork Oak -Lyralnut,Lyra,Walnut -Cygnust,Cygnus,Locust -Aquillow,Aquila,Willow -Perseusnut,Perseus,Butternut -Pegasuspen,Pegasus,Aspen -Andromelm,Andromeda,Elm -Triangulfir,Triangulum,Fir -Sombreruce,Sombrero,Spruce -Whirlpinecone,Whirlpool,Pine -Cartwheelnut,Cartwheel,Hazelnut -Helixelm,Helix,Elm -Crabapple,Crab Nebula,Crabapple -Pillarspen,Pillars,Aspen -Sagittariuswood,Sagittarius,Sandalwood -Scorpiuspen,Scorpius,Aspen -Taurusqoia,Taurus,Sequoia -Geminoak,Gemini,Oak -Cancerdar,Cancer,Cedar -Leowood,Leo,Boxwood -Virgowan,Virgo,Rowan -Librasswood,Libra,Basswood -Ariesnut,Aries,Chestnut -Capricornbeam,Capricorn,Hornbeam -Aquariuslder,Aquarius,Alder -Piscespress,Pisces,Cypress -Magnetardwood,Magnetar,Hardwood -Pulsarwood,Pulsar,Rosewood -Quasarberry,Quasar,Elderberry -Supernovakia,Supernova,Plumeria -Hypernovania,Hypernova,Paulownia -Blackholly,Black Hole,Holly -Whitedwarfpine,White Dwarf,White Pine -Redgiantwood,Red Giant,Giantwood -Bluegiganticacia,Blue Giant,Acacia -Neutronia,Neutron Star,Catalpa -Browndwarfnut,Brown Dwarf,Butternut -Protostarfir,Protostar,Fir -Mainsequoia,Main Sequence,Sequoia -Subergiantree,Supergiant,Tree -Yorkcloud,Oort Cloud,Cork -Kuiperlpa,Kuiper Belt,Catalpa -Heliospherical,Heliosphere,Spherical Tree -Heliopaulogany,Heliopause,Mahogany -Terminationoak,Termination Shock,Oak -Bowshockberry,Bow Shock,Hackberry -Stellarwindwood,Stellar Wind,Driftwood -Solarwindowan,Solar Wind,Rowan -Interstellarch,Interstellar Medium,Larch -Circumstellarix,Circumstellar Disk,Tamarix -Protoplanetree,Protoplanetary Disk,Plane Tree -Accretionoak,Accretion Disk,Oak -Eventhorizonwood,Event Horizon,Ironwood -Schwarzschillow,Schwarzschild Radius,Willow -Singularsimmon,Singularity,Persimmon -Rochelimewood,Roche Limit,Limewood -Lagrangewood,Lagrange Point,Rangewood -Trojantree,Trojan Asteroids,Tree -Hildazel,Hilda Group,Hazel -Amoribark,Amor Asteroids,Bark -Atenberry,Aten Asteroids,Elderberry -Apollomeria,Apollo Asteroids,Plumeria -Gammaburst Ash,Gamma Ray Burst,Ash -Xraybirch,X-ray Binary,Birch -Millisecwood,Millisecond Pulsar,Redwood -Magnetarch,Magnetar,Larch -Softgammawood,Soft Gamma Repeater,Softwood -Superclusternut,Supercluster,Clusternut -Galaxyclusterpine,Galaxy Cluster,Pine -Galaxygroupfir,Galaxy Group,Fir -Localgrowan,Local Group,Rowan -Virgoclusteroak,Virgo Cluster,Oak -Comaberrynicaceae,Coma Berenices,Berry -Greatwallnut,Great Wall,Wallnut -Sloangreatwillow,Sloan Great Wall,Willow -Herculessuperia,Hercules Supercluster,Wisteria -Shapleypress,Shapley Supercluster,Cypress -Laniakeroak,Laniakea Supercluster,Kauri Oak -Cosmicwebwood,Cosmic Web,Webwood -Filamentree,Filament,Tree -Voidwood,Void,Driftwood -Boötesvoidwood,Boötes Void,Olivewood -Sculptornut,Sculptor Void,Coconut -Greywallash,Great Wall,Ash -Localbubbaobab,Local Bubble,Baobab -Loopicacia,Local Interstellar Cloud,Acacia -Gouldspen,Gould Belt,Aspen -Orionspurmac,Orion Spur,Sumac -Sagittariusarmwood,Sagittarius Arm,Armwood -Perseusarmpine,Perseus Arm,Pine -Outerarmoak,Outer Arm,Oak -Scutumcentauruswood,Scutum-Centaurus Arm,Scutumwood -Normamalder,Norma Arm,Alder -Barredspiralex,Barred Spiral,Ilex -Ellipticacacia,Elliptical Galaxy,Acacia -Irregularnut,Irregular Galaxy,Butternut -Lenticularcorn,Lenticular Galaxy,Acorn -Dwarfnut,Dwarf Galaxy,Chestnut -Satelliteelm,Satellite Galaxy,Elm -Starburstfir,Starburst Galaxy,Fir -Seyfertree,Seyfert Galaxy,Feather Tree -Blazarwood,Blazar,Rosewood -Radiogalaxypress,Radio Galaxy,Cypress -Activenutcleus,Active Galactic Nucleus,Nut -Quasartree,Quasar,Quandong Tree -Ultraluminouswood,Ultraluminous Galaxy,Luminous Tree -Luminousredgiant,Luminous Red Galaxy,Red Giant Sequoia -Greenpeachia,Green Pea Galaxy,Peach -Polarringwood,Polar Ring Galaxy,Ringwood -Shellgalaxash,Shell Galaxy,Ash -Ringalaxypress,Ring Galaxy,Cypress -Hoagsobjectoak,Hoag's Object,Oak -Mayallsobjectree,Mayall's Object,Tree -Tadpolgalaxillow,Tadpole Galaxy,Willow -Cometgalaxalder,Comet Galaxy,Alder -Sunflowernut,Sunflower Galaxy,Sunflower -Blackeyewood,Black Eye Galaxy,Blackwood -Bodesgalaxnut,Bode's Galaxy,Coconut -Cigargalaxpress,Cigar Galaxy,Cypress -Mousesgalaxypen,Mice Galaxies,Aspen -Antennaegalaxynt,Antennae Galaxies,Antwood -Polarisstwood,Polaris,Sasswood -Rigwood,Rigel,Dogwood -Canopusnut,Canopus,Coconut -Vegamore,Vega,Sycamore -Capellalnut,Capella,Walnut -Rigelberry,Rigel,Mulberry -Procyonwood,Procyon,Cottonwood -Achernarnut,Achernar,Butternut -Betelgeuswood,Betelgeuse,Geuswood -Hadarcedarwood,Hadar,Cedarwood -Altairix,Altair,Tamarix -Alderaminwood,Alderamin,Alderwood -Denebolive,Denebola,Olive -Mimosacia,Mimosa,Acacia -Regulenut,Regulus,Filbert -Adharalnut,Adhara,Walnut -Shahelmwood,Shaula,Elm -Gacruxifir,Gacrux,Fir -Bellatrixnut,Bellatrix,Hazelnut -Elnathnut,Elnath,Chestnut -Miaplacidiumwood,Miaplacidus,Palladium -Alnitaknut,Alnitak,Coconut -Alnilamspen,Alnilam,Aspen -Aliothogany,Alioth,Mahogany -Mirzamberry,Mirzam,Mulberry -Alkaidnut,Alkaid,Butternut -Atriarch,Atria,Larch -Alhenawood,Alhena,Alderwood -Peacockahogany,Peacock,Mahogany -Mirfaknut,Mirfak,Coconut -Wezenalder,Wezen,Alder -Sargassage,Sargas,Sage -Menkarberry,Menkar,Elderberry -Eniffir,Enif,Fir -Denebalgedinut,Deneb Algedi,Butternut -Zubenhaknutbi,Zuben Hakrabi,Butternut -Alshainalnut,Alshain,Walnut -Rastabantamarind,Rastaban,Tamarind -Markabwood,Markab,Markwood -Schedarogany,Schedar,Mahogany -Almaalnutak,Almaak,Walnut -Rasalgetash,Rasalgethi,Ash -Thubanyan,Thuban,Banyan -Kausafras,Kaus Australis,Sassafras -Nunkirnut,Nunki,Coconut -Sabikwood,Sabik,Rosewood -Menkalnutnan,Menkalnan,Butternut -Asteropewood,Asterope,Redwood -Atlaspen,Atlas,Aspen -Electralder,Electra,Alder -Maiagany,Maia,Mahogany -Meropewood,Merope,Olivewood -Taugetamarix,Taygeta,Tamarix -Pleionetree,Pleione,Tree -Celaenoak,Celaeno,Oak -Alcyonedar,Alcyone,Cedar -Sterropopelnut,Sterope,Pecanut -Proximaple,Proxima Centauri,Apple -Alphacenternut,Alpha Centauri,Butternut -Barnardstaroak,Barnard's Star,Oak -Wolfwood,Wolf 359,Dogwood -Lalandelarch,Lalande 21185,Larch -Siriuswood,Sirius,Basswood -Luytenbirch,Luyten 726-8,Birch -Rossnut,Ross 154,Coconut -Rossberry,Ross 248,Elderberry -Epsilonwood,Epsilon Eridani,Sandalwood -Lacaillemwood,Lacaille 9352,Elmwood -Rosswood,Ross 128,Cottonwood -EZaquariuspen,EZ Aquarii,Aspen -Procyonix,Procyon,Phoenix -Sixtyonecygnust,61 Cygni,Locust -Struvewood,Struve 2398,Rosewood -Groombridgefir,Groombridge 34,Fir -Epsilondiwood,Epsilon Indi,Indigowood -DXcancroak,DX Cancri,Oak -Taucetialder,Tau Ceti,Alder -Luytenut,Luyten's Star,Butternut -Teegardeenspen,Teegarden's Star,Aspen -Kapteynsnut,Kapteyn's Star,Coconut -Lacaillewood,Lacaille 8760,Lacewood -Krugernut,Krüger 60,Coconut -Rosspress,Ross 614,Cypress -Wolfwoodogwood,Wolf 1061,Dogwood -Vanmaanensnut,Van Maanen's Star,Chestnut -Gliesetree,Gliese 1,Tree -Gielenseginkgo,Gliese 876,Ginkgo -Giseewood,Gliese 581,Rosewood -TRappistree,TRAPPIST-1,Trapwood -Gliesecherry,Gliese 667,Cherry -Glisewood,Gliese 832,Walnutwood -Glaesewood,Gliese 163,Olivewood -Gleasewood,Gliese 180,Teaselwood -Lessewood,Gliese 221,Lesserwood -Sleewood,Gliese 317,Sleekwood -Liewood,Gliese 357,Tree -Gleetree,Gliese 433,Tree -Graywood,Gliese 436,Graywood -Greatwood,Gliese 504,Greatwood -Greenwood,Gliese 570,Greenwood -Greshwood,Gliese 625,Ashwood -Gladewood,Gliese 649,Glade -Glowwood,Gliese 674,Glowwood -Gradewood,Gliese 682,Gradewood -Gracewood,Gliese 686,Gracewood -Grandewood,Gliese 785,Grandewood -Glintwood,Gliese 806,Flintwood -Grimwood,Gliese 832,Grimwood -Horsehead Hickory,Horsehead Nebula,Hickory -Eaglenberry,Eagle Nebula,Elderberry -Trifidogwood,Trifid Nebula,Dogwood -Lagoonwood,Lagoon Nebula,Lagoonwood -Rosettamarisk,Rosette Nebula,Tamarisk -Carinapress,Carina Nebula,Cypress -Veilbark,Veil Nebula,Bark -Northamericoak,North America Nebula,Oak -Flamingowan,Flaming Star Nebula,Rowan -Owlnut,Owl Nebula,Walnut -Catseyelm,Cat's Eye Nebula,Elm -Ringebula,Ring Nebula,Nebula -Dumbbellnut,Dumbbell Nebula,Butternut -Boomerangalm,Boomerang Nebula,Palm -Butterflypress,Butterfly Nebula,Cypress -Calasash,Calabash Nebula,Ash -Redspiderash,Red Spider Nebula,Ash -Eskimogany,Eskimo Nebula,Mahogany -Ghostjuniper,Ghost of Jupiter,Juniper -Helixogwood,Helix Nebula,Dogwood -Spiralpress,Spiral Planetary,Cypress -Littlegembark,Little Gem Nebula,Bark -Necklacacia,Necklace Nebula,Acacia -Saturnbeech,Saturn Nebula,Beech -Bluesnowbark,Blue Snowball,Bark -Twinjet Fir,Twin Jet Nebula,Fir -Hourglasswood,Hourglass Nebula,Boxwood -Eggwood,Egg Nebula,Dogwood -Boxtree,Box Nebula,Boxtree -Crescentpress,Crescent Nebula,Cypress -Bubblebark,Bubble Nebula,Bark -Thorwood,Thor's Helmet,Thornwood -Snailgum,Snail Nebula,Sweetgum -Conepinepress,Cone Nebula,Pine -Irisbeech,Iris Nebula,Beech -Witchheadash,Witch Head Nebula,Ash -Barnardloopwood,Barnard's Loop,Loopwood -Californiapress,California Nebula,Cypress -Heartbark,Heart Nebula,Bark -Soulspuce,Soul Nebula,Spruce -Pacmangany,Pacman Nebula,Mahogany -Monkeyheadnut,Monkey Head Nebula,Monkeynut -Wizardlm,Wizard Nebula,Elm -Tulipwood,Tulip Nebula,Tulipwood -Crescentbark,Crescent Nebula,Bark -Pelicangum,Pelican Nebula,Gum -Elephantstrunkwood,Elephant's Trunk,Trunkwood -Tuningforkpress,Tuning Fork Galaxy,Cypress -Sombrerowood,Sombrero Galaxy,Sombrerowood -Blackeyesycamore,Black Eye Galaxy,Sycamore -Sunflowernut,Sunflower Galaxy,Coconut -Pinwheelder,Pinwheel Galaxy,Elder -Fireworksgalaxyalder,Fireworks Galaxy,Alder -Spindlepress,Spindle Galaxy,Cypress -Bodesgalaxnut,Bode's Galaxy,Butternut -Cigargalaxbirch,Cigar Galaxy,Birch -Whirlpoolepress,Whirlpool Galaxy,Cypress -Cartwheelalder,Cartwheel Galaxy,Alder -Tadpolewood,Tadpole Galaxy,Tadwood -Antenaennut,Antennae Galaxies,Nut -Mousesgalaxielm,Mice Galaxies,Elm -Atomspeachachy,Atom's Peace Galaxy,Peachy -Malinsgalaxnut,Malin 1,Nut -Hoagsobjecteakwood,Hoag's Object,Teakwood -Ringgalaxynut,Ring Galaxy,Donut -Polarringiron,Polar Ring Galaxy,Ironwood -Silversliverwood,Silver Sliver Galaxy,Silverwood -Needlepress,Needle Galaxy,Cypress -Hamburgerash,Hamburger Galaxy,Ash \ No newline at end of file diff --git a/data/tree_cosmos_names_2.csv b/data/tree_cosmos_names_2.csv deleted file mode 100644 index 26f6434..0000000 --- a/data/tree_cosmos_names_2.csv +++ /dev/null @@ -1,420 +0,0 @@ -Name,Component_1,Component_2 -Pleiadeiba,Pleiades,Ceiba -Pau Brasilica,Pau Brasil,Crab Nebula -Araucarina,Araucaria,Carina Nebula -Quebracho X-1,Quebracho,Cygnus X-1 -Lapachomeda,Lapacho,Andromeda -Guanacastellum,Guanacaste,Magellanic Cloud -Trianguanum,Triangulum,Guanacaste -Jacarandromeda,Jacaranda,Andromeda -Ceibalaxy,Ceiba,Galaxy -Horsehead Brasil,Horsehead,Pau Brasil -Monkeypuzzle Void,Monkey Puzzle,Cosmic Void -Quebrachion,Quebracho,Orion -Lapachosar,Lapacho,Quasar -Rubber Rosette,Rubber Tree,Rosette Nebula -Cashewmeda,Cashew,Andromeda -Brazil Nutnova,Brazil Nut,Supernova -Cecropiades,Cecropia,Pleiades -Balsombrero,Balsa,Sombrero Galaxy -Rosewoodway,Rosewood,Milky Way -Ipe Helix,Ipe,Helix Nebula -Kapoksagittarius,Kapok,Sagittarius -Cedrella Cluster,Cedrella,Star Cluster -Alerce Lyra,Alerce,Lyra -Mahogalactica,Mahogany,Galactic Center -Pinwheelnut,Pinwheel,Brazil Nut -Tarantularch,Tarantula Nebula,Larch -Ceibomeda,Ceiba,Andromeda -Pau Brassiopeia,Pau Brasil,Cassiopeia -Araucarion,Araucaria,Orion -Quebrachole,Quebracho,Black Hole -Lapachoway,Lapacho,Milky Way -Guanasar,Guanacaste,Quasar -Jacaranebula,Jacaranda,Nebula -Ceiba Centauri,Ceiba,Alpha Centauri -Brazilbeehive,Brazil Nut,Beehive Cluster -Monkeypuzzar,Monkey Puzzle,Quasar -Quebrachomeda,Quebracho,Andromeda -Lapachydes,Lapacho,Hyades -Rubberway,Rubber Tree,Milky Way -Cashewsar,Cashew,Quasar -Nutmeda,Brazil Nut,Andromeda -Cecropiagalaxy,Cecropia,Galaxy -Balsanova,Balsa,Supernova -Rosettewood,Rosette,Rosewood -Ipegalaxy,Ipe,Galaxy -Kapoknebula,Kapok,Nebula -Cedrellaxy,Cedrella,Galaxy -Alercentauri,Alerce,Centaurus -Mahogacrab,Mahogany,Crab Nebula -Eaglebrasil,Eagle Nebula,Pau Brasil -Tarantuleia,Tarantula Nebula,Ceiba -Whirlpoolnut,Whirlpool,Brazil Nut -Araucagalaxy,Araucaria,Galaxy -Quebrachiades,Quebracho,Pleiades -Lapachomeda,Lapacho,Andromeda -Guanacasteroid,Guanacaste,Asteroid -Jacarandromeda,Jacaranda,Andromeda -Ceibulsar,Ceiba,Pulsar -Tadpolebrasil,Tadpole,Pau Brasil -Monkeynova,Monkey Puzzle,Supernova -Quebrachygnus,Quebracho,Cygnus -Lapachoacentauri,Lapacho,Alpha Centauri -Rubberhole,Rubber Tree,Black Hole -Cashewnova,Cashew,Supernova -Brazilbutterfly,Brazil Nut,Butterfly Cluster -Cecropiomeda,Cecropia,Andromeda -Balsagittarius,Balsa,Sagittarius -Rosewoodrion,Rosewood,Orion -Ipeleiades,Ipe,Pleiades -Kapoksombrero,Kapok,Sombrero Galaxy -Cedrellipse,Cedrella,Elliptical Galaxy -Alercegalaxy,Alerce,Galaxy -Mahogaring,Mahogany,Ring Nebula -Cartwheeiba,Cartwheel,Ceiba -Pau Brasilian,Pau Brasil,Cassiopeian -Araucarionebula,Araucaria,Orion Nebula -Quebrasar,Quebracho,Quasar -Lapachomega,Lapacho,Omega Centauri -Guanacastway,Guanacaste,Milky Way -Jacaranovae,Jacaranda,Supernova -Ceibachomeda,Ceiba,Andromeda -Sunflowerbrasil,Sunflower Galaxy,Pau Brasil -Monkeypuzzalaxy,Monkey Puzzle,Galaxy -Quebrachoxima,Quebracho,Proxima -Lapachosar,Lapacho,Quasar -Rubberbula,Rubber Tree,Nebula -Cashewcluster,Cashew,Star Cluster -Nutway,Brazil Nut,Milky Way -Cecropianebula,Cecropia,Nebula -Balsagara,Balsa,Sagittarius A* -Rosewoodraco,Rosewood,Draco -Ipelipse,Ipe,Ellipse -Kapokagoon,Kapok,Lagoon Nebula -Cedrellaneta,Cedrella,Planetary Nebula -Alercerion,Alerce,Orion -Mahogacarina,Mahogany,Carina -Trifidnut,Trifid Nebula,Brazil Nut -Tarantulacho,Tarantula,Lapacho -Ceibanova,Ceiba,Supernova -Pau Brasilion,Pau Brasil,Orion -Araucariades,Araucaria,Pleiades -Quebrachoelix,Quebracho,Helix -Lapacholia,Lapacho,Magnolia -Guanacasar,Guanacaste,Quasar -Jacarandromeda,Jacaranda,Andromeda -Ceibulsar,Ceiba,Pulsar -Titan Brasil,Titan,Pau Brasil -Monkeynebula,Monkey Puzzle,Nebula -Quebrachomeda,Quebracho,Andromeda -Lapachogalaxy,Lapacho,Galaxy -Rubbersar,Rubber Tree,Quasar -Cashewmeda,Cashew,Andromeda -Nutcluster,Brazil Nut,Cluster -Cecropioway,Cecropia,Milky Way -Balsanebula,Balsa,Nebula -Rosewoodway,Rosewood,Milky Way -Ipegalaxy,Ipe,Galaxy -Kapoksar,Kapok,Quasar -Cedrellaxy,Cedrella,Galaxy -Alercenova,Alerce,Supernova -Mahogalactic,Mahogany,Galactic -Sculptorbrasil,Sculptor Galaxy,Pau Brasil -Ceibomeda,Ceiba,Andromeda -Araucarionway,Araucaria,Milky Way -Quebrachion,Quebracho,Orion -Lapachohole,Lapacho,Black Hole -Guanacastromeda,Guanacaste,Andromeda -Jacaranebula,Jacaranda,Nebula -Ceibagalaxy,Ceiba,Galaxy -Europa Brasil,Europa,Pau Brasil -Monkeypuzzar,Monkey Puzzle,Quasar -Quebrachnova,Quebracho,Supernova -Lapachoides,Lapacho,Hyades -Rubbermeda,Rubber Tree,Andromeda -Cashewnova,Cashew,Supernova -Nutway,Brazil Nut,Milky Way -Cecropiagalaxy,Cecropia,Galaxy -Balsaquila,Balsa,Aquila -Rosewoodsar,Rosewood,Quasar -Ipecentauri,Ipe,Alpha Centauri -Kapokgalaxy,Kapok,Galaxy -Cedrellaneta,Cedrella,Planetary Nebula -Alercemeda,Alerce,Andromeda -Mahoganova,Mahogany,Supernova -Boomerangbrasil,Boomerang Nebula,Pau Brasil -Ceibulsar,Ceiba,Pulsar -Pau Brasilades,Pau Brasil,Pleiades -Araucagalaxy,Araucaria,Galaxy -Quebrachomeda,Quebracho,Andromeda -Lapachosar,Lapacho,Quasar -Guanacastellum,Guanacaste,Magellanic -Jacaranovae,Jacaranda,Supernova -Ceibaway,Ceiba,Milky Way -Enceladnut,Enceladus,Brazil Nut -Monkeynebula,Monkey Puzzle,Nebula -Quebrachogalaxy,Quebracho,Galaxy -Lapachonova,Lapacho,Supernova -Rubberion,Rubber Tree,Orion -Cashewgalaxy,Cashew,Galaxy -Nutsagittarius,Brazil Nut,Sagittarius -Cecropia A*,Cecropia,Sagittarius A* -Balsanova,Balsa,Supernova -Rosewoodmeda,Rosewood,Andromeda -Ipesar,Ipe,Quasar -Kapokway,Kapok,Milky Way -Cedrellipse,Cedrella,Ellipse -Alercenebula,Alerce,Nebula -Mahogaring,Mahogany,Ring Nebula -Cat's Eye Brasil,Cat's Eye,Pau Brasil -Ceibomeda,Ceiba,Andromeda -Araucarionebula,Araucaria,Orion Nebula -Quebrachoides,Quebracho,Hyades -Lapachogalaxy,Lapacho,Galaxy -Guanacastar,Guanacaste,Quasar -Jacarandromeda,Jacaranda,Andromeda -Ceibaneta,Ceiba,Planetary Nebula -Ganymede Brasil,Ganymede,Pau Brasil -Monkeypuzzalaxy,Monkey Puzzle,Galaxy -Quebrachonova,Quebracho,Supernova -Lapachomeda,Lapacho,Andromeda -Rubbersar,Rubber Tree,Quasar -Cashewnebula,Cashew,Nebula -Nutgalaxy,Brazil Nut,Galaxy -Cecropiomeda,Cecropia,Andromeda -Balsagittarius,Balsa,Sagittarius -Rosewoodion,Rosewood,Orion -Ipeleiades,Ipe,Pleiades -Kapoknova,Kapok,Supernova -Cedrellaxy,Cedrella,Galaxy -Alercegalaxy,Alerce,Galaxy -Mahogacrab,Mahogany,Crab -Dumbbell Brasil,Dumbbell Nebula,Pau Brasil -Ceibulsar,Ceiba,Pulsar -Pau Brasilway,Pau Brasil,Milky Way -Araucarina,Araucaria,Carina -Quebrachogalaxy,Quebracho,Galaxy -Lapachosar,Lapacho,Quasar -Guanacastromeda,Guanacaste,Andromeda -Jacaranebula,Jacaranda,Nebula -Ceibanova,Ceiba,Supernova -Io Brasil,Io,Pau Brasil -Monkeypuzzar,Monkey Puzzle,Quasar -Quebrachomeda,Quebracho,Andromeda -Lapachogalaxy,Lapacho,Galaxy -Rubbernova,Rubber Tree,Supernova -Cashewmeda,Cashew,Andromeda -Nutnebula,Brazil Nut,Nebula -Cecropiagalaxy,Cecropia,Galaxy -Balsasar,Balsa,Quasar -Rosewoodway,Rosewood,Milky Way -Ipegalaxy,Ipe,Galaxy -Kapokmeda,Kapok,Andromeda -Cedrellaneta,Cedrella,Planetary Nebula -Alercenova,Alerce,Supernova -Mahogalagoon,Mahogany,Lagoon -Crescent Brasil,Crescent Nebula,Pau Brasil -Ceibagalaxy,Ceiba,Galaxy -Araucarionway,Araucaria,Milky Way -Quebrachion,Quebracho,Orion -Lapachohole,Lapacho,Black Hole -Guanacastar,Guanacaste,Quasar -Jacaranovo,Jacaranda,Supernova -Ceibomeda,Ceiba,Andromeda -Callisto Brasil,Callisto,Pau Brasil -Monkeynebula,Monkey Puzzle,Nebula -Quebrachogalaxy,Quebracho,Galaxy -Lapachonova,Lapacho,Supernova -Rubbermeda,Rubber Tree,Andromeda -Cashewgalaxy,Cashew,Galaxy -Nutway,Brazil Nut,Milky Way -Cecropiomeda,Cecropia,Andromeda -Balsanebula,Balsa,Nebula -Rosewoodsar,Rosewood,Quasar -Ipecentauri,Ipe,Centauri -Kapokgalaxy,Kapok,Galaxy -Cedrellaxy,Cedrella,Galaxy -Alercemeda,Alerce,Andromeda -Mahoganova,Mahogany,Supernova -Flame Brasil,Flame Nebula,Pau Brasil -Ceibulsar,Ceiba,Pulsar -Pau Brasilion,Pau Brasil,Orion -Araucagalaxy,Araucaria,Galaxy -Quebrachomeda,Quebracho,Andromeda -Lapachosar,Lapacho,Quasar -Guanacastway,Guanacaste,Milky Way -Jacarandromeda,Jacaranda,Andromeda -Ceibanova,Ceiba,Supernova -Veil Brasil,Veil Nebula,Pau Brasil -Monkeypuzzalaxy,Monkey Puzzle,Galaxy -Quebrachonova,Quebracho,Supernova -Lapachogalaxy,Lapacho,Galaxy -Rubberion,Rubber Tree,Orion -Cashewnova,Cashew,Supernova -Nutgalaxy,Brazil Nut,Galaxy -Cecropiagalaxy,Cecropia,Galaxy -Balsomeda,Balsa,Andromeda -Rosewoodway,Rosewood,Milky Way -Ipesar,Ipe,Quasar -Kapoknebula,Kapok,Nebula -Cedrellaneta,Cedrella,Planetary Nebula -Alercenebula,Alerce,Nebula -Mahogaring,Mahogany,Ring -Pelican Brasil,Pelican Nebula,Pau Brasil -Ceibagalaxy,Ceiba,Galaxy -Araucarionebula,Araucaria,Orion Nebula -Quebrachoides,Quebracho,Hyades -Lapachomeda,Lapacho,Andromeda -Guanacastar,Guanacaste,Quasar -Jacaranovo,Jacaranda,Supernova -Ceibulsar,Ceiba,Pulsar -Cone Brasil,Cone Nebula,Pau Brasil -Monkeypuzzar,Monkey Puzzle,Quasar -Quebrachogalaxy,Quebracho,Galaxy -Lapachosar,Lapacho,Quasar -Rubbernova,Rubber Tree,Supernova -Cashewmeda,Cashew,Andromeda -Nutway,Brazil Nut,Milky Way -Cecropiomeda,Cecropia,Andromeda -Balsagalaxy,Balsa,Galaxy -Rosewoodion,Rosewood,Orion -Ipeleiades,Ipe,Pleiades -Kapokgalaxy,Kapok,Galaxy -Cedrellaxy,Cedrella,Galaxy -Alercegalaxy,Alerce,Galaxy -Mahoganova,Mahogany,Supernova -Witch Head Brasil,Witch Head,Pau Brasil -Ceiboway,Ceiba,Milky Way -Pau Brasilmeda,Pau Brasil,Andromeda -Araucarina,Araucaria,Carina -Quebrachonebula,Quebracho,Nebula -Lapachogalaxy,Lapacho,Galaxy -Guanacastromeda,Guanacaste,Andromeda -Jacaranebula,Jacaranda,Nebula -Ceibanova,Ceiba,Supernova -Iris Brasil,Iris Nebula,Pau Brasil -Monkeynova,Monkey Puzzle,Supernova -Quebrachomeda,Quebracho,Andromeda -Lapachosar,Lapacho,Quasar -Rubbermeda,Rubber Tree,Andromeda -Cashewgalaxy,Cashew,Galaxy -Nutnebula,Brazil Nut,Nebula -Cecropiagalaxy,Cecropia,Galaxy -Balsanovo,Balsa,Supernova -Rosewoodway,Rosewood,Milky Way -Ipegalaxy,Ipe,Galaxy -Kapokmeda,Kapok,Andromeda -Cedrellaneta,Cedrella,Planetary Nebula -Alercenova,Alerce,Supernova -Mahogagalaxy,Mahogany,Galaxy -Bubble Brasil,Bubble Nebula,Pau Brasil -Ceibomeda,Ceiba,Andromeda -Araucarionway,Araucaria,Milky Way -Quebrachion,Quebracho,Orion -Lapachohole,Lapacho,Black Hole -Guanacastar,Guanacaste,Quasar -Jacaranovo,Jacaranda,Supernova -Ceibagalaxy,Ceiba,Galaxy -North America Brasil,North America,Pau Brasil -Monkeypuzzar,Monkey Puzzle,Quasar -Quebrachogalaxy,Quebracho,Galaxy -Lapachonova,Lapacho,Supernova -Rubbersar,Rubber Tree,Quasar -Cashewnebula,Cashew,Nebula -Nutgalaxy,Brazil Nut,Galaxy -Cecropiomeda,Cecropia,Andromeda -Balsoway,Balsa,Milky Way -Rosewoodsar,Rosewood,Quasar -Ipecentauri,Ipe,Centauri -Kapokgalaxy,Kapok,Galaxy -Cedrellaxy,Cedrella,Galaxy -Alercemeda,Alerce,Andromeda -Mahoganova,Mahogany,Supernova -Pacman Brasil,Pacman Nebula,Pau Brasil -Ceibulsar,Ceiba,Pulsar -Pau Brasilades,Pau Brasil,Pleiades -Araucagalaxy,Araucaria,Galaxy -Quebrachomeda,Quebracho,Andromeda -Lapachosar,Lapacho,Quasar -Guanacastway,Guanacaste,Milky Way -Jacarandromeda,Jacaranda,Andromeda -Ceibanova,Ceiba,Supernova -Soul Brasil,Soul Nebula,Pau Brasil -Monkeynebula,Monkey Puzzle,Nebula -Quebrachonova,Quebracho,Supernova -Lapachogalaxy,Lapacho,Galaxy -Rubberion,Rubber Tree,Orion -Cashewnova,Cashew,Supernova -Nutway,Brazil Nut,Milky Way -Cecropiagalaxy,Cecropia,Galaxy -Balsameda,Balsa,Andromeda -Rosewoodway,Rosewood,Milky Way -Ipesar,Ipe,Quasar -Kapoknebula,Kapok,Nebula -Cedrellaneta,Cedrella,Planetary Nebula -Alercenebula,Alerce,Nebula -Mahogaring,Mahogany,Ring -Heart Brasil,Heart Nebula,Pau Brasil -Ceibagalaxy,Ceiba,Galaxy -Araucarionebula,Araucaria,Orion Nebula -Quebrachoides,Quebracho,Hyades -Lapachomeda,Lapacho,Andromeda -Guanacastar,Guanacaste,Quasar -Jacaranovo,Jacaranda,Supernova -Ceiboway,Ceiba,Milky Way -Elephant Trunk Brasil,Elephant Trunk,Pau Brasil -Monkeypuzzalaxy,Monkey Puzzle,Galaxy -Quebrachogalaxy,Quebracho,Galaxy -Lapachosar,Lapacho,Quasar -Rubbernova,Rubber Tree,Supernova -Cashewmeda,Cashew,Andromeda -Nutgalaxy,Brazil Nut,Galaxy -Cecropiomeda,Cecropia,Andromeda -Balsagalaxy,Balsa,Galaxy -Rosewoodion,Rosewood,Orion -Ipeleiades,Ipe,Pleiades -Kapokgalaxy,Kapok,Galaxy -Cedrellaxy,Cedrella,Galaxy -Alercegalaxy,Alerce,Galaxy -Mahoganova,Mahogany,Supernova -Omega Brasil,Omega Nebula,Pau Brasil -Ceibulsar,Ceiba,Pulsar -Pau Brasilion,Pau Brasil,Orion -Araucarina,Araucaria,Carina -Quebrachonebula,Quebracho,Nebula -Lapachogalaxy,Lapacho,Galaxy -Guanacastromeda,Guanacaste,Andromeda -Jacaranebula,Jacaranda,Nebula -Ceibanova,Ceiba,Supernova -Blinking Brasil,Blinking Nebula,Pau Brasil -Monkeypuzzar,Monkey Puzzle,Quasar -Quebrachomeda,Quebracho,Andromeda -Lapachosar,Lapacho,Quasar -Rubbermeda,Rubber Tree,Andromeda -Cashewgalaxy,Cashew,Galaxy -Nutnebula,Brazil Nut,Nebula -Cecropiagalaxy,Cecropia,Galaxy -Balsanovo,Balsa,Supernova -Rosewoodway,Rosewood,Milky Way -Ipegalaxy,Ipe,Galaxy -Kapokmeda,Kapok,Andromeda -Cedrellaneta,Cedrella,Planetary Nebula -Alercenova,Alerce,Supernova -Mahogagalaxy,Mahogany,Galaxy -Ghost Brasil,Ghost Nebula,Pau Brasil -Ceibomeda,Ceiba,Andromeda -Araucarionway,Araucaria,Milky Way -Quebrachion,Quebracho,Orion -Lapachohole,Lapacho,Black Hole -Guanacastar,Guanacaste,Quasar -Jacaranovo,Jacaranda,Supernova -Ceibagalaxy,Ceiba,Galaxy -Eskimo Brasil,Eskimo Nebula,Pau Brasil -Monkeynebula,Monkey Puzzle,Nebula -Quebrachogalaxy,Quebracho,Galaxy -Lapachonova,Lapacho,Supernova -Rubbersar,Rubber Tree,Quasar -Cashewnebula,Cashew,Nebula -Nutgalaxy,Brazil Nut,Galaxy -Cecropioway,Cecropia,Milky Way \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 157f9ef..0000000 --- a/package-lock.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "memlnaut-nisps-tests", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "memlnaut-nisps-tests", - "version": "1.0.0", - "devDependencies": { - "@playwright/test": "^1.59.1" - } - }, - "node_modules/@playwright/test": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", - "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.59.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/playwright": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", - "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.59.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index b34fc21..0000000 --- a/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "memlnaut-nisps-tests", - "version": "1.0.0", - "private": true, - "scripts": { - "test": "playwright test", - "test:ui": "playwright test --ui", - "test:headed": "playwright test --headed" - }, - "devDependencies": { - "@playwright/test": "^1.59.1" - } -} diff --git a/playground/dist/assets/PrimitivesShowcase-B55FOguW.css b/playground/dist/assets/PrimitivesShowcase-B55FOguW.css deleted file mode 100644 index 097442d..0000000 --- a/playground/dist/assets/PrimitivesShowcase-B55FOguW.css +++ /dev/null @@ -1 +0,0 @@ -._root_1attb_1{max-width:1200px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-5)}._header_1attb_9{display:flex;flex-direction:column;gap:var(--sp-2)}._header_1attb_9 h1{margin:0;font-size:var(--fs-xl);color:var(--accent)}._lede_1attb_21{color:var(--fg-mute);margin:0;max-width:720px}._grid_1attb_27{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:var(--sp-4)}._card_1attb_33{background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-3);padding:var(--sp-4);display:flex;flex-direction:column;gap:var(--sp-3);min-height:280px}._cardHead_1attb_44{display:flex;flex-direction:column;gap:var(--sp-1);border-bottom:1px solid var(--line);padding-bottom:var(--sp-2)}._cardTitle_1attb_52{margin:0;font-size:var(--fs-md);color:var(--fg)}._cardDesc_1attb_58{margin:0;font-size:var(--fs-xs);color:var(--fg-mute)}._cardBody_1attb_64{display:flex;flex-direction:column;align-items:stretch;flex:1;justify-content:flex-start}._bank_3njyn_1{display:flex;flex-direction:column;gap:var(--sp-3);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-3)}._title_3njyn_11{font-size:var(--fs-md);margin:0 0 var(--sp-2) 0;color:var(--fg)}._section_3njyn_17{display:flex;flex-direction:column;gap:var(--sp-2)}._sectionHeader_3njyn_23{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-1) var(--sp-2);background:var(--bg-2);border:1px solid var(--line);text-align:left;width:100%;font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.08em;color:var(--fg-mute)}._caret_3njyn_38{width:1ch;display:inline-block}._sectionName_3njyn_43{flex:1}._sectionCount_3njyn_47{color:var(--fg-dim)}._list_3njyn_51{display:flex;flex-direction:column;gap:var(--sp-2);padding:var(--sp-1) 0} diff --git a/playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js b/playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js deleted file mode 100644 index d5fc798..0000000 --- a/playground/dist/assets/PrimitivesShowcase-D2lYbVrQ.js +++ /dev/null @@ -1,2 +0,0 @@ -import{c as d,i as o,a as u,S,t as p,b as D,d as x,e as z,s as O,F as R,f as F,V as j,X as V,o as M,g as T,h as P,H as I,P as k,O as L,T as q,D as B,C as H,j as A,k as J,J as G,W as U,G as X,L as W}from"./index-C1-a2ZPT.js";const Y="_root_1attb_1",Z="_header_1attb_9",K="_lede_1attb_21",Q="_grid_1attb_27",ee="_card_1attb_33",te="_cardHead_1attb_44",ne="_cardTitle_1attb_52",ae="_cardDesc_1attb_58",le="_cardBody_1attb_64",b={root:Y,header:Z,lede:K,grid:Q,card:ee,cardHead:te,cardTitle:ne,cardDesc:ae,cardBody:le};var re=p("
");const ie=()=>{const[a,i]=d(.5),[r,e]=d(20),[l,t]=d(0);return(()=>{var n=re();return o(n,u(S,{get value(){return a()},onChange:i,min:0,max:1,step:.01,label:"linear"}),null),o(n,u(S,{get value(){return r()},onChange:e,min:1,max:1e3,curve:"exp",label:"exp (1..1000)",unit:"hz"}),null),o(n,u(S,{get value(){return l()},onChange:t,min:-50,max:50,step:1,label:"bipolar (-50..50)"}),null),n})()},oe="_bank_3njyn_1",se="_title_3njyn_11",ce="_section_3njyn_17",ue="_sectionHeader_3njyn_23",de="_caret_3njyn_38",me="_sectionName_3njyn_43",pe="_sectionCount_3njyn_47",ve="_list_3njyn_51",$={bank:oe,title:se,section:ce,sectionHeader:ue,caret:de,sectionName:me,sectionCount:pe,list:ve};var he=p("

"),N=p("
"),ge=p("\n \n \n
\n {({ cfg, index }) => (\n props.onChange(cfg.id, v, index)}\n min={cfg.min}\n max={cfg.max}\n step={cfg.step}\n curve={cfg.curve}\n curveParam={cfg.curveParam}\n label={cfg.label}\n unit={cfg.unit}\n />\n )}\n
\n
\n \n )}\n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { SliderBank, type SliderConfig } from './SliderBank';\n\nconst CFG: SliderConfig[] = [\n { id: 'cutoff', label: 'Cutoff', min: 20, max: 20000, curve: 'exp', section: 'Filter', unit: 'Hz' },\n { id: 'res', label: 'Resonance', min: 0, max: 1, step: 0.01, section: 'Filter' },\n { id: 'attack', label: 'Attack', min: 0, max: 5, step: 0.01, section: 'Envelope', unit: 's' },\n { id: 'decay', label: 'Decay', min: 0, max: 5, step: 0.01, section: 'Envelope', unit: 's' },\n { id: 'sustain', label: 'Sustain', min: 0, max: 1, step: 0.01, section: 'Envelope' },\n { id: 'release', label: 'Release', min: 0, max: 5, step: 0.01, section: 'Envelope', unit: 's' },\n];\n\nexport const SliderBankDemo: Component = () => {\n const [values, setValues] = createSignal([8000, 0.3, 0.05, 0.4, 0.7, 1.0]);\n return (\n setValues((prev) => prev.map((x, j) => (j === i ? v : x)))}\n title=\"Synth\"\n collapsedSections={[]}\n />\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { VirtualJoystick } from './VirtualJoystick';\n\nexport const VirtualJoystickDemo: Component = () => {\n const [pos, setPos] = createSignal([0.5, 0.5]);\n return (\n
\n setPos([x, y])}\n />\n \n x={pos()[0].toFixed(3)} y={pos()[1].toFixed(3)}\n \n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { XYPad } from './XYPad';\n\nexport const XYPadDemo: Component = () => {\n const [pos, setPos] = createSignal([0.5, 0.5]);\n return (\n
\n setPos([x, y])} />\n \n x={pos()[0].toFixed(3)} y={pos()[1].toFixed(3)}\n \n
\n );\n};\n","import { Component, createMemo, createSignal, onCleanup, onMount } from 'solid-js';\nimport { Heatmap, type HeatmapColorMode } from './Heatmap';\nimport { PillToggle } from './PillToggle';\n\nconst N = 16;\n\nfunction generateField(seed: number): Float32Array {\n const arr = new Float32Array(N * N);\n for (let y = 0; y < N; y++) {\n for (let x = 0; x < N; x++) {\n const fx = (x - N / 2) / (N / 2);\n const fy = (y - N / 2) / (N / 2);\n const r = Math.sqrt(fx * fx + fy * fy);\n const v = 0.5 + 0.5 * Math.sin(r * 3 + seed * 0.5) * Math.cos(fx * 4 + seed);\n arr[y * N + x] = Math.max(0, Math.min(1, v));\n }\n }\n return arr;\n}\n\nexport const HeatmapDemo: Component = () => {\n const [mode, setMode] = createSignal('luminance');\n const [tick, setTick] = createSignal(0);\n let timer: ReturnType;\n onMount(() => {\n timer = setInterval(() => setTick((t) => t + 1), 200);\n });\n onCleanup(() => clearInterval(timer));\n\n const samples = createMemo(() => generateField(tick()));\n\n return (\n
\n \n setMode(v as HeatmapColorMode)}\n />\n
\n );\n};\n","import { Component, createMemo, createSignal, onCleanup, onMount } from 'solid-js';\nimport { OutputDisplay } from './OutputDisplay';\n\nexport const OutputDisplayDemo: Component = () => {\n const [tick, setTick] = createSignal(0);\n let raf = 0;\n let last = performance.now();\n const animate = (t: number) => {\n if (t - last > 80) {\n setTick((x) => x + 1);\n last = t;\n }\n raf = requestAnimationFrame(animate);\n };\n onMount(() => { raf = requestAnimationFrame(animate); });\n onCleanup(() => cancelAnimationFrame(raf));\n\n const values = createMemo(() => {\n const t = tick();\n const arr = new Float32Array(126);\n for (let i = 0; i < arr.length; i++) {\n arr[i] = 0.5 + 0.5 * Math.sin(t * 0.05 + i * 0.21);\n }\n return arr;\n });\n\n return (\n
\n \n \n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { TrainingControls } from './TrainingControls';\n\nexport const TrainingControlsDemo: Component = () => {\n const [count, setCount] = createSignal(3);\n const [loss, setLoss] = createSignal(0.0123);\n const [busy, setBusy] = createSignal(false);\n const [stack, setStack] = createSignal([]);\n\n const train = () => {\n setBusy(true);\n setTimeout(() => {\n setLoss(Math.random() * 0.05);\n setBusy(false);\n }, 250);\n };\n\n return (\n setLoss(0.5)}\n onThumbsUp={() => { setStack((s) => [...s, count()]); setCount((c) => c + 1); train(); }}\n onThumbsDown={() => { setStack((s) => [...s, count()]); setLoss((l) => (l ?? 0) * 1.4); }}\n onUndo={() => { const prev = [...stack()]; const v = prev.pop(); if (v !== undefined) setCount(v); setStack(prev); }}\n exampleCount={count}\n lastLoss={loss}\n busy={busy}\n canUndo={() => stack().length > 0}\n />\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { Drawer } from './Drawer';\n\nexport const DrawerDemo: Component = () => {\n const [openR, setOpenR] = createSignal(false);\n const [openL, setOpenL] = createSignal(false);\n return (\n
\n \n \n setOpenL(false)} side=\"left\" title=\"Left drawer\">\n

Slides in from the left.

\n

Press Escape, click outside, or click × to close.

\n
\n setOpenR(false)} side=\"right\" title=\"Right drawer\">\n

Slides in from the right.

\n

Mode-specific drawers will use this primitive.

\n
\n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { ControlAxis } from './ControlAxis';\n\nexport const ControlAxisDemo: Component = () => {\n const [boldness, setBoldness] = createSignal(0.5);\n const [memory, setMemory] = createSignal(0.7);\n const [precision, setPrecision] = createSignal(0.3);\n const [preset, setPreset] = createSignal('default');\n\n const onAny = () => setPreset(null);\n\n return (\n
\n { setBoldness(v); onAny(); }}\n preset={preset}\n endpoints={['Caution', 'Bold']}\n accentColor=\"var(--accent)\"\n onDoubleTap={() => setBoldness(0.5)}\n />\n { setMemory(v); onAny(); }}\n preset={preset}\n endpoints={['Amnesia', 'Elephant']}\n accentColor=\"var(--accent-2)\"\n onDoubleTap={() => setMemory(0.5)}\n />\n { setPrecision(v); onAny(); }}\n preset={preset}\n endpoints={['Raw', 'Precise']}\n accentColor=\"var(--good)\"\n onDoubleTap={() => setPrecision(0.3)}\n />\n
\n );\n};\n","import { Component, createSignal, onCleanup, onMount } from 'solid-js';\nimport { ProgressRing } from './ProgressRing';\n\nexport const ProgressRingDemo: Component = () => {\n const [p, setP] = createSignal(0);\n let timer: ReturnType;\n onMount(() => {\n timer = setInterval(() => {\n setP((q) => (q + 0.02) % 1);\n }, 50);\n });\n onCleanup(() => clearInterval(timer));\n return (\n
\n \n \n \n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { PillToggle } from './PillToggle';\n\nexport const PillToggleDemo: Component = () => {\n const [mode, setMode] = createSignal('synth');\n const [size, setSize] = createSignal('m');\n return (\n
\n \n \n
\n );\n};\n","import { Component, createSignal } from 'solid-js';\nimport { ParamEditor, type ParamDef, type ParamOverride } from './ParamEditor';\n\nconst PARAM: ParamDef = {\n name: 'osc1_freq',\n label: 'Osc 1 Freq',\n hardMin: 20,\n hardMax: 8000,\n default: 440,\n curve: 'exp',\n group: 'oscillators',\n};\n\nexport const ParamEditorDemo: Component = () => {\n const [override, setOverride] = createSignal({\n min: 100,\n max: 2000,\n curve: 'exp',\n muted: false,\n pinned: false,\n fixedValue: 440,\n });\n return ;\n};\n","import { Component, createSignal, onCleanup, onMount } from 'solid-js';\nimport { JoyMap, type TrailPoint, type RegionPin } from './JoyMap';\nimport { Slider } from './Slider';\n\nexport const JoyMapDemo: Component = () => {\n const [zoom, setZoom] = createSignal(0.5);\n const [pos, setPos] = createSignal([0.5, 0.5]);\n const [trail, setTrail] = createSignal([]);\n const [pins] = createSignal([\n { id: 'p1', x: 0.6, y: 0.5, width: 0.18, height: 0.18, colorSlot: 0 },\n { id: 'p2', x: 0.18, y: 0.18, width: 0.12, height: 0.12, colorSlot: 1 },\n ]);\n const [frozen, setFrozen] = createSignal(false);\n\n // Simulated wandering position\n let raf = 0;\n let t0 = performance.now();\n const animate = () => {\n const t = (performance.now() - t0) / 1000;\n const x = 0.5 + 0.4 * Math.sin(t * 0.4);\n const y = 0.5 + 0.4 * Math.cos(t * 0.65);\n setPos([x, y]);\n setTrail((prev) => {\n const next = [...prev, { x, y, t: performance.now() }];\n // Cap trail length to avoid runaway growth\n return next.slice(-200);\n });\n raf = requestAnimationFrame(animate);\n };\n onMount(() => { raf = requestAnimationFrame(animate); });\n onCleanup(() => cancelAnimationFrame(raf));\n\n return (\n
\n [0.18, 0.06]}\n onTrailTap={(p) => setPos([p.x, p.y])}\n frozen={frozen}\n />\n \n \n
\n );\n};\n","import { Component, createMemo, createSignal } from 'solid-js';\nimport { WeightHealth, type WeightStatus } from './WeightHealth';\nimport { PillToggle } from './PillToggle';\n\nexport const WeightHealthDemo: Component = () => {\n const [status, setStatus] = createSignal('healthy');\n\n const histogram = createMemo(() => {\n const bins = new Array(10).fill(0);\n const s = status();\n if (s === 'dead') {\n bins[0] = 80; bins[1] = 12; bins[2] = 4; bins[3] = 2;\n } else if (s === 'saturating') {\n bins[7] = 12; bins[8] = 24; bins[9] = 60;\n } else {\n // healthy bell-ish\n for (let i = 0; i < 10; i++) {\n bins[i] = 30 * Math.exp(-Math.pow((i - 4.5) / 2.5, 2));\n }\n }\n return bins;\n });\n\n return (\n
\n \n setStatus(v as WeightStatus)}\n />\n
\n );\n};\n","import { Component, createMemo, createSignal } from 'solid-js';\nimport { GradientFlow, type GradientStatus } from './GradientFlow';\nimport { PillToggle } from './PillToggle';\n\nexport const GradientFlowDemo: Component = () => {\n const [pattern, setPattern] = createSignal<'healthy' | 'vanishing' | 'exploding'>('healthy');\n\n const norms = createMemo(() => {\n const p = pattern();\n if (p === 'vanishing') return [1.0, 0.4, 0.15, 0.04];\n if (p === 'exploding') return [0.4, 0.8, 1.6, 3.2];\n return [0.6, 0.55, 0.5, 0.5];\n });\n\n const status = createMemo(() => {\n const p = pattern();\n if (p === 'vanishing') return ['healthy', 'vanishing', 'vanishing', 'vanishing'];\n if (p === 'exploding') return ['healthy', 'healthy', 'exploding', 'exploding'];\n return ['healthy', 'healthy', 'healthy', 'healthy'];\n });\n\n return (\n
\n \n setPattern(v as 'healthy' | 'vanishing' | 'exploding')}\n />\n
\n );\n};\n","import { Component, createSignal, onCleanup, onMount } from 'solid-js';\nimport { LossPlot } from './LossPlot';\n\nexport const LossPlotDemo: Component = () => {\n const [history, setHistory] = createSignal([]);\n\n let timer: ReturnType;\n let i = 0;\n onMount(() => {\n timer = setInterval(() => {\n i += 1;\n const v = 0.5 * Math.exp(-i * 0.04) + 0.005 + 0.02 * Math.random();\n setHistory((h) => {\n const next = [...h, v];\n return next.slice(-200);\n });\n }, 80);\n });\n onCleanup(() => clearInterval(timer));\n\n return (\n
\n \n \n
\n );\n};\n","/**\n * Primitives showcase — Storybook-equivalent at /dev/primitives.\n *\n * Each section wraps one primitive's demo. Demos use realistic interactive\n * state so a quick visual scan answers \"does this thing actually work?\".\n */\n\nimport { Component, For, JSX } from 'solid-js';\nimport styles from './PrimitivesShowcase.module.css';\n\nimport { SliderDemo } from '../primitives/Slider.demo';\nimport { SliderBankDemo } from '../primitives/SliderBank.demo';\nimport { VirtualJoystickDemo } from '../primitives/VirtualJoystick.demo';\nimport { XYPadDemo } from '../primitives/XYPad.demo';\nimport { HeatmapDemo } from '../primitives/Heatmap.demo';\nimport { OutputDisplayDemo } from '../primitives/OutputDisplay.demo';\nimport { TrainingControlsDemo } from '../primitives/TrainingControls.demo';\nimport { DrawerDemo } from '../primitives/Drawer.demo';\nimport { ControlAxisDemo } from '../primitives/ControlAxis.demo';\nimport { ProgressRingDemo } from '../primitives/ProgressRing.demo';\nimport { PillToggleDemo } from '../primitives/PillToggle.demo';\nimport { ParamEditorDemo } from '../primitives/ParamEditor.demo';\nimport { JoyMapDemo } from '../primitives/JoyMap.demo';\nimport { WeightHealthDemo } from '../primitives/WeightHealth.demo';\nimport { GradientFlowDemo } from '../primitives/GradientFlow.demo';\nimport { LossPlotDemo } from '../primitives/LossPlot.demo';\n\ninterface Section {\n name: string;\n description: string;\n Demo: Component;\n}\n\nconst SECTIONS: ReadonlyArray
= [\n { name: 'Slider', description: 'Single-value slider with optional curve mapping.', Demo: SliderDemo },\n { name: 'SliderBank', description: 'Vertical stack with collapsible sections.', Demo: SliderBankDemo },\n { name: 'VirtualJoystick', description: 'Touch + pointer 2D input, returns [0,1]^2.', Demo: VirtualJoystickDemo },\n { name: 'XYPad', description: 'Rectangular 2D input pad.', Demo: XYPadDemo },\n { name: 'Heatmap', description: 'NxN colored grid, three color modes.', Demo: HeatmapDemo },\n { name: 'OutputDisplay', description: 'Bar chart of N output values.', Demo: OutputDisplayDemo },\n { name: 'TrainingControls', description: 'RL feedback + status panel.', Demo: TrainingControlsDemo },\n { name: 'Drawer', description: 'Slide-in panel from left/right.', Demo: DrawerDemo },\n { name: 'ControlAxis', description: 'Compound axis slider (Boldness/Memory/Precision shape).', Demo: ControlAxisDemo },\n { name: 'ProgressRing', description: 'Circular progress indicator.', Demo: ProgressRingDemo },\n { name: 'PillToggle', description: 'Segmented control.', Demo: PillToggleDemo },\n { name: 'ParamEditor', description: 'Range/curve/mute/pin for one parameter.', Demo: ParamEditorDemo },\n { name: 'JoyMap', description: 'Zoom minimap with trail, noise rings, region pins.', Demo: JoyMapDemo },\n { name: 'WeightHealth', description: 'Histogram with status glow.', Demo: WeightHealthDemo },\n { name: 'GradientFlow', description: 'Per-layer gradient bars.', Demo: GradientFlowDemo },\n { name: 'LossPlot', description: 'Training-loss line chart over time.', Demo: LossPlotDemo },\n];\n\nconst PrimitivesShowcase: Component = () => {\n return (\n
\n
\n

Primitive Library

\n

\n Each card is a live demo. Use this page as a sanity-check during the rewrite.\n Modes (stream 9) and feature parity (stream 10) compose these primitives.\n

\n
\n
\n {(section) => (\n
\n
\n

{section.name}

\n

{section.description}

\n
\n
\n {section.Demo({}) as unknown as JSX.Element}\n
\n
\n )}
\n
\n
\n );\n};\n\nexport default PrimitivesShowcase;\n"],"names":["SliderDemo","linear","setLinear","createSignal","exp","setExp","neg","setNeg","_el$","_tmpl$","_$insert","_$createComponent","Slider","value","onChange","min","max","step","label","curve","unit","group","configs","sections","current","forEach","cfg","index","sectionName","section","name","items","push","SliderBank","props","collapsed","setCollapsed","Object","fromEntries","collapsedSections","map","s","sliders","toggle","prev","_tmpl$2","Show","when","title","children","_el$2","_$effect","_$className","styles","For","each","_el$3","_tmpl$4","_el$4","_tmpl$3","_el$5","firstChild","_el$6","nextSibling","_el$7","$$click","length","_p$","_v$","sectionHeader","_v$2","_v$3","caret","_v$4","_v$5","sectionCount","e","t","_$setAttribute","a","o","i","undefined","_el$8","values","v","id","curveParam","list","bank","_$delegateEvents","CFG","SliderBankDemo","setValues","_id","x","j","VirtualJoystickDemo","pos","setPos","VirtualJoystick","size","position","onMove","y","toFixed","XYPadDemo","XYPad","N","generateField","seed","arr","Float32Array","fx","fy","r","Math","sqrt","sin","cos","HeatmapDemo","mode","setMode","tick","setTick","timer","onMount","setInterval","onCleanup","clearInterval","samples","createMemo","Heatmap","resolution","colorMode","showGrid","PillToggle","options","OutputDisplayDemo","raf","last","performance","now","animate","requestAnimationFrame","cancelAnimationFrame","OutputDisplay","width","height","compact","TrainingControlsDemo","count","setCount","loss","setLoss","busy","setBusy","stack","setStack","train","setTimeout","random","TrainingControls","onTrain","onRandomize","onThumbsUp","c","onThumbsDown","l","onUndo","pop","exampleCount","lastLoss","canUndo","DrawerDemo","openR","setOpenR","openL","setOpenL","_tmpl$5","Drawer","open","onClose","side","ControlAxisDemo","boldness","setBoldness","memory","setMemory","precision","setPrecision","preset","setPreset","onAny","ControlAxis","endpoints","accentColor","onDoubleTap","ProgressRingDemo","p","setP","q","ProgressRing","progress","color","showLabel","PillToggleDemo","setSize","PARAM","hardMin","hardMax","default","ParamEditorDemo","override","setOverride","muted","pinned","fixedValue","ParamEditor","param","JoyMapDemo","zoom","setZoom","trail","setTrail","pins","colorSlot","frozen","setFrozen","t0","slice","JoyMap","anchor","regionPins","noiseRings","onTrailTap","f","WeightHealthDemo","status","setStatus","histogram","bins","Array","fill","pow","WeightHealth","GradientFlowDemo","pattern","setPattern","norms","GradientFlow","layerNorms","LossPlotDemo","history","setHistory","h","LossPlot","log","SECTIONS","description","Demo","PrimitivesShowcase","_el$9","_el$0","card","_v$6","cardHead","_v$7","cardTitle","_v$8","cardDesc","_v$9","cardBody","root","header","lede","grid"],"mappings":"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"} \ No newline at end of file diff --git a/playground/dist/assets/index-C1-a2ZPT.js b/playground/dist/assets/index-C1-a2ZPT.js deleted file mode 100644 index 98e0804..0000000 --- a/playground/dist/assets/index-C1-a2ZPT.js +++ /dev/null @@ -1,3 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/PrimitivesShowcase-D2lYbVrQ.js","assets/PrimitivesShowcase-B55FOguW.css"])))=>i.map(i=>d[i]); -(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))a(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&a(o)}).observe(document,{childList:!0,subtree:!0});function n(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function a(r){if(r.ep)return;r.ep=!0;const i=n(r);fetch(r.href,i)}})();const J={context:void 0,registry:void 0,effects:void 0,done:!1,getContextId(){return ea(this.context.count)},getNextContextId(){return ea(this.context.count++)}};function ea(e){const t=String(e),n=t.length-1;return J.context.id+(n?String.fromCharCode(96+n):"")+t}function Zt(e){J.context=e}const Fa=!1,Pr=(e,t)=>e===t,tt=Symbol("solid-proxy"),kr=typeof Proxy=="function",bn=Symbol("solid-track"),pn={equals:Pr};let Ea=za;const ut=1,_n=2,Ia={owned:null,cleanups:null,context:null,owner:null},kn={};var be=null;let Ln=null,Lr=null,ve=null,De=null,lt=null,Cn=0;function Jt(e,t){const n=ve,a=be,r=e.length===0,i=t===void 0?a:t,o=r?Ia:{owned:null,cleanups:null,context:i?i.context:null,owner:i},s=r?e:()=>e(()=>pe(()=>nn(o)));be=o,ve=null;try{return st(s,!0)}finally{ve=n,be=a}}function ee(e,t){t=t?Object.assign({},pn,t):pn;const n={value:e,observers:null,observerSlots:null,comparator:t.equals||void 0},a=r=>(typeof r=="function"&&(r=r(n.value)),Ta(n,r));return[Oa.bind(n),a]}function Fr(e,t,n){const a=$n(e,t,!0,ut);Bt(a)}function A(e,t,n){const a=$n(e,t,!1,ut);Bt(a)}function ze(e,t,n){Ea=zr;const a=$n(e,t,!1,ut);(!n||!n.render)&&(a.user=!0),lt?lt.push(a):Bt(a)}function $e(e,t,n){n=n?Object.assign({},pn,n):pn;const a=$n(e,t,!0,0);return a.observers=null,a.observerSlots=null,a.comparator=n.equals||void 0,Bt(a),Oa.bind(a)}function Er(e){return e&&typeof e=="object"&&"then"in e}function Ir(e,t,n){let a,r,i;a=!0,r=e,i={};let o=null,s=kn,l=null,d=!1,c="initialValue"in i,u=typeof a=="function"&&$e(a);const m=new Set,[v,g]=(i.storage||ee)(i.initialValue),[f,w]=ee(void 0),[x,_]=ee(void 0,{equals:!1}),[M,O]=ee(c?"ready":"unresolved");J.context&&(l=J.getNextContextId(),i.ssrLoadFrom==="initial"?s=i.initialValue:J.load&&J.has(l)&&(s=J.load(l)));function y(F,$,U,ne){return o===F&&(o=null,ne!==void 0&&(c=!0),(F===s||$===s)&&i.onHydrated&&queueMicrotask(()=>i.onHydrated(ne,{value:$})),s=kn,q($,U)),$}function q(F,$){st(()=>{$===void 0&&g(()=>F),O($!==void 0?"errored":c?"ready":"unresolved"),w($);for(const U of m.keys())U.decrement();m.clear()},!1)}function W(){const F=Rr,$=v(),U=f();if(U!==void 0&&!o)throw U;return ve&&ve.user,$}function N(F=!0){if(F!==!1&&d)return;d=!1;const $=u?u():a;if($==null||$===!1){y(o,pe(v));return}let U;const ne=s!==kn?s:pe(()=>{try{return r($,{value:v(),refetching:F})}catch(oe){U=oe}});if(U!==void 0){y(o,void 0,hn(U),$);return}else if(!Er(ne))return y(o,ne,void 0,$),ne;return o=ne,"v"in ne?(ne.s===1?y(o,ne.v,void 0,$):y(o,void 0,hn(ne.v),$),ne):(d=!0,queueMicrotask(()=>d=!1),st(()=>{O(c?"refreshing":"pending"),_()},!1),ne.then(oe=>y(ne,oe,void 0,$),oe=>y(ne,void 0,hn(oe),$)))}Object.defineProperties(W,{state:{get:()=>M()},error:{get:()=>f()},loading:{get(){const F=M();return F==="pending"||F==="refreshing"}},latest:{get(){if(!c)return W();const F=f();if(F&&!o)throw F;return v()}}});let Y=be;return u?Fr(()=>(Y=be,N(!1))):N(!1),[W,{refetch:F=>Ra(Y,()=>N(F)),mutate:g}]}function Qt(e){return st(e,!1)}function pe(e){if(ve===null)return e();const t=ve;ve=null;try{return e()}finally{ve=t}}function Yn(e){ze(()=>pe(e))}function et(e){return be===null||(be.cleanups===null?be.cleanups=[e]:be.cleanups.push(e)),e}function Vn(){return ve}function ta(){return be}function Ra(e,t){const n=be,a=ve;be=e,ve=null;try{return st(t,!0)}catch(r){Zn(r)}finally{be=n,ve=a}}const[nd,ad]=ee(!1);let Rr;function Oa(){if(this.sources&&this.state)if(this.state===ut)Bt(this);else{const e=De;De=null,st(()=>xn(this),!1),De=e}if(ve){const e=this.observers;if(!e||e[e.length-1]!==ve){const t=e?e.length:0;ve.sources?(ve.sources.push(this),ve.sourceSlots.push(t)):(ve.sources=[this],ve.sourceSlots=[t]),e?(e.push(ve),this.observerSlots.push(ve.sources.length-1)):(this.observers=[ve],this.observerSlots=[ve.sources.length-1])}}return this.value}function Ta(e,t,n){let a=e.value;return(!e.comparator||!e.comparator(a,t))&&(e.value=t,e.observers&&e.observers.length&&st(()=>{for(let r=0;r1e6)throw De=[],new Error},!1)),t}function Bt(e){if(!e.fn)return;nn(e);const t=Cn;Or(e,e.value,t)}function Or(e,t,n){let a;const r=be,i=ve;ve=be=e;try{a=e.fn(t)}catch(o){return e.pure&&(e.state=ut,e.owned&&e.owned.forEach(nn),e.owned=null),e.updatedAt=n+1,Zn(o)}finally{ve=i,be=r}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&"observers"in e?Ta(e,a):e.value=a,e.updatedAt=n)}function $n(e,t,n,a=ut,r){const i={fn:e,state:a,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:t,owner:be,context:be?be.context:null,pure:n};return be===null||be!==Ia&&(be.owned?be.owned.push(i):be.owned=[i]),i}function yn(e){if(e.state===0)return;if(e.state===_n)return xn(e);if(e.suspense&&pe(e.suspense.inFallback))return e.suspense.effects.push(e);const t=[e];for(;(e=e.owner)&&(!e.updatedAt||e.updatedAt=0;n--)if(e=t[n],e.state===ut)Bt(e);else if(e.state===_n){const a=De;De=null,st(()=>xn(e,t[0]),!1),De=a}}function st(e,t){if(De)return e();let n=!1;t||(De=[]),lt?n=!0:lt=[],Cn++;try{const a=e();return Tr(n),a}catch(a){n||(lt=null),De=null,Zn(a)}}function Tr(e){if(De&&(za(De),De=null),e)return;const t=lt;lt=null,t.length&&st(()=>Ea(t),!1)}function za(e){for(let t=0;t=0;t--)nn(e.tOwned[t]);delete e.tOwned}if(e.owned){for(t=e.owned.length-1;t>=0;t--)nn(e.owned[t]);e.owned=null}if(e.cleanups){for(t=e.cleanups.length-1;t>=0;t--)e.cleanups[t]();e.cleanups=null}e.state=0}function hn(e){return e instanceof Error?e:new Error(typeof e=="string"?e:"Unknown error",{cause:e})}function Zn(e,t=be){throw hn(e)}const Dr=Symbol("fallback");function na(e){for(let t=0;t1?[]:null;return et(()=>na(i)),()=>{let l=e()||[],d=l.length,c,u;return l[bn],pe(()=>{let v,g,f,w,x,_,M,O,y;if(d===0)o!==0&&(na(i),i=[],a=[],r=[],o=0,s&&(s=[])),n.fallback&&(a=[Dr],r[0]=Jt(q=>(i[0]=q,n.fallback())),o=1);else if(o===0){for(r=new Array(d),u=0;u=_&&O>=_&&a[M]===l[O];M--,O--)f[O]=r[M],w[O]=i[M],s&&(x[O]=s[M]);for(v=new Map,g=new Array(O+1),u=O;u>=_;u--)y=l[u],c=v.get(y),g[u]=c===void 0?-1:c,v.set(y,u);for(c=_;c<=M;c++)y=a[c],u=v.get(y),u!==void 0&&u!==-1?(f[u]=r[c],w[u]=i[c],s&&(x[u]=s[c]),u=g[u],v.set(y,u)):i[c]();for(u=_;ue(t||{}))}function ln(){return!0}const aa={get(e,t,n){return t===tt?n:e.get(t)},has(e,t){return t===tt?!0:e.has(t)},set:ln,deleteProperty:ln,getOwnPropertyDescriptor(e,t){return{configurable:!0,enumerable:!0,get(){return e.get(t)},set:ln,deleteProperty:ln}},ownKeys(e){return e.keys()}};function Nr(e,...t){const n=t.length;if(kr&&tt in e){const r=n>1?t.flat():t[0],i=t.map(o=>new Proxy({get(s){return o.includes(s)?e[s]:void 0},has(s){return o.includes(s)&&s in e},keys(){return o.filter(s=>s in e)}},aa));return i.push(new Proxy({get(o){return r.includes(o)?void 0:e[o]},has(o){return r.includes(o)?!1:o in e},keys(){return Object.keys(e).filter(o=>!r.includes(o))}},aa)),i}const a=[];for(let r=0;r<=n;r++)a[r]={};for(const r of Object.getOwnPropertyNames(e)){let i=n;for(let l=0;l{const i=J.context;if(i){const[s,l]=ee();J.count||(J.count=0),J.count++,(n||(n=e())).then(d=>{!J.done&&Zt(i),J.count--,l(()=>d.default),Zt()}),t=s}else if(!t){const[s]=Ir(()=>(n||(n=e())).then(l=>l.default));t=s}let o;return $e(()=>(o=t())?pe(()=>{if(!i||J.done)return o(r);const s=J.context;Zt(i);const l=o(r);return Zt(s),l}):"")};return a.preload=()=>n||((n=e()).then(r=>t=()=>r.default),n),a}const Vr=e=>`Stale read from <${e}>.`;function vt(e){const t="fallback"in e&&{fallback:()=>e.fallback};return $e(qr(()=>e.each,e.children,t||void 0))}function re(e){const t=e.keyed,n=$e(()=>e.when,void 0,void 0),a=t?n:$e(n,void 0,{equals:(r,i)=>!r==!i});return $e(()=>{const r=a();if(r){const i=e.children;return typeof i=="function"&&i.length>0?pe(()=>i(t?r:()=>{if(!pe(a))throw Vr("Show");return n()})):i}return e.fallback},void 0,void 0)}const jr=["allowfullscreen","async","alpha","autofocus","autoplay","checked","controls","default","disabled","formnovalidate","hidden","indeterminate","inert","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","seamless","selected","adauctionheaders","browsingtopics","credentialless","defaultchecked","defaultmuted","defaultselected","defer","disablepictureinpicture","disableremoteplayback","preservespitch","shadowrootclonable","shadowrootcustomelementregistry","shadowrootdelegatesfocus","shadowrootserializable","sharedstoragewritable"],Hr=new Set(["className","value","readOnly","noValidate","formNoValidate","isMap","noModule","playsInline","adAuctionHeaders","allowFullscreen","browsingTopics","defaultChecked","defaultMuted","defaultSelected","disablePictureInPicture","disableRemotePlayback","preservesPitch","shadowRootClonable","shadowRootCustomElementRegistry","shadowRootDelegatesFocus","shadowRootSerializable","sharedStorageWritable",...jr]),Wr=new Set(["innerHTML","textContent","innerText","children"]),Ur=Object.assign(Object.create(null),{className:"class",htmlFor:"for"}),Gr=Object.assign(Object.create(null),{class:"className",novalidate:{$:"noValidate",FORM:1},formnovalidate:{$:"formNoValidate",BUTTON:1,INPUT:1},ismap:{$:"isMap",IMG:1},nomodule:{$:"noModule",SCRIPT:1},playsinline:{$:"playsInline",VIDEO:1},readonly:{$:"readOnly",INPUT:1,TEXTAREA:1},adauctionheaders:{$:"adAuctionHeaders",IFRAME:1},allowfullscreen:{$:"allowFullscreen",IFRAME:1},browsingtopics:{$:"browsingTopics",IMG:1},defaultchecked:{$:"defaultChecked",INPUT:1},defaultmuted:{$:"defaultMuted",AUDIO:1,VIDEO:1},defaultselected:{$:"defaultSelected",OPTION:1},disablepictureinpicture:{$:"disablePictureInPicture",VIDEO:1},disableremoteplayback:{$:"disableRemotePlayback",AUDIO:1,VIDEO:1},preservespitch:{$:"preservesPitch",AUDIO:1,VIDEO:1},shadowrootclonable:{$:"shadowRootClonable",TEMPLATE:1},shadowrootdelegatesfocus:{$:"shadowRootDelegatesFocus",TEMPLATE:1},shadowrootserializable:{$:"shadowRootSerializable",TEMPLATE:1},sharedstoragewritable:{$:"sharedStorageWritable",IFRAME:1,IMG:1}});function Xr(e,t){const n=Gr[e];return typeof n=="object"?n[t]?n.$:void 0:n}const Yr=new Set(["beforeinput","click","dblclick","contextmenu","focusin","focusout","input","keydown","keyup","mousedown","mousemove","mouseout","mouseover","mouseup","pointerdown","pointermove","pointerout","pointerover","pointerup","touchend","touchmove","touchstart"]),Zr=new Set(["altGlyph","altGlyphDef","altGlyphItem","animate","animateColor","animateMotion","animateTransform","circle","clipPath","color-profile","cursor","defs","desc","ellipse","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","filter","font","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignObject","g","glyph","glyphRef","hkern","image","line","linearGradient","marker","mask","metadata","missing-glyph","mpath","path","pattern","polygon","polyline","radialGradient","rect","set","stop","svg","switch","symbol","text","textPath","tref","tspan","use","view","vkern"]),Kr={xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace"},zt=e=>$e(()=>e());function Jr(e,t,n){let a=n.length,r=t.length,i=a,o=0,s=0,l=t[r-1].nextSibling,d=null;for(;oc-s){const g=t[o];for(;s{r=i,t===document?e():b(t,e(),t.firstChild?null:void 0,n)},a.owner),()=>{r(),t.textContent=""}}function E(e,t,n,a){let r;const i=()=>{const s=a?document.createElementNS("http://www.w3.org/1998/Math/MathML","template"):document.createElement("template");return s.innerHTML=e,n?s.content.firstChild.firstChild:a?s.firstChild:s.content.firstChild},o=t?()=>pe(()=>document.importNode(r||(r=i()),!0)):()=>(r||(r=i())).cloneNode(!0);return o.cloneNode=o,o}function Je(e,t=window.document){const n=t[ra]||(t[ra]=new Set);for(let a=0,r=e.length;ar.call(e,n[1],i))}else e.addEventListener(t,n,typeof n!="function"&&n)}function Ke(e,t,n={}){const a=Object.keys(t||{}),r=Object.keys(n);let i,o;for(i=0,o=r.length;ir.children=an(e,t.children,r.children)),A(()=>typeof t.ref=="function"&&ct(t.ref,e)),A(()=>ai(e,t,n,!0,r,!0)),r}function ct(e,t,n){return pe(()=>e(t,n))}function b(e,t,n,a){if(n!==void 0&&!a&&(a=[]),typeof t!="function")return an(e,t,a,n);A(r=>an(e,t(),r,n),a)}function ai(e,t,n,a,r={},i=!1){t||(t={});for(const o in r)if(!(o in t)){if(o==="children")continue;r[o]=oa(e,o,null,r[o],n,i,t)}for(const o in t){if(o==="children")continue;const s=t[o];r[o]=oa(e,o,s,r[o],n,i,t)}}function ri(e){let t,n;return!kt()||!(t=J.registry.get(n=li()))?e():(J.completed&&J.completed.add(t),J.registry.delete(n),t)}function kt(e){return!!J.context&&!J.done&&(!e||e.isConnected)}function ii(e){return e.toLowerCase().replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}function ia(e,t,n){const a=t.trim().split(/\s+/);for(let r=0,i=a.length;r-1&&Kr[t.split(":")[0]];m?ei(e,m,t,n):X(e,Ur[t]||t,n)}return n}function oi(e){if(J.registry&&J.events&&J.events.find(([l,d])=>d===e))return;let t=e.target;const n=`$$${e.type}`,a=e.target,r=e.currentTarget,i=l=>Object.defineProperty(e,"target",{configurable:!0,value:l}),o=()=>{const l=t[n];if(l&&!t.disabled){const d=t[`${n}Data`];if(d!==void 0?l.call(t,d,e):l.call(t,e),e.cancelBubble)return}return t.host&&typeof t.host!="string"&&!t.host._$host&&t.contains(e.target)&&i(t.host),!0},s=()=>{for(;o()&&(t=t._$host||t.parentNode||t.host););};if(Object.defineProperty(e,"currentTarget",{configurable:!0,get(){return t||document}}),J.registry&&!J.done&&(J.done=_$HY.done=!0),e.composedPath){const l=e.composedPath();i(l[0]);for(let d=0;d{let l=t();for(;typeof l=="function";)l=l();n=an(e,l,n,a)}),()=>n;if(Array.isArray(t)){const l=[],d=n&&Array.isArray(n);if(jn(l,t,n,r))return A(()=>n=an(e,l,n,a,!0)),()=>n;if(i){if(!l.length)return n;if(a===void 0)return n=[...e.childNodes];let c=l[0];if(c.parentNode!==e)return n;const u=[c];for(;(c=c.nextSibling)!==a;)u.push(c);return n=u}if(l.length===0){if(n=Ft(e,n,a),s)return n}else d?n.length===0?la(e,l,a):Jr(e,n,l):(n&&Ft(e),la(e,l));n=l}else if(t.nodeType){if(i&&t.parentNode)return n=s?[t]:t;if(Array.isArray(n)){if(s)return n=Ft(e,n,a,t);Ft(e,n,null,t)}else n==null||n===""||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}}return n}function jn(e,t,n,a){let r=!1;for(let i=0,o=t.length;i=0;o--){const s=t[o];if(r!==s){const l=s.parentNode===e;!i&&!o?l?e.replaceChild(r,s):e.insertBefore(r,n):l&&s.remove()}else i=!0}}else e.insertBefore(r,n);return[r]}function li(){return J.getNextContextId()}const si="http://www.w3.org/2000/svg";function qa(e,t=!1,n=void 0){return t?document.createElementNS(si,e):document.createElement(e,{is:n})}function ui(e){const{useShadow:t}=e,n=document.createTextNode(""),a=()=>e.mount||document.body,r=ta();let i,o=!!J.context;return ze(()=>{o&&(ta().user=o=!1),i||(i=Ra(r,()=>$e(()=>e.children)));const s=a();if(s instanceof HTMLHeadElement){const[l,d]=ee(!1),c=()=>d(!0);Jt(u=>b(s,()=>l()?u():i(),null)),et(c)}else{const l=qa(e.isSVG?"g":"div",e.isSVG),d=t&&l.attachShadow?l.attachShadow({mode:"open"}):l;Object.defineProperty(l,"_$host",{get(){return n.parentNode},configurable:!0}),b(d,i),s.appendChild(l),e.ref&&e.ref(l),et(()=>s.removeChild(l))}},void 0,{render:!o}),n}function ci(e,t){const n=$e(e);return $e(()=>{const a=n();switch(typeof a){case"function":return pe(()=>a(t));case"string":const r=Zr.has(a),i=J.context?ri():qa(a,r,pe(()=>t.is));return ni(i,t,r),i}})}function di(e){const[,t]=Nr(e,["component"]);return ci(()=>e.component,t)}const mi="modulepreload",fi=function(e){return"/"+e},sa={},Na=function(t,n,a){let r=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),s=o?.nonce||o?.getAttribute("nonce");r=Promise.allSettled(n.map(l=>{if(l=fi(l),l in sa)return;sa[l]=!0;const d=l.endsWith(".css"),c=d?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${c}`))return;const u=document.createElement("link");if(u.rel=d?"stylesheet":mi,d||(u.as="script"),u.crossOrigin="",u.href=l,s&&u.setAttribute("nonce",s),document.head.appendChild(u),d)return new Promise((m,v)=>{u.addEventListener("load",m),u.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${l}`)))})}))}function i(o){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=o,window.dispatchEvent(s),!s.defaultPrevented)throw o}return r.then(o=>{for(const s of o||[])s.status==="rejected"&&i(s.reason);return t().catch(i)})},wn=Symbol("store-raw"),At=Symbol("store-node"),rt=Symbol("store-has"),Ba=Symbol("store-self");function Va(e){let t=e[tt];if(!t&&(Object.defineProperty(e,tt,{value:t=new Proxy(e,gi)}),!Array.isArray(e))){const n=Object.keys(e),a=Object.getOwnPropertyDescriptors(e),r=Object.getPrototypeOf(e),i=r!==null&&e!==null&&typeof e=="object"&&!Array.isArray(e)&&r!==Object.prototype;if(i){const o=Object.getOwnPropertyDescriptors(r);n.push(...Object.keys(o)),Object.assign(a,o)}for(let o=0,s=n.length;oe[tt][t]),n}function ja(e){Vn()&&rn(Sn(e,At),Ba)()}function vi(e){return ja(e),Reflect.ownKeys(e)}const gi={get(e,t,n){if(t===wn)return e;if(t===tt)return n;if(t===bn)return ja(e),n;const a=Sn(e,At),r=a[t];let i=r?r():e[t];if(t===At||t===rt||t==="__proto__")return i;if(!r){const o=Object.getOwnPropertyDescriptor(e,t);Vn()&&(typeof i!="function"||e.hasOwnProperty(t))&&!(o&&o.get)&&(i=rn(a,t,i)())}return Dt(i)?Va(i):i},has(e,t){return t===wn||t===tt||t===bn||t===At||t===rt||t==="__proto__"?!0:(Vn()&&rn(Sn(e,rt),t)(),t in e)},set(){return!0},deleteProperty(){return!0},ownKeys:vi,getOwnPropertyDescriptor:hi};function Nt(e,t,n,a=!1){if(t==="__proto__"||!a&&e[t]===n)return;const r=e[t],i=e.length;n===void 0?(delete e[t],e[rt]&&e[rt][t]&&r!==void 0&&e[rt][t].$()):(e[t]=n,e[rt]&&e[rt][t]&&r===void 0&&e[rt][t].$());let o=Sn(e,At),s;if((s=rn(o,t,r))&&s.$(()=>n),Array.isArray(e)&&e.length!==i){for(let l=e.length;l1){a=t.shift();const o=typeof a,s=Array.isArray(e);if(o==="string"&&(a==="__proto__"||t.length>1&&Wa(a)))return;if(Array.isArray(a)){for(let l=0;l1){Kt(e[a],t,[a].concat(n));return}r=e[a],n=[a].concat(n)}let i=t[0];typeof i=="function"&&(i=i(r,n),i===r)||a===void 0&&i==null||(i=qt(i),a===void 0||Dt(r)&&Dt(i)&&!Array.isArray(i)?Ha(r,i):Nt(e,a,i))}function gt(...[e,t]){const n=qt(e||{}),a=Array.isArray(n),r=Va(n);function i(...o){Qt(()=>{a&&o.length===1?bi(n,o[0]):Kt(n,o)})}return[r,i]}const Mn=new WeakMap,Ua={get(e,t){if(t===wn)return e;const n=e[t];if(t===tt||t===bn||t===At||t===rt||t==="__proto__")return n;let a;return Dt(n)?Mn.get(n)||(Mn.set(n,a=new Proxy(n,Ua)),a):n},set(e,t,n){return Nt(e,t,qt(n)),!0},deleteProperty(e,t){return Nt(e,t,void 0,!0),!0}};function D(e){return t=>{if(Dt(t)){let n;(n=Mn.get(t))||Mn.set(t,n=new Proxy(t,Ua)),e(n)}return t}}function pi(){const e=new Map,t=[];function n(o,s){const l=e.get(o);if(l)for(const d of l)try{d(s,o)}catch(c){console.error(`[bus] handler for "${o}" threw:`,c)}for(const{prefix:d,handler:c}of t)if(o===d||o.startsWith(d+"."))try{c(s,o)}catch(u){console.error(`[bus] prefix handler "${d}" threw on "${o}":`,u)}}function a(o,s){let l=e.get(o);return l||(l=new Set,e.set(o,l)),l.add(s),()=>{l.delete(s)}}function r(o,s){const l={prefix:o,handler:s};return t.push(l),()=>{const d=t.indexOf(l);d>=0&&t.splice(d,1)}}function i(){e.clear(),t.length=0}return{emit:n,on:a,onPrefix:r,clear:i}}const Fe=pi(),_i=200,Hn=new Map;let Wn=null;function yi(){Wn=null;for(const[e,t]of Hn)try{const n=t();n===void 0?localStorage.removeItem(e):localStorage.setItem(e,JSON.stringify(n))}catch(n){console.warn(`[persistence] failed to write "${e}":`,n)}Hn.clear()}function Vt(e,t){typeof localStorage>"u"||(Hn.set(e,t),Wn===null&&(Wn=setTimeout(yi,_i)))}function jt(e,t){if(typeof localStorage>"u")return t;try{const n=localStorage.getItem(e);return n===null?t:JSON.parse(n)}catch{return t}}const Ga="nisps:mode";function xi(){const e=jt(Ga,{});return{activeModeId:e.activeModeId??null,overrides:e.overrides??{}}}const[en,Ht]=gt(xi());function Wt(){Vt(Ga,()=>({activeModeId:en.activeModeId,overrides:en.overrides}))}const xe={state:en,switchMode(e){const t=en.activeModeId;t!==e&&(Ht(D(n=>{n.activeModeId=e,n.overrides[e]||(n.overrides[e]={})})),Wt(),Fe.emit("ui.mode_switch",{from:t,to:e}))},setOverride(e,t,n){Ht(D(a=>{a.overrides[e]||(a.overrides[e]={}),a.overrides[e][t]=n})),Wt(),Fe.emit("mode.params_changed",{modeId:e})},clearOverride(e,t){Ht(D(n=>{const a=n.overrides[e];a&&delete a[t]})),Wt(),Fe.emit("mode.params_changed",{modeId:e})},clearAllOverrides(e){Ht(D(t=>{t.overrides[e]={}})),Wt(),Fe.emit("mode.params_changed",{modeId:e})},getOverride(e,t){return en.overrides[e]?.[t]},reset(){Ht({activeModeId:null,overrides:{}}),Wt()}},wi="_wrap_1uogo_1",Si="_row_1uogo_12",Mi="_btn_1uogo_17",Ci="_thumbsUp_1uogo_25",$i="_thumbsDown_1uogo_34",Ai="_undo_1uogo_43",Pi="_btnAlt_1uogo_48",ki="_status_1uogo_55",Li="_statusItem_1uogo_63",Fi="_statusLabel_1uogo_70",Ei="_statusValue_1uogo_76",Ii="_busy_1uogo_81",Pe={wrap:wi,row:Si,btn:Mi,thumbsUp:Ci,thumbsDown:$i,undo:Ai,btnAlt:Pi,status:ki,statusItem:Li,statusLabel:Fi,statusValue:Ei,busy:Ii};var Ri=E("loss"),Oi=E("training…"),Ti=E('
examples');const zi=e=>{const t=()=>e.busy?.()??!1,n=()=>e.canUndo?.()??!0;return(()=>{var a=Ti(),r=a.firstChild,i=r.firstChild,o=i.nextSibling,s=o.nextSibling,l=r.nextSibling,d=l.firstChild,c=d.nextSibling,u=l.nextSibling,m=u.firstChild,v=m.firstChild,g=v.nextSibling;return $t(i,"click",e.onThumbsDown,!0),$t(o,"click",e.onThumbsUp,!0),$t(s,"click",e.onUndo,!0),$t(d,"click",e.onTrain,!0),$t(c,"click",e.onRandomize,!0),b(g,()=>e.exampleCount()),b(u,p(re,{get when(){return e.lastLoss()!==null},get children(){var f=Ri(),w=f.firstChild,x=w.nextSibling;return b(x,()=>e.lastLoss().toExponential(2)),A(_=>{var M=Pe.statusItem,O=Pe.statusLabel,y=Pe.statusValue;return M!==_.e&&h(f,_.e=M),O!==_.t&&h(w,_.t=O),y!==_.a&&h(x,_.a=y),_},{e:void 0,t:void 0,a:void 0}),f}}),null),b(u,p(re,{get when(){return t()},get children(){var f=Oi();return A(()=>h(f,`${Pe.statusItem} ${Pe.busy}`)),f}}),null),A(f=>{var w=Pe.wrap,x=Pe.row,_=`${Pe.btn} ${Pe.thumbsDown}`,M=t(),O=`${Pe.btn} ${Pe.thumbsUp}`,y=t(),q=`${Pe.btn} ${Pe.undo}`,W=t()||!n(),N=Pe.row,Y=Pe.btnAlt,F=t(),$=Pe.btnAlt,U=t(),ne=Pe.status,oe=Pe.statusItem,S=Pe.statusLabel,te=Pe.statusValue;return w!==f.e&&h(a,f.e=w),x!==f.t&&h(r,f.t=x),_!==f.a&&h(i,f.a=_),M!==f.o&&(i.disabled=f.o=M),O!==f.i&&h(o,f.i=O),y!==f.n&&(o.disabled=f.n=y),q!==f.s&&h(s,f.s=q),W!==f.h&&(s.disabled=f.h=W),N!==f.r&&h(l,f.r=N),Y!==f.d&&h(d,f.d=Y),F!==f.l&&(d.disabled=f.l=F),$!==f.u&&h(c,f.u=$),U!==f.c&&(c.disabled=f.c=U),ne!==f.w&&h(u,f.w=ne),oe!==f.m&&h(m,f.m=oe),S!==f.f&&h(v,f.f=S),te!==f.y&&h(g,f.y=te),f},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0,d:void 0,l:void 0,u:void 0,c:void 0,w:void 0,m:void 0,f:void 0,y:void 0}),a})()};Je(["click"]);const Di="_axis_1gq0t_1",qi="_head_1gq0t_12",Ni="_label_1gq0t_19",Bi="_preset_1gq0t_27",Vi="_value_1gq0t_34",ji="_input_1gq0t_42",Hi="_endpoints_1gq0t_87",Wi="_disabled_1gq0t_96",ft={axis:Di,head:qi,label:Ni,preset:Bi,value:Vi,input:ji,endpoints:Hi,disabled:Wi};var Ui=E(""),Gi=E("
"),Xi=E("
");const Yi=e=>{let t=0;const n=()=>{const i=performance.now();i-t<350&&e.onDoubleTap?(e.onDoubleTap(),t=0):t=i},a=i=>{const o=parseFloat(i.currentTarget.value);Number.isNaN(o)||e.onChange(Math.max(0,Math.min(1,o)))},r=()=>e.accentColor??"var(--accent)";return(()=>{var i=Xi(),o=i.firstChild,s=o.firstChild,l=s.nextSibling,d=o.nextSibling;return b(s,()=>e.label),b(o,p(re,{get when(){return e.preset?.()},get children(){var c=Ui();return b(c,e.preset),A(()=>h(c,ft.preset)),c}}),l),b(l,()=>e.value().toFixed(2)),d.$$pointerdown=n,d.$$input=a,b(i,p(re,{get when(){return e.endpoints},get children(){var c=Gi(),u=c.firstChild,m=u.nextSibling;return b(u,()=>e.endpoints[0]),b(m,()=>e.endpoints[1]),A(()=>h(c,ft.endpoints)),c}}),null),A(c=>{var u=ft.axis,m={[ft.disabled]:!!e.disabled},v=r(),g=ft.head,f=ft.label,w=ft.value,x=ft.input,_=e.disabled,M=`${e.label} control axis`;return u!==c.e&&h(i,c.e=u),c.t=Ke(i,m,c.t),v!==c.a&&ke(i,"--axis-accent",c.a=v),g!==c.o&&h(o,c.o=g),f!==c.i&&h(s,c.i=f),w!==c.n&&h(l,c.n=w),x!==c.s&&h(d,c.s=x),_!==c.h&&(d.disabled=c.h=_),M!==c.r&&X(d,"aria-label",c.r=M),c},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0}),A(()=>d.value=e.value()),i})()};Je(["input","pointerdown"]);const Zi="_group_1erfx_1",Ki="_pill_1erfx_10",Ji="_active_1erfx_22",Qi="_disabled_1erfx_31",sn={group:Zi,pill:Ki,active:Ji,disabled:Qi};var eo=E("
"),to=E("
');const ua=e=>{const t=n=>{e.open&&e.dismissable!==!1&&n.key==="Escape"&&(n.stopPropagation(),e.onClose())};return Yn(()=>{document.addEventListener("keydown",t)}),et(()=>{document.removeEventListener("keydown",t)}),p(re,{get when(){return e.open},get children(){return p(ui,{get children(){var n=mo(),a=n.firstChild,r=a.nextSibling,i=r.firstChild,o=i.firstChild,s=o.nextSibling,l=i.nextSibling;return a.$$click=()=>{e.dismissable!==!1&&e.onClose()},b(o,()=>e.title??""),$t(s,"click",e.onClose,!0),b(l,()=>e.children),A(d=>{var c=ot.root,u={[ot.left]:e.side==="left",[ot.right]:e.side==="right"},m=e.title??"drawer",v=ot.scrim,g=ot.panel,f=`${e.width??360}px`,w=ot.header,x=ot.title,_=ot.close,M=ot.body;return c!==d.e&&h(n,d.e=c),d.t=Ke(n,u,d.t),m!==d.a&&X(n,"aria-label",d.a=m),v!==d.o&&h(a,d.o=v),g!==d.i&&h(r,d.i=g),f!==d.n&&ke(r,"width",d.n=f),w!==d.s&&h(i,d.s=w),x!==d.h&&h(o,d.h=x),_!==d.r&&h(s,d.r=_),M!==d.d&&h(l,d.d=M),d},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0,d:void 0}),n}})}})};Je(["click"]);const fo="_canvas_mlv8d_1",ho={canvas:fo};var vo=E("");const ca=12,Fn=5e3,da=1.5,go=4,bo=600,ma=["rgba(255, 106, 0, 0.25)","rgba(0, 204, 255, 0.25)","rgba(180, 100, 255, 0.25)","rgba(80, 200, 120, 0.25)","rgba(255, 200, 80, 0.25)"],fa=["rgba(255, 106, 0, 0.7)","rgba(0, 204, 255, 0.7)","rgba(180, 100, 255, 0.7)","rgba(80, 200, 120, 0.7)","rgba(255, 200, 80, 0.7)"];function po(e,t){if(!(t.length<2)){e.beginPath(),e.moveTo(t[0].x,t[0].y);for(let n=0;n{let t,n=0,a=null;const r=()=>e.size??200,i=(c,u,m,v)=>{const g=m<=.1?16:m<=.3?8:m<=.6?4:2;c.strokeStyle="rgba(255,255,255,0.04)",c.lineWidth=1;for(let x=1;x{if(!t)return;const c=window.devicePixelRatio||1,u=r()*c;(t.width!==u||t.height!==u)&&(t.width=u,t.height=u);const m=t.getContext("2d");if(!m)return;m.clearRect(0,0,u,u);const v=Math.max(.01,Math.min(1,e.zoom())),g=e.anchor(),f=e.position(),w=e.trail(),x=e.regionPins?.()??[];m.fillStyle="#0a0a0a",m.fillRect(0,0,u,u),i(m,u,v,g);for(const y of x){const q=y.x*u,W=(1-y.y-y.height)*u,N=y.width*u,Y=y.height*u,F=ma[y.colorSlot%ma.length],$=fa[y.colorSlot%fa.length];m.fillStyle=F,m.fillRect(q,W,N,Y),m.strokeStyle=$,m.lineWidth=2*c,m.strokeRect(q,W,N,Y)}if(w.length>=2){const y=performance.now(),q=da+(go-da)*(1-v),W=w.filter(N=>y-N.t<=Fn).map(N=>({x:N.x*u,y:(1-N.y)*u}));W.length>=2&&(m.lineWidth=q*c,m.lineCap="round",m.lineJoin="round",m.strokeStyle="rgba(255, 106, 0, 0.55)",po(m,W));for(const N of w){const Y=y-N.t;if(Y>Fn)continue;const F=1-Y/Fn;m.fillStyle=`rgba(255, 106, 0, ${F*.6})`;const $=N.x*u,U=(1-N.y)*u;m.beginPath(),m.arc($,U,2*c,0,Math.PI*2),m.fill()}}const _=f[0]*u,M=(1-f[1])*u;m.fillStyle="#00ccff",m.beginPath(),m.arc(_,M,4*c,0,Math.PI*2),m.fill(),m.strokeStyle="rgba(0, 204, 255, 0.4)",m.lineWidth=1,m.beginPath(),m.arc(_,M,9*c,0,Math.PI*2),m.stroke();const O=e.noiseRings?.();if(O){const[y,q]=O;m.strokeStyle="rgba(255, 200, 80, 0.4)",m.beginPath(),m.arc(_,M,y*u,0,Math.PI*2),m.stroke(),m.strokeStyle="rgba(255, 200, 80, 0.6)",m.beginPath(),m.arc(_,M,q*u,0,Math.PI*2),m.stroke()}e.frozen?.()&&(m.fillStyle="rgba(100, 180, 255, 0.15)",m.fillRect(0,0,u,u),m.fillStyle="rgba(100, 180, 255, 0.85)",m.font=`${12*c}px var(--font-mono)`,m.textAlign="center",m.fillText("FROZEN",u/2,u-12*c))},s=()=>{o(),n=requestAnimationFrame(s)};Yn(()=>{n=requestAnimationFrame(s)}),et(()=>{n&&cancelAnimationFrame(n),a&&clearTimeout(a)}),ze(()=>{e.zoom(),e.position(),e.trail(),o()});const l=c=>{if(e.onLongPress&&(a=setTimeout(()=>{e.onLongPress?.(),a=null},bo)),t&&e.onTrailTap){const u=t.getBoundingClientRect(),m=c.clientX-u.left,v=c.clientY-u.top,g=e.trail();for(const f of g){const w=f.x*u.width,x=(1-f.y)*u.height,_=w-m,M=x-v;if(_*_+M*M<=ca*ca){e.onTrailTap({x:f.x,y:f.y,t:f.t}),a&&(clearTimeout(a),a=null);return}}}},d=()=>{a&&(clearTimeout(a),a=null)};return(()=>{var c=vo();c.addEventListener("pointercancel",d),c.$$pointerup=d,c.$$pointerdown=l;var u=t;return typeof u=="function"?ct(u,c):t=c,A(m=>{var v=ho.canvas,g=`${r()}px`,f=`${r()}px`,w=e.ariaLabel??"Joy map";return v!==m.e&&h(c,m.e=v),g!==m.t&&ke(c,"width",m.t=g),f!==m.a&&ke(c,"height",m.a=f),w!==m.o&&X(c,"aria-label",m.o=w),m},{e:void 0,t:void 0,a:void 0,o:void 0}),c})()};Je(["pointerdown","pointerup"]);function We(e){return e<0?0:e>1?1:e}function K(e,t,n){return en?n:e}function yo(e){return We(e)}function xo(e,t=4){if(e<=0)return 0;if(e>=1)return 1;const n=Math.exp(t)-1;return n===0?e:(Math.exp(t*e)-1)/n}function wo(e,t=4){if(e<=0)return 0;if(e>=1)return 1;const n=Math.exp(t)-1;return n===0?e:Math.log(1+e*n)/t}function So(e){const t=We(e);return t*t}function Mo(e){return Math.sqrt(We(e))}function Co(e,t=8){const n=(e-.5)*t,a=1/(1+Math.exp(-n)),r=1/(1+Math.exp(t/2)),i=1/(1+Math.exp(-t/2));return(a-r)/(i-r)}function $o(e){const t=We(e);return t*t*(3-2*t)}function Un(e,t){if(t===1)return We(e);const n=e-.5,r=(n<0?-1:1)*Math.pow(Math.abs(n)*2,t)/2;return We(r+.5)}function Xa(e,t,n){switch(e){case"linear":return yo(t);case"exp":return xo(t,n??4);case"log":return wo(t,n??4);case"square":return So(t);case"sqrt":return Mo(t);case"sigmoid":return Co(t,n??8);case"cubic":return $o(t);case"centered_power":return Un(t,n??1)}}const Ao=["linear","exp","log","square","sqrt","sigmoid","cubic","centered_power"],Ya="nisps:control",Po=[{axis:0,values:{zoom:.1,noiseCap:.02,noiseGrowth:1.1,learningRate:.1,weightDecay:.15,noiseDistribution:"gaussian"}},{axis:.5,values:{zoom:.5,noiseCap:.12,noiseGrowth:1.5,learningRate:1,weightDecay:.06,noiseDistribution:"gaussian"}},{axis:1,values:{zoom:1,noiseCap:.3,noiseGrowth:2.5,learningRate:3,weightDecay:0,noiseDistribution:"cauchy"}}],ko=[{axis:0,values:{maxExamples:5,exampleDecay:.3,memoryWeightDecay:.2,noiseDecay:.85,convergenceThreshold:.001}},{axis:.5,values:{maxExamples:50,exampleDecay:.7,memoryWeightDecay:.06,noiseDecay:.97,convergenceThreshold:1e-5}},{axis:1,values:{maxExamples:500,exampleDecay:1,memoryWeightDecay:0,noiseDecay:.995,convergenceThreshold:1e-8}}],Lo=[{axis:0,values:{inputCurve:1,deadzone:0,smoothing:0,slewRate:1,momentumZoom:"off"}},{axis:.5,values:{inputCurve:1.5,deadzone:.05,smoothing:.15,slewRate:.3,momentumZoom:"off"}},{axis:1,values:{inputCurve:3,deadzone:.15,smoothing:.4,slewRate:.1,momentumZoom:"off"}}],Fo=[{id:"default",label:"Default",boldness:.5,memory:.5,precision:.3},{id:"first-touch",label:"First Touch",boldness:.2,memory:.7,precision:.6},{id:"jazz-hands",label:"Jazz Hands",boldness:.8,memory:.2,precision:0},{id:"sculptor",label:"Sculptor",boldness:.3,memory:.9,precision:.8},{id:"improviser",label:"Improviser",boldness:.6,memory:.3,precision:.2},{id:"microscope",label:"Microscope",boldness:.1,memory:1,precision:1}];function En(e,t){const n=K(t,0,1);let a=e[0],r=e[e.length-1];for(let s=0;s=l.axis&&n<=d.axis){a=l,r=d;break}}if(a===r)return{...a.values};const i=(n-a.axis)/(r.axis-a.axis),o={};for(const s of Object.keys(a.values)){const l=a.values[s],d=r.values[s];typeof l=="number"&&typeof d=="number"?o[s]=l+(d-l)*i:o[s]=i>=.75?d??l:l}return o}function Eo(){const e={boldness:.5,memory:.5,precision:.3,offsets:{boldness:{},memory:{},precision:{}},presetId:"default"},t=jt(Ya,{});return{boldness:t.boldness??e.boldness,memory:t.memory??e.memory,precision:t.precision??e.precision,offsets:t.offsets??e.offsets,presetId:t.presetId??e.presetId}}const[it,Ut]=gt(Eo());function Gt(){Vt(Ya,()=>({boldness:it.boldness,memory:it.memory,precision:it.precision,offsets:it.offsets,presetId:it.presetId}))}const Me={state:it,setAxis(e,t){const n=K(t,0,1);Ut(D(a=>{a[e]=n,a.presetId!==null&&(a.presetId=null)})),Gt()},setOffset(e,t,n){Ut(D(a=>{a.offsets[e][t]=n})),Gt()},clearOffsets(e){Ut(D(t=>{t.offsets[e]={}})),Gt()},applyPreset(e){const t=Fo.find(n=>n.id===e);return t?(Ut(D(n=>{n.boldness=t.boldness,n.memory=t.memory,n.precision=t.precision,n.presetId=t.id})),Gt(),!0):!1},resolveParams(){const e=En(Po,it.boldness),t=En(ko,it.memory),n=En(Lo,it.precision),a={...e,...t,...n},r=i=>{const o=it.offsets[i];for(const[s,l]of Object.entries(o)){const d=a[s];typeof d=="number"&&(a[s]=d+l)}};return r("boldness"),r("memory"),r("precision"),a},reset(){Ut({boldness:.5,memory:.5,precision:.3,offsets:{boldness:{},memory:{},precision:{}},presetId:"default"}),Gt()}},Pt=.01,Ot=1,un=Pt,Za=.4,vn=.2,gn=5,Ka=.95,Io=150,Gn=1/60,Ro={off:null,gentle:{factor:.6,minZoomMul:.3,maxZoomMul:1},strong:{factor:1.5,minZoomMul:.15,maxZoomMul:1}};function Ja(){return{zoom:1,zoomX:null,zoomY:null,anchorX:.5,anchorY:.5,anchorMode:"center",deadzone:0,inputCurve:1,inputCurveX:null,inputCurveY:null,smoothing:0,momentumZoom:"off",velocityWindow:Io,invertX:!1,invertY:!1}}function Kn(){return{smoothedX:.5,smoothedY:.5,velocityHistory:[],momentumZoomMultiplier:1,frozen:!1}}function ha(e,t){if(t<=0)return e;const n=e-.5,a=Math.abs(n),r=t*.5;if(a<=r)return .5;const i=n<0?-1:1,o=(a-r)/(.5-r)*.5;return .5+i*o}function va(e,t,n){return K(t+(e-.5)*n,0,1)}function ga(e,t,n,a){if(n<=0)return t;const r=a>0?a:Gn,i=1-n,o=1-Math.pow(1-i,r/Gn);return e+o*(t-e)}function Oo(e,t,n,a,r){const i=Ro[e.momentumZoom];if(!i)return{multiplier:1,history:[]};const o=performance.now(),s=e.velocityWindow,d=[...t.velocityHistory.filter(y=>o-y.t<=s),{x:n,y:a,t:o}];if(d.length<2)return{multiplier:1,history:d};const c=d[0],u=d[d.length-1],m=u.t-c.t;if(m<=0)return{multiplier:t.momentumZoomMultiplier,history:d};const v=u.x-c.x,g=u.y-c.y,w=Math.sqrt(v*v+g*g)/(m/1e3),x=K(w*i.factor,0,1),_=i.maxZoomMul-(i.maxZoomMul-i.minZoomMul)*x,M=K(r*6,0,1);return{multiplier:t.momentumZoomMultiplier+M*(_-t.momentumZoomMultiplier),history:d}}function To(e,t){return e.anchorMode==="center"?.5:(e.anchorMode==="sticky",e.anchorX)}function zo(e,t){return e.anchorMode==="center"?.5:(e.anchorMode==="sticky",e.anchorY)}function Do(e,t,n,a=Gn){const r=Math.max(0,a),i=t.zoomX??t.zoom,o=t.zoomY??t.zoom,s=i<=un,l=o<=un;if(s&&l)return{x:n.smoothedX,y:n.smoothedY,frozen:!0,state:{...n,frozen:!0}};let[c,u]=e,m=t.invertX?1-c:c,v=t.invertY?1-u:u;m=ha(m,t.deadzone),v=ha(v,t.deadzone);{const N=m-.5,Y=v-.5,F=Math.sqrt(N*N+Y*Y);if(F>.5&&F>1e-12){const $=.5/F;m=.5+N*$,v=.5+Y*$}}const g=To(t),f=zo(t),w=s?un:K(i*n.momentumZoomMultiplier,Pt,Ot),x=l?un:K(o*n.momentumZoomMultiplier,Pt,Ot);m=s?n.smoothedX:va(m,g,w),v=l?n.smoothedY:va(v,f,x);const _=t.inputCurveX??t.inputCurve,M=t.inputCurveY??t.inputCurve;s||(m=Un(m,_)),l||(v=Un(v,M));const O=s?n.smoothedX:ga(n.smoothedX,m,t.smoothing,r),y=l?n.smoothedY:ga(n.smoothedY,v,t.smoothing,r),{multiplier:q,history:W}=Oo(t,n,c,u,r);return{x:O,y,frozen:!1,state:{smoothedX:O,smoothedY:y,velocityHistory:W,momentumZoomMultiplier:q,frozen:!1}}}const Qa="nisps:input";function qo(){const e=Ja(),t=jt(Qa,{});return{...e,...t}}const[er,Ue]=gt(qo()),[xt,In]=gt(Kn());function Ge(){Vt(Qa,()=>({...er}))}const H={config:er,liveState:xt,setZoom(e){const t=K(e,Pt,Ot);Ue(D(n=>{const a=n.zoom;n.zoom=t,n.anchorMode==="auto"&&a!==t&&(n.anchorX=xt.smoothedX,n.anchorY=xt.smoothedY)})),Ge()},setZoomPerAxis(e,t){Ue(D(n=>{n.zoomX=e!==null?K(e,Pt,Ot):null,n.zoomY=t!==null?K(t,Pt,Ot):null,n.anchorMode==="auto"&&(n.anchorX=xt.smoothedX,n.anchorY=xt.smoothedY)})),Ge()},setAnchor(e,t){Ue(D(n=>{n.anchorX=K(e,0,1),n.anchorY=K(t,0,1)})),Ge()},setAnchorMode(e){Ue(D(t=>{t.anchorMode=e,e==="center"?(t.anchorX=.5,t.anchorY=.5):e==="auto"&&(t.anchorX=xt.smoothedX,t.anchorY=xt.smoothedY)})),Ge()},setDeadzone(e){Ue(D(t=>{t.deadzone=K(e,0,Za)})),Ge()},setInputCurve(e){Ue(D(t=>{t.inputCurve=K(e,vn,gn)})),Ge()},setInputCurvePerAxis(e,t){Ue(D(n=>{n.inputCurveX=e!==null?K(e,vn,gn):null,n.inputCurveY=t!==null?K(t,vn,gn):null})),Ge()},setSmoothing(e){Ue(D(t=>{t.smoothing=K(e,0,Ka)})),Ge()},setMomentumZoom(e){Ue(D(t=>{t.momentumZoom=e})),e==="off"&&In(D(t=>{t.velocityHistory=[],t.momentumZoomMultiplier=1})),Ge()},setInvert(e,t){Ue(D(n=>{n.invertX=e,n.invertY=t})),Ge()},setVelocityWindow(e){Ue(D(t=>{t.velocityWindow=K(e,50,500)})),Ge()},__setLiveState(e){In(e)},reset(){Ue(Ja()),In(Kn()),Ge()}},tr="nisps:session",No=20;function nr(){const e=jt(tr,{});return{snapshots:[],ab:{a:null,b:null,live:"B"},regionPins:e.regionPins??[],paramPins:e.paramPins??[],presets:e.presets??[]}}const[Ye,Ee]=gt(nr());function wt(){Vt(tr,()=>({regionPins:Ye.regionPins,paramPins:Ye.paramPins,presets:Ye.presets}))}let Xn=0;function Bo(){return Xn+=1,`snap-${Date.now().toString(36)}-${Xn}`}const Z={state:Ye,pushSnapshot(e,t={}){const n={id:Bo(),tag:e,timestamp:Date.now(),noiseLevel:t.noiseLevel??0,zoomLevel:t.zoomLevel??null,weights:t.weights?new Float32Array(t.weights):null};return Ee(D(a=>{a.snapshots.push(n),a.snapshots.length>No&&a.snapshots.shift()})),Fe.emit("snap.push",{tag:e}),n},peekSnapshot(){return Ye.snapshots.length>0?Ye.snapshots[Ye.snapshots.length-1]:null},listSnapshots(){return Ye.snapshots},popSnapshot(){let e=null;Ee(D(n=>{e=n.snapshots.pop()??null}));const t=e?.tag??null;return Fe.emit("snap.pop",{tag:t}),e},jumpToSnapshot(e){const t=Ye.snapshots.find(n=>n.id===e)??null;return t?(Ee(D(n=>{const a=n.snapshots.findIndex(r=>r.id===e);a>=0&&(n.snapshots=n.snapshots.slice(0,a+1))})),Fe.emit("snap.pop",{tag:t.tag}),t):null},clearSnapshots(){Ee(D(e=>{e.snapshots=[]})),Fe.emit("snap.cleared",void 0)},captureA(e){Ee(D(t=>{t.ab.a=e,t.ab.live="B"}))},captureB(e){Ee(D(t=>{t.ab.b=e}))},toggleAB(){let e="B";return Ee(D(t=>{e=t.ab.live==="A"?"B":"A",t.ab.live=e})),e},acceptB(){Ee(D(e=>{e.ab.a=null,e.ab.live="B"}))},revertToA(){Ee(D(e=>{e.ab.b=null,e.ab.live="A"}))},resetAB(){Ee(D(e=>{e.ab={a:null,b:null,live:"B"}}))},addRegionPin(e){const t=`pin-${Date.now().toString(36)}-${Math.floor(Math.random()*1e6)}`,n=e.colorSlot??Ye.regionPins.length%5,a={...e,colorSlot:n,id:t,createdAt:Date.now()};return Ee(D(r=>{r.regionPins.length>=5&&r.regionPins.shift(),r.regionPins.push(a)})),wt(),Fe.emit("pin.create",{kind:"region",id:t}),a},removeRegionPin(e){Ee(D(t=>{const n=t.regionPins.findIndex(a=>a.id===e);n>=0&&t.regionPins.splice(n,1)})),wt(),Fe.emit("pin.remove",{kind:"region",id:e})},clearRegionPins(){Ee(D(e=>{e.regionPins=[]})),wt()},toggleParamPin(e,t){let n=!1;return Ee(D(a=>{const r=a.paramPins.findIndex(i=>i.key===e);r>=0?(a.paramPins.splice(r,1),n=!1):(a.paramPins.push({key:e,outputIndex:t}),n=!0)})),wt(),n?Fe.emit("pin.create",{kind:"param",id:e}):Fe.emit("pin.remove",{kind:"param",id:e}),n},isParamPinned(e){return Ye.paramPins.some(t=>t.key===e)},clearParamPins(){Ee(D(e=>{e.paramPins=[]})),wt()},paramPinMask(e,t){const n=new Uint8Array(e);for(const a of Ye.paramPins)t&&!a.key.startsWith(t+":")||a.outputIndex>=0&&a.outputIndex{a.presets.push(n)})),wt(),n},removePreset(e){Ee(D(t=>{const n=t.presets.findIndex(a=>a.id===e);n>=0&&t.presets.splice(n,1)})),wt()},reset(){Ee(nr()),Xn=0}},ar="nisps:exploration";function rr(){return{noiseLevel:.05,noiseFloor:.005,noiseCap:.12,noiseGrowth:1.5,noiseDecay:.97,spread:.6,learningRate:1,weightDecay:.06,autoExploreEnabled:!1,autoExploreIntervalMs:2e3,autoExploreIntensity:.5,pressureForce:0,holdMs:0}}function Vo(){const e=rr(),t=jt(ar,{});return{...e,noiseFloor:t.noiseFloor??e.noiseFloor,noiseCap:t.noiseCap??e.noiseCap,noiseGrowth:t.noiseGrowth??e.noiseGrowth,noiseDecay:t.noiseDecay??e.noiseDecay,spread:t.spread??e.spread,learningRate:t.learningRate??e.learningRate,weightDecay:t.weightDecay??e.weightDecay,autoExploreEnabled:t.autoExploreEnabled??e.autoExploreEnabled,autoExploreIntervalMs:t.autoExploreIntervalMs??e.autoExploreIntervalMs,autoExploreIntensity:t.autoExploreIntensity??e.autoExploreIntensity}}const[He,Te]=gt(Vo());function Xe(){Vt(ar,()=>({noiseFloor:He.noiseFloor,noiseCap:He.noiseCap,noiseGrowth:He.noiseGrowth,noiseDecay:He.noiseDecay,spread:He.spread,learningRate:He.learningRate,weightDecay:He.weightDecay,autoExploreEnabled:He.autoExploreEnabled,autoExploreIntervalMs:He.autoExploreIntervalMs,autoExploreIntensity:He.autoExploreIntensity}))}const I={state:He,applyCompoundParams(e){Te(D(t=>{const n=l=>{const d=e[l];return typeof d=="number"?d:void 0},a=n("noiseCap");a!==void 0&&(t.noiseCap=K(a,.005,1));const r=n("noiseGrowth");r!==void 0&&(t.noiseGrowth=K(r,1,4));const i=n("noiseDecay");i!==void 0&&(t.noiseDecay=K(i,0,1));const o=n("learningRate");o!==void 0&&(t.learningRate=K(o,.01,10));const s=n("weightDecay");s!==void 0&&(t.weightDecay=K(s,0,.5)),t.noiseLevel>t.noiseCap&&(t.noiseLevel=t.noiseCap),t.noiseLevel{t.noiseLevel=K(e,t.noiseFloor,t.noiseCap)}))},setSpread(e){Te(D(t=>{t.spread=K(e,0,1)})),Xe()},setNoiseFloor(e){Te(D(t=>{t.noiseFloor=K(e,0,t.noiseCap),t.noiseLevel{t.noiseCap=K(e,t.noiseFloor,1),t.noiseLevel>t.noiseCap&&(t.noiseLevel=t.noiseCap)})),Xe()},setNoiseGrowth(e){Te(D(t=>{t.noiseGrowth=K(e,1,4)})),Xe()},setNoiseDecay(e){Te(D(t=>{t.noiseDecay=K(e,0,1)})),Xe()},setLearningRate(e){Te(D(t=>{t.learningRate=K(e,.001,10)})),Xe()},setWeightDecay(e){Te(D(t=>{t.weightDecay=K(e,0,.5)})),Xe()},growNoise(e=.5){let t=He.noiseLevel;return Te(D(n=>{const a=K(e,0,1),r=1+(n.noiseGrowth-1)*(.5+a);t=K(n.noiseLevel*r,n.noiseFloor,n.noiseCap),n.noiseLevel=t})),t},decayNoise(e=.5){let t=He.noiseLevel;return Te(D(n=>{const a=K(e,0,1),r=n.noiseDecay-(1-n.noiseDecay)*(a-.5)*.4;t=K(n.noiseLevel*K(r,0,1),n.noiseFloor,n.noiseCap),n.noiseLevel=t})),t},setAutoExplore(e){Te(D(t=>{t.autoExploreEnabled=e})),Xe()},setAutoExploreInterval(e){Te(D(t=>{t.autoExploreIntervalMs=K(e,500,1e4)})),Xe()},setAutoExploreIntensity(e){Te(D(t=>{t.autoExploreIntensity=K(e,.1,1)})),Xe()},setPressure(e,t){Te(D(n=>{n.pressureForce=K(e,0,1),n.holdMs=Math.max(0,t)}))},reset(){Te(rr()),Xe()}},jo="_wrap_lq3ei_1",Ho="_label_lq3ei_10",Wo="_row_lq3ei_17",Uo="_input_lq3ei_23",Go="_value_lq3ei_80",Xo="_unit_lq3ei_88",Yo="_disabled_lq3ei_93",St={wrap:jo,label:Ho,row:Wo,input:Uo,value:Go,unit:Xo,disabled:Yo};var ba=E(""),Zo=E("
\n );\n};\n\nexport default C15Mode;\n","/**\n * Mode registry — maps mode_id (and the special \"c15\" placeholder) to the\n * TSX component that renders it. The ModeSwitcher reads from this list to\n * populate its dropdown, and `App.tsx` looks up the active mode here.\n *\n * Order matters — the switcher renders modes in this order.\n */\n\nimport type { Component } from 'solid-js';\nimport { PAFSynthMode } from './PAFSynthMode';\nimport { ChannelStripMode } from './ChannelStripMode';\nimport { XIASRIMode } from './XIASRIMode';\nimport { VerbFXMode } from './VerbFXMode';\nimport { MEMLCeliumMode } from './MEMLCeliumMode';\nimport { BreakOrMode } from './BreakOrMode';\nimport { ElysiamorfMode } from './ElysiamorfMode';\nimport { SoundAnalysisMIDIMode } from './SoundAnalysisMIDIMode';\nimport { C15Mode } from './C15Mode';\n\nexport interface ModeRegistration {\n /** Stable id; matches the schema's `mode_id` for firmware modes. */\n id: string;\n /** Human-readable label for the switcher. */\n label: string;\n /** Short description for the switcher tooltip. */\n description: string;\n /** TSX component rendering the mode. */\n Component: Component;\n /** True for browser-only placeholders (currently just C15). */\n placeholder?: boolean;\n}\n\nexport const MODE_REGISTRY: ReadonlyArray = [\n {\n id: 'paf_synth',\n label: 'PAF Synth',\n description: 'Phase-aligned formant synth (XY pad).',\n Component: PAFSynthMode,\n },\n {\n id: 'channel_strip',\n label: 'Channel Strip',\n description: 'EQ + dynamics processing channel.',\n Component: ChannelStripMode,\n },\n {\n id: 'xiasri',\n label: 'XIASRI',\n description: 'Audio-reactive verb / pitch engine.',\n Component: XIASRIMode,\n },\n {\n id: 'verb_fx',\n label: 'Verb FX',\n description: 'Reverb / multi-effects unit.',\n Component: VerbFXMode,\n },\n {\n id: 'memlcelium',\n label: 'MEML Celium',\n description: 'Voice + dual-MLP CV/gate via uSEQ.',\n Component: MEMLCeliumMode,\n },\n {\n id: 'breakor',\n label: 'Breakor',\n description: 'Drum / breakbeat synthesis.',\n Component: BreakOrMode,\n },\n {\n id: 'elysiamorf',\n label: 'Elysiamorf',\n description: 'Granular morphing synth.',\n Component: ElysiamorfMode,\n },\n {\n id: 'sound_analysis_midi',\n label: 'Sound Analysis → MIDI',\n description: 'Audio features → MIDI CC routing.',\n Component: SoundAnalysisMIDIMode,\n },\n {\n id: 'c15',\n label: 'C15 (browser-only)',\n description: 'C15 WASM synth. Not yet ported.',\n Component: C15Mode,\n placeholder: true,\n },\n];\n\n/** Look up a mode by id, falling back to the first registration. */\nexport function getModeById(id: string | null): ModeRegistration {\n if (!id) return MODE_REGISTRY[0]!;\n return MODE_REGISTRY.find((m) => m.id === id) ?? MODE_REGISTRY[0]!;\n}\n\nexport { ModeShell } from './ModeShell';\nexport { useModeRuntime } from './mode-runtime';\nexport type { ModeRuntime } from './mode-runtime';\n","/**\n * ModeSwitcher — top-level select that picks the active mode.\n *\n * Writes through `modeStore.switchMode(id)` so persistence + the bus event\n * fire correctly. Reads the current selection back from the store so it\n * stays in sync with persisted state on first paint.\n *\n * Audio engine switching is handled inside the mode runtime (each mode\n * routes its own engine_id through `EngineHost.setEngine` when started).\n */\n\nimport { Component, createMemo, For, Show } from 'solid-js';\nimport { modeStore } from '../stores/mode-store';\nimport { MODE_REGISTRY, getModeById } from './index';\nimport styles from './ModeSwitcher.module.css';\n\nexport const ModeSwitcher: Component = () => {\n const activeId = () => modeStore.state.activeModeId ?? MODE_REGISTRY[0]!.id;\n const active = createMemo(() => getModeById(activeId()));\n\n return (\n
\n Mode\n modeStore.switchMode(e.currentTarget.value)}\n aria-label=\"Select active mode\"\n >\n \n {(m) => (\n \n )}\n \n \n \n {active().description}\n \n \n \n ⚠\n \n \n
\n );\n};\n\nexport default ModeSwitcher;\n","import { Component, createMemo, createSignal, lazy, onCleanup, Show } from 'solid-js';\nimport { Dynamic } from 'solid-js/web';\nimport { modeStore } from './stores/mode-store';\nimport { MODE_REGISTRY, getModeById } from './modes';\nimport { ModeSwitcher } from './modes/ModeSwitcher';\nimport styles from './App.module.css';\n\ntype Route = 'home' | 'primitives' | 'modes' | 'unknown';\n\nfunction parseRoute(path: string): Route {\n if (path === '' || path === '/' || path === '/index.html') return 'home';\n if (path === '/dev/primitives' || path === '/dev/primitives/') return 'primitives';\n if (path === '/modes' || path === '/modes/' || path.startsWith('/modes/')) return 'modes';\n return 'unknown';\n}\n\n// Lazy-loaded so the home route doesn't pay the cost of loading every\n// primitive demo. Stream 9/10 can grow the showcase freely.\nconst PrimitivesShowcase = lazy(() => import('./dev/PrimitivesShowcase'));\n\nconst App: Component = () => {\n const [route, setRoute] = createSignal(parseRoute(window.location.pathname));\n\n const onPop = () => setRoute(parseRoute(window.location.pathname));\n window.addEventListener('popstate', onPop);\n onCleanup(() => window.removeEventListener('popstate', onPop));\n\n const navigate = (path: string) => {\n if (window.location.pathname === path) return;\n window.history.pushState({}, '', path);\n setRoute(parseRoute(path));\n };\n\n return (\n
\n
\n MEMLNaut\n playground\n \n
\n
\n \n \n \n \n \n \n \n \n \n \n
\n

404

\n

No route at {window.location.pathname}.

\n

\n { e.preventDefault(); navigate('/'); }}>back to home\n

\n
\n
\n
\n
\n );\n};\n\nconst ModesPage: Component = () => {\n const activeId = () => modeStore.state.activeModeId ?? MODE_REGISTRY[0]!.id;\n const ActiveComponent = createMemo(() => getModeById(activeId()).Component);\n\n return (\n
\n \n \n
\n );\n};\n\ninterface HomeProps {\n navigate: (path: string) => void;\n}\n\nconst Home: Component = (props) => {\n return (\n
\n

MEMLNaut Playground

\n

\n Interactive ML control of audio. Stream 9: nine modes, one shell.\n

\n
    \n
  • \n {\n e.preventDefault();\n props.navigate('/modes');\n }}\n >\n Modes\n {' '}\n — pick a mode and start playing\n
  • \n
  • \n {\n e.preventDefault();\n props.navigate('/dev/primitives');\n }}\n >\n Primitives showcase\n {' '}\n — UI building blocks\n
  • \n
\n

\n ML inference runs on the main thread via WASM. Audio synthesis runs\n in an AudioWorklet — start it from inside any mode using the \"Start\n audio\" button. Audio cannot autoplay (browser policy).\n

\n
\n );\n};\n\nexport default App;\n","/**\n * Debug probe: window.__nisps\n *\n * Synchronous-or-immediate Promise API for Playwright tests and dev console\n * use. Bypasses SolidJS reactivity by reading/writing the underlying stores\n * and WASM directly — values propagate to the UI on the next reactive tick,\n * but the probe always returns the freshest state.\n *\n * Stream 10 expanded the surface to cover stream 8/9 features:\n * - Snapshot stack (push, pop, list)\n * - A/B compare (capture, toggle, accept, revert)\n * - Region pinning (add, remove, clear)\n * - Auto-explore (toggle, interval, intensity)\n * - Heatmap (refresh, color mode, getCells)\n * - Weight health (histogram, status, layer stats)\n * - Session presets (save, load, list, share URL)\n * - Override application (read, write, clear)\n *\n * Test contract: every method either returns a value, returns null/empty,\n * or returns an immediately-resolved Promise. None of them throw — bad\n * input is silently ignored.\n */\n\nimport { batch, untrack } from 'solid-js';\nimport { mlStore } from '../stores/ml-store';\nimport { modeStore } from '../stores/mode-store';\nimport { sessionStore } from '../stores/session-store';\nimport { explorationStore } from '../stores/exploration-store';\nimport { controlStore } from '../stores/control-store';\nimport { inputStore } from '../stores/input-store';\nimport { outputStore } from '../stores/output-store';\nimport { coreBus } from '../stores/bus';\nimport {\n autoSnapshot,\n undoLastSnapshot,\n captureA as snapCaptureA,\n toggleAB as snapToggleAB,\n acceptB as snapAcceptB,\n revertToA as snapRevertToA,\n} from '../features/snapshots';\nimport { HeatmapSampler } from '../features/heatmap-sampler';\nimport {\n weightHistogram,\n weightStatus as healthStatus,\n layerNorms,\n gradientStatuses,\n layerWeightCounts,\n} from '../features/weight-health';\nimport {\n captureSessionPreset,\n restoreSessionPreset,\n saveNamedPreset,\n loadNamedPreset,\n buildShareUrl,\n applyUrlParams,\n type SessionPresetPayload,\n} from '../features/session-preset';\nimport type { HeatmapColorMode } from '../features/heatmap-sampler';\n\nexport interface DebugProbe {\n /** Current 126-element output vector (Float32Array). */\n getOutputs(): Float32Array;\n /** Current per-param post-override values (length = active mode params). */\n getParamOutputs(): Float32Array;\n /** Last training loss, or null if no training has occurred. */\n getLoss(): number | null;\n /** Loss history of last training run. */\n getLossHistory(): ReadonlyArray;\n /** Flat weight array. */\n getWeights(): Float32Array;\n /** Number of training examples currently in the dataset. */\n getExampleCount(): number;\n /** Set joystick X/Y in [0,1] and run inference. */\n setInputs(x: number, y: number): void;\n /** Trigger thumbs-up RL feedback. */\n thumbsUp(): void;\n /** Trigger thumbs-down RL feedback. */\n thumbsDown(): void;\n /** Synchronous training; returns final loss. */\n train(): number;\n /** Async training; returns Promise. */\n trainAsync(): Promise;\n /** Randomize weights with default spread. */\n randomise(): void;\n /** Clear all training examples. */\n clearExamples(): void;\n /** Force a save to localStorage now (no debounce). */\n saveState(): void;\n /** Non-destructive loss query against current dataset. */\n evalLoss(): number | null;\n /** Batch inference: input is Nx2 array of [x,y] pairs. */\n inferBatch(points: ReadonlyArray): Float32Array;\n /** Per-layer weight statistics: layerCount * 4 floats (mean|w|, max|w|, dead%, sat%). */\n getLayerStats(): Float32Array;\n\n // Snapshot / undo / A/B\n pushSnapshot(tag: string): void;\n popSnapshot(): boolean;\n listSnapshots(): ReadonlyArray<{ id: string; tag: string; timestamp: number; noiseLevel: number }>;\n clearSnapshots(): void;\n captureA(): void;\n toggleAB(): 'A' | 'B';\n acceptB(): void;\n revertToA(): void;\n\n // Pins\n addRegionPin(pin: { x: number; y: number; width: number; height: number }): string;\n removeRegionPin(id: string): void;\n toggleParamPin(modeId: string, paramName: string, outputIndex: number): boolean;\n isParamPinned(modeId: string, paramName: string): boolean;\n\n // Auto-explore\n setAutoExplore(enabled: boolean): void;\n setAutoExploreInterval(ms: number): void;\n setAutoExploreIntensity(v: number): void;\n\n // Pressure\n setPressure(force: number, holdMs?: number): void;\n\n // Heatmap\n refreshHeatmap(force?: boolean): boolean;\n getHeatmapCells(): Float32Array;\n setHeatmapColorMode(mode: HeatmapColorMode): void;\n\n // Weight health\n getWeightHistogram(): number[];\n getWeightStatus(): 'dead' | 'saturating' | 'healthy';\n getGradientFlow(beforeWeights: Float32Array, afterWeights: Float32Array): { norms: number[]; status: ReturnType };\n\n // Overrides\n setOverride(modeId: string, paramName: string, override: Partial<{ min: number; max: number; curve: string; muted: boolean; pinned: boolean; frozen: boolean; fixedValue: number }>): void;\n clearOverride(modeId: string, paramName: string): void;\n clearAllOverrides(modeId: string): void;\n getOverride(modeId: string, paramName: string): unknown;\n\n // Compound axes\n setAxis(axis: 'boldness' | 'memory' | 'precision', value: number): void;\n applyControlPreset(id: string): boolean;\n\n // Pipelines\n setZoom(zoom: number): void;\n setSpread(spread: number): void;\n setOutputFreeze(frozen: boolean): void;\n\n // Session presets\n captureSessionPreset(withWeights?: boolean): SessionPresetPayload;\n restoreSessionPreset(payload: SessionPresetPayload): boolean;\n saveSessionPreset(name: string, withWeights?: boolean): void;\n loadSessionPreset(id: string): boolean;\n buildShareUrl(): string;\n applyUrlParams(search?: string): boolean;\n\n // Bus\n emit(topic: string, data: unknown): void;\n on(topic: string, handler: (data: unknown) => void): () => void;\n\n /** True once the WASM is fully initialised. */\n readonly __ready: boolean;\n /** Force initialisation. Returns a promise that resolves when the WASM is ready. */\n __init(): Promise;\n}\n\ndeclare global {\n interface Window {\n __nisps?: DebugProbe;\n }\n}\n\nconst EMPTY_F32 = new Float32Array(0);\n\nlet lazyInitPromise: Promise | null = null;\nfunction lazyInit(): Promise {\n if (mlStore.iml) return Promise.resolve();\n if (!lazyInitPromise) {\n lazyInitPromise = mlStore.initialize().then(() => undefined);\n }\n return lazyInitPromise;\n}\n\n// Probe-local heatmap sampler (independent of any active mode runtime).\nconst probeHeatmap = new HeatmapSampler({ resolution: 16 });\n\nconst probe: DebugProbe = {\n get __ready(): boolean {\n return !!mlStore.iml && mlStore.state.ready;\n },\n\n __init(): Promise {\n return lazyInit();\n },\n\n getOutputs(): Float32Array {\n return mlStore.outputs();\n },\n\n getParamOutputs(): Float32Array {\n // The probe doesn't have a runtime reference; recompute from raw outputs.\n const raw = mlStore.outputs();\n if (raw.length === 0) return EMPTY_F32;\n // Just return the raw outputs — actual override application requires\n // schema knowledge that the probe doesn't track. Tests can read\n // overrides via `getOverride` instead.\n return raw;\n },\n\n getLoss(): number | null {\n return mlStore.state.lastLoss;\n },\n\n getLossHistory(): ReadonlyArray {\n return mlStore.state.lossHistory;\n },\n\n getWeights(): Float32Array {\n return mlStore.getWeights();\n },\n\n getExampleCount(): number {\n return mlStore.state.exampleCount;\n },\n\n setInputs(x: number, y: number): void {\n if (!mlStore.iml) {\n void lazyInit();\n return;\n }\n untrack(() => mlStore.iml!.inferXY(x, y));\n },\n\n thumbsUp(): void {\n if (!mlStore.iml) return;\n untrack(() => {\n autoSnapshot('before thumbs-up (probe)');\n mlStore.iml!.train(explorationStore.state.learningRate);\n explorationStore.decayNoise(0.5);\n });\n },\n\n thumbsDown(): void {\n if (!mlStore.iml) return;\n untrack(() => {\n autoSnapshot('before thumbs-down (probe)');\n const cap = explorationStore.state.noiseCap;\n const spread = explorationStore.state.spread;\n mlStore.iml!.moveWeights(cap, spread);\n explorationStore.growNoise(0.5);\n });\n },\n\n train(): number {\n if (!mlStore.iml) {\n void lazyInit();\n return 0;\n }\n return untrack(() => {\n autoSnapshot('before train (probe)');\n return mlStore.iml!.train(explorationStore.state.learningRate);\n });\n },\n\n async trainAsync(): Promise {\n await lazyInit();\n if (!mlStore.iml) return 0;\n autoSnapshot('before trainAsync (probe)');\n return mlStore.iml.trainAsync(explorationStore.state.learningRate);\n },\n\n randomise(): void {\n if (!mlStore.iml) return;\n untrack(() => {\n autoSnapshot('before randomize (probe)');\n mlStore.iml!.randomiseWeights(explorationStore.state.spread);\n });\n },\n\n clearExamples(): void {\n mlStore.clearExamples();\n },\n\n saveState(): void {\n mlStore.saveNow();\n },\n\n evalLoss(): number | null {\n if (!mlStore.iml) return null;\n return mlStore.iml.evalLoss();\n },\n\n inferBatch(points: ReadonlyArray): Float32Array {\n if (!mlStore.iml) return new Float32Array(points.length * mlStore.state.outputSize);\n return mlStore.iml.inferBatch(points);\n },\n\n getLayerStats(): Float32Array {\n if (!mlStore.iml) return EMPTY_F32;\n return mlStore.iml.getLayerStatsFlat();\n },\n\n // ----- Snapshot / undo / A/B ---------------------------------------------\n\n pushSnapshot(tag: string): void {\n autoSnapshot(tag);\n },\n\n popSnapshot(): boolean {\n return undoLastSnapshot();\n },\n\n listSnapshots() {\n return sessionStore.listSnapshots().map((s) => ({\n id: s.id,\n tag: s.tag,\n timestamp: s.timestamp,\n noiseLevel: s.noiseLevel,\n }));\n },\n\n clearSnapshots(): void {\n sessionStore.clearSnapshots();\n },\n\n captureA(): void {\n snapCaptureA();\n },\n\n toggleAB(): 'A' | 'B' {\n return snapToggleAB();\n },\n\n acceptB(): void {\n snapAcceptB();\n },\n\n revertToA(): void {\n snapRevertToA();\n },\n\n // ----- Pins ---------------------------------------------------------------\n\n addRegionPin(pin) {\n const created = sessionStore.addRegionPin(pin);\n return created.id;\n },\n\n removeRegionPin(id: string): void {\n sessionStore.removeRegionPin(id);\n },\n\n toggleParamPin(modeId: string, paramName: string, outputIndex: number): boolean {\n return sessionStore.toggleParamPin(`${modeId}:${paramName}`, outputIndex);\n },\n\n isParamPinned(modeId: string, paramName: string): boolean {\n return sessionStore.isParamPinned(`${modeId}:${paramName}`);\n },\n\n // ----- Auto-explore -------------------------------------------------------\n\n setAutoExplore(enabled: boolean): void {\n explorationStore.setAutoExplore(enabled);\n },\n\n setAutoExploreInterval(ms: number): void {\n explorationStore.setAutoExploreInterval(ms);\n },\n\n setAutoExploreIntensity(v: number): void {\n explorationStore.setAutoExploreIntensity(v);\n },\n\n setPressure(force: number, holdMs: number = 0): void {\n explorationStore.setPressure(force, holdMs);\n },\n\n // ----- Heatmap ------------------------------------------------------------\n\n refreshHeatmap(force = false): boolean {\n if (!mlStore.iml) return false;\n const cfg = inputStore.config;\n const z = cfg.zoom;\n const cx = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorX;\n const cy = cfg.anchorMode === 'center' ? 0.5 : cfg.anchorY;\n return probeHeatmap.update({ cx, cy, zoom: z }, force);\n },\n\n getHeatmapCells(): Float32Array {\n return probeHeatmap.getCells();\n },\n\n setHeatmapColorMode(mode: HeatmapColorMode): void {\n probeHeatmap.setColorMode(mode);\n },\n\n // ----- Weight health ------------------------------------------------------\n\n getWeightHistogram(): number[] {\n return weightHistogram(mlStore.weights());\n },\n\n getWeightStatus() {\n return healthStatus(mlStore.weights());\n },\n\n getGradientFlow(beforeWeights: Float32Array, afterWeights: Float32Array) {\n if (!mlStore.iml) return { norms: [], status: [] };\n const arch = mlStore.iml.architecture;\n const sizes = layerWeightCounts({\n inputSize: arch.inputSize,\n hidden: arch.hidden,\n outputSize: arch.outputSize,\n });\n const norms = layerNorms(beforeWeights, afterWeights, sizes);\n return { norms, status: gradientStatuses(norms) };\n },\n\n // ----- Overrides ----------------------------------------------------------\n\n setOverride(modeId: string, paramName: string, patch): void {\n const existing = modeStore.getOverride(modeId, paramName);\n const next = {\n min: existing?.min ?? 0,\n max: existing?.max ?? 1,\n curve: existing?.curve ?? 'linear',\n curveParam: existing?.curveParam,\n muted: existing?.muted ?? false,\n pinned: existing?.pinned ?? false,\n frozen: existing?.frozen ?? false,\n fixedValue: existing?.fixedValue ?? 0.5,\n ...patch,\n } as Parameters[2];\n modeStore.setOverride(modeId, paramName, next);\n },\n\n clearOverride(modeId: string, paramName: string): void {\n modeStore.clearOverride(modeId, paramName);\n },\n\n clearAllOverrides(modeId: string): void {\n modeStore.clearAllOverrides(modeId);\n },\n\n getOverride(modeId: string, paramName: string) {\n return modeStore.getOverride(modeId, paramName);\n },\n\n // ----- Compound axes ------------------------------------------------------\n\n setAxis(axis: 'boldness' | 'memory' | 'precision', value: number): void {\n controlStore.setAxis(axis, value);\n },\n\n applyControlPreset(id: string): boolean {\n return controlStore.applyPreset(id);\n },\n\n // ----- Pipelines ----------------------------------------------------------\n\n setZoom(zoom: number): void {\n inputStore.setZoom(zoom);\n },\n\n setSpread(spread: number): void {\n explorationStore.setSpread(spread);\n },\n\n setOutputFreeze(frozen: boolean): void {\n outputStore.setFreezeOutput(frozen);\n },\n\n // ----- Session presets ----------------------------------------------------\n\n captureSessionPreset(withWeights = false): SessionPresetPayload {\n return captureSessionPreset(withWeights);\n },\n\n restoreSessionPreset(payload: SessionPresetPayload): boolean {\n return batch(() => restoreSessionPreset(payload));\n },\n\n saveSessionPreset(name: string, withWeights = false): void {\n saveNamedPreset(name, withWeights);\n },\n\n loadSessionPreset(id: string): boolean {\n return batch(() => loadNamedPreset(id));\n },\n\n buildShareUrl(): string {\n return buildShareUrl();\n },\n\n applyUrlParams(search?: string): boolean {\n return batch(() => applyUrlParams(search));\n },\n\n // ----- Bus ----------------------------------------------------------------\n\n emit(topic: string, data: unknown): void {\n // Lossy cast — the probe is intentionally weakly typed.\n coreBus.emit(topic as never, data as never);\n },\n\n on(topic: string, handler): () => void {\n return coreBus.onPrefix(topic, handler);\n },\n};\n\n/**\n * Install the probe on window. Idempotent — the probe object is a\n * singleton, so capturing `window.__nisps` once is safe across hot\n * reloads and re-installs.\n */\nexport function installDebugProbe(): void {\n if (typeof window === 'undefined') return;\n window.__nisps = probe;\n}\n","/* @refresh reload */\nimport { render } from 'solid-js/web';\nimport App from './App';\nimport './styles/tokens.css';\nimport { installDebugProbe } from './debug/probe';\nimport { applyUrlParams } from './features/session-preset';\n\nconst root = document.getElementById('root');\nif (!root) {\n throw new Error('Root element #root not found');\n}\n\n// Install debug probe early. Playwright tests use it to drive the ML\n// engine programmatically.\ninstallDebugProbe();\n\n// Apply URL params (?session=..., ?boldness=..., etc) before mounting so\n// stores read the resolved values on first render.\ntry {\n applyUrlParams();\n} catch (err) {\n console.warn('[main] applyUrlParams failed:', err);\n}\n\nrender(() => , root);\n"],"file":"assets/index-C1-a2ZPT.js"} \ No newline at end of file diff --git a/playground/dist/assets/index-D19lnkK_.css b/playground/dist/assets/index-D19lnkK_.css deleted file mode 100644 index 8502003..0000000 --- a/playground/dist/assets/index-D19lnkK_.css +++ /dev/null @@ -1 +0,0 @@ -._wrap_1uogo_1{display:flex;flex-direction:column;gap:var(--sp-2);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-3);font-size:var(--fs-sm)}._row_1uogo_12{display:flex;gap:var(--sp-2)}._btn_1uogo_17{flex:1;height:48px;font-size:var(--fs-lg);border-radius:var(--r-1);font-weight:600}._thumbsUp_1uogo_25{background:#6bc26b29;border-color:#6bc26b66;color:var(--good)}._thumbsUp_1uogo_25:hover:not(:disabled){background:#6bc26b47}._thumbsDown_1uogo_34{background:#ef5b5b29;border-color:#ef5b5b66;color:var(--bad)}._thumbsDown_1uogo_34:hover:not(:disabled){background:#ef5b5b47}._undo_1uogo_43{background:var(--bg-2);color:var(--fg-mute)}._btnAlt_1uogo_48{flex:1;font-size:var(--fs-sm);text-transform:uppercase;letter-spacing:.06em}._status_1uogo_55{display:flex;gap:var(--sp-3);padding-top:var(--sp-2);border-top:1px solid var(--line);flex-wrap:wrap}._statusItem_1uogo_63{display:flex;flex-direction:column;gap:2px;font-size:var(--fs-xs)}._statusLabel_1uogo_70{color:var(--fg-dim);text-transform:uppercase;letter-spacing:.08em}._statusValue_1uogo_76{color:var(--fg);font-variant-numeric:tabular-nums}._busy_1uogo_81{color:var(--accent-2);align-self:center;font-style:italic}._axis_1gq0t_1{display:flex;flex-direction:column;gap:var(--sp-1);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-2) var(--sp-3);--axis-accent: var(--accent)}._head_1gq0t_12{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-sm)}._label_1gq0t_19{font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--fg);flex:1}._preset_1gq0t_27{color:var(--axis-accent);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.06em}._value_1gq0t_34{font-variant-numeric:tabular-nums;color:var(--fg-mute);font-size:var(--fs-xs);min-width:4ch;text-align:right}._input_1gq0t_42{appearance:none;-webkit-appearance:none;width:100%;height:24px;background:transparent;margin:0;cursor:pointer}._input_1gq0t_42::-webkit-slider-runnable-track{height:6px;border-radius:999px;background:var(--bg-3)}._input_1gq0t_42::-moz-range-track{height:6px;border-radius:999px;background:var(--bg-3)}._input_1gq0t_42::-webkit-slider-thumb{appearance:none;-webkit-appearance:none;width:18px;height:18px;border-radius:50%;background:var(--axis-accent);margin-top:-6px;box-shadow:0 0 10px var(--axis-accent);cursor:pointer}._input_1gq0t_42::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--axis-accent);border:none;box-shadow:0 0 10px var(--axis-accent)}._input_1gq0t_42:focus{outline:none}._endpoints_1gq0t_87{display:flex;justify-content:space-between;font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--fg-dim)}._disabled_1gq0t_96{opacity:.5;pointer-events:none}._group_1erfx_1{display:inline-flex;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-pill);padding:2px;gap:2px}._pill_1erfx_10{background:transparent;border:0;border-radius:var(--r-pill);padding:6px 14px;font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.08em;color:var(--fg-mute);transition:background var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)}._pill_1erfx_10:hover:not(:disabled):not(._active_1erfx_22){color:var(--fg)}._active_1erfx_22{background:var(--accent);color:var(--bg)}._disabled_1erfx_31{opacity:.5;pointer-events:none}._root_xdkqq_1{position:fixed;inset:0;z-index:var(--z-drawer);pointer-events:none}._scrim_xdkqq_8{position:absolute;inset:0;background:#00000073;pointer-events:auto;animation:_fadeIn_xdkqq_1 var(--dur-fast) var(--ease)}._panel_xdkqq_16{position:absolute;top:0;bottom:0;background:var(--bg-1);border-left:1px solid var(--line);pointer-events:auto;display:flex;flex-direction:column;box-shadow:0 0 24px #00000080;animation:_slideIn_xdkqq_1 var(--dur-med) var(--ease)}._left_xdkqq_29 ._panel_xdkqq_16{left:0;border-right:1px solid var(--line);border-left:0;animation-name:_slideInLeft_xdkqq_1}._right_xdkqq_36 ._panel_xdkqq_16{right:0}._header_xdkqq_40{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--line);flex:0 0 auto}._title_xdkqq_49{flex:1;font-size:var(--fs-md);text-transform:uppercase;letter-spacing:.06em;color:var(--fg-mute)}._close_xdkqq_57{background:transparent;border:0;font-size:22px;color:var(--fg-mute);width:32px;height:32px;border-radius:var(--r-1);padding:0}._close_xdkqq_57:hover{background:var(--bg-2);color:var(--fg)}._body_xdkqq_73{flex:1;overflow:auto;padding:var(--sp-3) var(--sp-4)}@keyframes _fadeIn_xdkqq_1{0%{opacity:0}to{opacity:1}}@keyframes _slideIn_xdkqq_1{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes _slideInLeft_xdkqq_1{0%{transform:translate(-100%)}to{transform:translate(0)}}._canvas_mlv8d_1{display:block;border:1px solid var(--line);border-radius:var(--r-2);background:var(--bg-1);touch-action:none;cursor:pointer}._wrap_lq3ei_1{display:flex;flex-direction:column;gap:var(--sp-1);font-size:var(--fs-sm);color:var(--fg);user-select:none}._label_lq3ei_10{color:var(--fg-mute);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.06em}._row_lq3ei_17{display:flex;gap:var(--sp-3);align-items:center}._input_lq3ei_23{flex:1;appearance:none;-webkit-appearance:none;background:transparent;height:24px;margin:0;cursor:pointer}._input_lq3ei_23::-webkit-slider-runnable-track{height:4px;border-radius:999px;background:linear-gradient(to right,var(--accent) 0%,var(--accent) calc(var(--sp, 0) * 100%),var(--bg-3) 0%);background-color:var(--bg-3)}._input_lq3ei_23::-moz-range-track{height:4px;border-radius:999px;background:var(--bg-3)}._input_lq3ei_23::-webkit-slider-thumb{appearance:none;-webkit-appearance:none;width:16px;height:16px;border-radius:50%;background:var(--accent);margin-top:-6px;box-shadow:0 0 8px #ff6a0066;cursor:pointer;transition:transform var(--dur-fast) var(--ease)}._input_lq3ei_23::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--accent);border:none;box-shadow:0 0 8px #ff6a0066}._input_lq3ei_23:hover::-webkit-slider-thumb{transform:scale(1.15)}._input_lq3ei_23:focus{outline:none}._input_lq3ei_23:focus::-webkit-slider-thumb{box-shadow:0 0 0 3px #ff6a004d}._value_lq3ei_80{font-variant-numeric:tabular-nums;font-size:var(--fs-xs);color:var(--fg-mute);min-width:4ch;text-align:right}._unit_lq3ei_88{color:var(--fg-dim);margin-left:2px}._disabled_lq3ei_93{opacity:.5;pointer-events:none}._editor_1yxvy_1{display:flex;flex-direction:column;gap:var(--sp-2);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-3)}._compact_1yxvy_11{padding:var(--sp-2);gap:var(--sp-1)}._header_1yxvy_16{display:flex;align-items:baseline;gap:var(--sp-2)}._name_1yxvy_22{flex:1;font-size:var(--fs-sm);color:var(--fg)}._id_1yxvy_28{color:var(--fg-dim);font-size:var(--fs-xs)}._toggles_1yxvy_33{display:flex;gap:var(--sp-2)}._fields_1yxvy_38{display:flex;flex-direction:column;gap:var(--sp-2)}._curveRow_1yxvy_44{display:flex;align-items:center;gap:var(--sp-3);font-size:var(--fs-xs)}._fieldLabel_1yxvy_51{color:var(--fg-mute);text-transform:uppercase;letter-spacing:.06em;flex:0 0 auto}._select_1yxvy_58{flex:1;background:var(--bg-2);color:var(--fg);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-1) var(--sp-2);font-size:var(--fs-sm)}._wrap_389vj_1{display:flex;flex-direction:column;align-items:stretch;gap:var(--sp-2);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);padding:var(--sp-2)}._canvas_389vj_12{display:block;background:var(--bg-2);border-radius:var(--r-1)}._label_389vj_18{align-self:center;padding:4px 12px;font-size:var(--fs-xs);letter-spacing:.1em;border-radius:var(--r-pill);background:var(--bg-2);color:var(--fg);font-weight:600;transition:box-shadow var(--dur-med) var(--ease)}._pulsing_389vj_30{animation:_pulse_389vj_1 1.6s var(--ease) infinite}@keyframes _pulse_389vj_1{0%,to{opacity:1}50%{opacity:.65}}._canvas_rdbrc_1{display:block;border:1px solid var(--line);border-radius:var(--r-2);background:var(--bg-1);image-rendering:pixelated}._canvas_1ra7r_1{display:block;background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2)}._wrap_14adf_1{display:flex;flex-direction:column;gap:var(--sp-2)}._section_14adf_7{border-top:1px solid var(--line);padding-top:var(--sp-2)}._section_14adf_7:first-of-type{border-top:none;padding-top:0}._sectionHeader_14adf_17{display:flex;align-items:center;gap:var(--sp-2);width:100%;background:transparent;border:none;padding:var(--sp-2) 0;font-size:var(--fs-sm);text-transform:uppercase;letter-spacing:.08em;color:var(--fg-mute);cursor:pointer;text-align:left;font-family:var(--font-mono)}._sectionHeader_14adf_17:hover{color:var(--fg)}._caret_14adf_38{color:var(--fg-dim);font-size:var(--fs-xs);width:12px;display:inline-block}._badge_14adf_45{margin-left:auto;font-size:var(--fs-xs);background:var(--bg-3);color:var(--fg-mute);padding:1px 6px;border-radius:var(--r-1);font-family:var(--font-mono)}._body_14adf_55{display:flex;flex-direction:column;gap:var(--sp-3);padding:var(--sp-2) 0 var(--sp-3)}._row_14adf_62{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}._fieldLabel_14adf_69{font-size:var(--fs-xs);color:var(--fg-mute);text-transform:uppercase;letter-spacing:.06em;min-width:90px}._checkbox_14adf_77{display:flex;align-items:center;gap:var(--sp-1);font-size:var(--fs-sm);color:var(--fg);cursor:pointer}._subhead_14adf_86{font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.08em;color:var(--fg-mute);margin:var(--sp-2) 0 var(--sp-1)}._muted_14adf_94{color:var(--fg-dim);font-size:var(--fs-xs);font-family:var(--font-mono)}._btnAlt_14adf_100{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-1) var(--sp-3);font-size:var(--fs-sm);color:var(--fg);cursor:pointer}._btnAlt_14adf_100:hover:not(:disabled){background:var(--bg-3)}._btnAlt_14adf_100:disabled{opacity:.5;cursor:not-allowed}._btnTiny_14adf_119{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:2px 8px;font-size:var(--fs-xs);color:var(--fg-mute);cursor:pointer}._btnTiny_14adf_119:hover{color:var(--fg);background:var(--bg-3)}._snapshotList_14adf_134,._presetList_14adf_135{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;max-height:220px;overflow-y:auto}._snapshotBtn_14adf_146{display:flex;flex-direction:column;width:100%;background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-1) var(--sp-2);text-align:left;cursor:pointer;color:var(--fg)}._snapshotBtn_14adf_146:hover{background:var(--bg-3)}._snapshotTag_14adf_163{font-size:var(--fs-sm);font-family:var(--font-mono)}._snapshotMeta_14adf_168{font-size:var(--fs-xs);color:var(--fg-mute);font-family:var(--font-mono)}._presetRow_14adf_174{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-1);border-bottom:1px dashed var(--line)}._presetRow_14adf_174>span{flex:1;font-size:var(--fs-sm)}._paramList_14adf_187{display:flex;flex-direction:column;gap:var(--sp-2);max-height:360px;overflow-y:auto}._layerStats_14adf_195{display:flex;flex-direction:column;gap:var(--sp-1);font-family:var(--font-mono);font-size:var(--fs-xs)}._layerStatRow_14adf_203{display:flex;align-items:center;gap:var(--sp-2);padding:2px var(--sp-2);background:var(--bg-2);border-radius:var(--r-1)}._layerStatLabel_14adf_212{color:var(--accent);font-weight:600;min-width:24px}._layerStatValue_14adf_218{flex:1;color:var(--fg-mute)}._layerStatStatus_14adf_223{font-size:10px;text-transform:uppercase;padding:1px 6px;border-radius:var(--r-1);background:var(--bg-3)}._layerStatStatus_14adf_223[data-status=dead]{color:#999}._layerStatStatus_14adf_223[data-status=saturating]{color:var(--warn)}._layerStatStatus_14adf_223[data-status=healthy]{color:var(--good)}._textInput_14adf_235{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);color:var(--fg);font-family:var(--font-mono);font-size:var(--fs-sm);padding:var(--sp-1) var(--sp-2);flex:1}._hr_14adf_246{border:none;border-top:1px dashed var(--line);margin:var(--sp-2) 0}._shell_jqlsx_1{display:grid;grid-template-rows:auto 1fr auto auto;gap:var(--sp-4);min-height:100%;padding:var(--sp-4);background:var(--bg);color:var(--fg)}._header_jqlsx_11{display:flex;align-items:center;gap:var(--sp-3);padding-bottom:var(--sp-3);border-bottom:1px solid var(--line)}._title_jqlsx_19{font-size:var(--fs-lg);font-weight:600;color:var(--accent);margin:0}._subtitle_jqlsx_26{font-size:var(--fs-sm);color:var(--fg-mute);margin:0}._audioToggle_jqlsx_32{margin-left:auto;display:flex;align-items:center;gap:var(--sp-2)}._audioBtn_jqlsx_39{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-2) var(--sp-3);font-size:var(--fs-sm);color:var(--fg);cursor:pointer}._audioBtn_jqlsx_39._on_jqlsx_49{border-color:var(--accent);color:var(--accent)}._audioBtn_jqlsx_39:hover{background:var(--bg-3)}._voiceSpaces_jqlsx_58{display:flex;align-items:center;gap:var(--sp-2)}._voiceSpacesLabel_jqlsx_64{font-size:var(--fs-xs);color:var(--fg-mute);text-transform:uppercase;letter-spacing:.08em}._body_jqlsx_71{display:grid;grid-template-columns:minmax(320px,1fr) minmax(280px,380px);gap:var(--sp-4);align-items:start}@media (max-width: 900px){._body_jqlsx_71{grid-template-columns:1fr}}._primaryArea_jqlsx_84{display:flex;flex-direction:column;align-items:center;gap:var(--sp-3);padding:var(--sp-4);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);min-height:280px}._outputArea_jqlsx_96{display:flex;flex-direction:column;gap:var(--sp-3);padding:var(--sp-3);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);min-height:200px}._controls_jqlsx_107{display:flex;gap:var(--sp-4);flex-wrap:wrap;align-items:stretch;padding:var(--sp-3);background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2)}._controlAxes_jqlsx_118{flex:1 1 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--sp-3)}._trainingPanel_jqlsx_125{flex:0 0 auto}._drawerToggleBar_jqlsx_129{display:flex;gap:var(--sp-2);justify-content:flex-end}._drawerToggleBtn_jqlsx_135{background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-2) var(--sp-3);cursor:pointer;font-size:var(--fs-sm);color:var(--fg-mute)}._drawerToggleBtn_jqlsx_135:hover{color:var(--fg);background:var(--bg-3)}._statusLine_jqlsx_150{font-size:var(--fs-xs);color:var(--fg-dim);font-family:var(--font-mono)}._frozen_jqlsx_156{color:var(--accent-2)}._notReady_jqlsx_160{color:var(--warn)}._pad_1w4bb_1{position:relative;background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2);touch-action:none;cursor:crosshair;outline:none;user-select:none;overflow:hidden}._pad_1w4bb_1:focus-visible{border-color:var(--accent);box-shadow:0 0 0 3px #ff6a004d}._dragging_1w4bb_18{cursor:crosshair}._disabled_1w4bb_22{opacity:.4;pointer-events:none}._grid_1w4bb_27{position:absolute;inset:0;pointer-events:none;opacity:.5}._gridH_1w4bb_34,._gridV_1w4bb_34{position:absolute;background:var(--line-strong)}._gridH_1w4bb_34{left:0;right:0;top:50%;height:1px}._gridV_1w4bb_34{top:0;bottom:0;left:50%;width:1px}._dot_1w4bb_53{position:absolute;width:14px;height:14px;border-radius:50%;background:var(--accent-2);box-shadow:0 0 10px #00ccff80;pointer-events:none;top:0;left:0}._canvas_136xq_1{display:block;background:var(--bg-1);border:1px solid var(--line);border-radius:var(--r-2)}._compact_136xq_8{border-radius:var(--r-1)}._container_wvoct_1{position:relative;background:var(--bg-1);border-radius:50%;border:1px solid var(--line);touch-action:none;cursor:grab;outline:none;user-select:none;overflow:hidden}._container_wvoct_1:focus-visible{border-color:var(--accent);box-shadow:0 0 0 3px #ff6a004d}._dragging_wvoct_18{cursor:grabbing}._disabled_wvoct_22{opacity:.4;pointer-events:none}._boundary_wvoct_27{position:absolute;inset:6%;border-radius:50%;border:1px dashed var(--line-strong);pointer-events:none}._crosshair_wvoct_35{position:absolute;inset:0;pointer-events:none;opacity:.4}._hLine_wvoct_42,._vLine_wvoct_42{position:absolute;background:var(--line-strong)}._hLine_wvoct_42{left:0;right:0;top:50%;height:1px}._vLine_wvoct_42{top:0;bottom:0;left:50%;width:1px}._knob_wvoct_61{position:absolute;width:24px;height:24px;border-radius:50%;background:var(--accent);box-shadow:0 0 12px #ff6a0080;pointer-events:none;top:0;left:0}._bar_m3f4r_1{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--sp-4);background:var(--bg-1);border-bottom:1px solid var(--line)}._label_m3f4r_10{font-size:var(--fs-xs);color:var(--fg-mute);text-transform:uppercase;letter-spacing:.08em}._select_m3f4r_17{background:var(--bg-2);color:var(--fg);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-1) var(--sp-3);font-family:var(--font-mono);font-size:var(--fs-sm);cursor:pointer}._select_m3f4r_17:hover{border-color:var(--line-strong)}._select_m3f4r_17:focus{outline:1px solid var(--accent)}._description_m3f4r_36{font-size:var(--fs-xs);color:var(--fg-dim)}._placeholder_m3f4r_41{color:var(--warn)}._app_vixlr_1{display:flex;flex-direction:column;min-height:100%}._header_vixlr_7{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--line);background:var(--bg-1)}._dim_vixlr_16{color:var(--fg-mute);font-size:var(--fs-sm)}._nav_vixlr_21{margin-left:auto;display:flex;gap:var(--sp-2)}._nav_vixlr_21 button{padding:var(--sp-1) var(--sp-3);font-size:var(--fs-sm)}._active_vixlr_32{border-color:var(--accent)!important;color:var(--accent)}._main_vixlr_37{flex:1;padding:var(--sp-5);overflow:auto}._home_vixlr_43{max-width:720px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-4)}._title_vixlr_51{font-size:var(--fs-xl);margin:0;color:var(--accent)}._tagline_vixlr_57{color:var(--fg-mute);margin:0}._linkList_vixlr_62{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:var(--sp-2)}._note_vixlr_71{color:var(--fg-dim);font-size:var(--fs-sm);margin-top:var(--sp-4)}._notFound_vixlr_77{max-width:540px;margin:var(--sp-6) auto}._notFound_vixlr_77 h1{color:var(--accent)}:root{--bg: #0d0d0d;--bg-1: #141414;--bg-2: #1c1c1c;--bg-3: #242424;--fg: #e8e8e8;--fg-mute: #9a9a9a;--fg-dim: #5a5a5a;--line: #2a2a2a;--line-strong: #3a3a3a;--accent: #ff6a00;--accent-2: #00ccff;--accent-3: #ffa860;--good: #6bc26b;--warn: #f5c45e;--bad: #ef5b5b;--info: #5b9eef;--pin-1: rgba(255, 106, 0, .25);--pin-2: rgba(0, 204, 255, .25);--pin-3: rgba(180, 100, 255, .25);--pin-4: rgba(80, 200, 120, .25);--pin-5: rgba(255, 200, 80, .25);--font-mono: "JetBrains Mono", "Fira Code", "SF Mono", Menlo, Consolas, monospace;--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;--fs-xs: 11px;--fs-sm: 13px;--fs-md: 15px;--fs-lg: 18px;--fs-xl: 24px;--sp-0: 2px;--sp-1: 4px;--sp-2: 8px;--sp-3: 12px;--sp-4: 16px;--sp-5: 24px;--sp-6: 32px;--r-1: 4px;--r-2: 8px;--r-3: 14px;--r-pill: 999px;--ease: cubic-bezier(.25, .8, .35, 1);--dur-fast: .12s;--dur-med: .22s;--dur-slow: .36s;--z-bg: 0;--z-content: 10;--z-overlay: 100;--z-drawer: 200;--z-modal: 1000}*{box-sizing:border-box}html,body,#root{margin:0;padding:0;height:100%;background:var(--bg);color:var(--fg);font-family:var(--font-mono);font-size:var(--fs-md);-webkit-tap-highlight-color:transparent}button{font-family:inherit;font-size:inherit;background:var(--bg-2);color:var(--fg);border:1px solid var(--line);border-radius:var(--r-1);padding:var(--sp-2) var(--sp-3);cursor:pointer;transition:background var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)}button:hover:not(:disabled){background:var(--bg-3);border-color:var(--line-strong)}button:disabled{color:var(--fg-dim);cursor:not-allowed}input,select{font-family:inherit;font-size:inherit;color:var(--fg)}a{color:var(--accent-2);text-decoration:none}a:hover{text-decoration:underline}::selection{background:var(--accent);color:var(--bg)} diff --git a/playground/dist/assets/nisps-processor-BuKuNOY1.js b/playground/dist/assets/nisps-processor-BuKuNOY1.js deleted file mode 100644 index 1c2f3b2..0000000 --- a/playground/dist/assets/nisps-processor-BuKuNOY1.js +++ /dev/null @@ -1,2 +0,0 @@ -(function(){"use strict";class f extends AudioWorkletProcessor{instance=null;engineHandle=0;engineId="thru";muted=!0;inLPtr=0;inRPtr=0;outLPtr=0;outRPtr=0;idPtr=0;paramsPtr=0;inLView=null;inRView=null;outLView=null;outRView=null;paramsView=null;idView=null;mem=null;pendingParams=null;constructor(){super(),this.port.onmessage=i=>this.onMessage_(i.data)}async onMessage_(i){if(i.kind==="init")try{await this.init_(i.wasmBinary,i.sampleRate),this.post_({kind:"ready"})}catch(r){this.post_({kind:"error",message:r instanceof Error?r.message:String(r)})}else i.kind==="engine"?this.switchEngine_(i.engineId):i.kind==="params"?this.applyParams_(i.params):i.kind==="mute"&&(this.muted=i.muted)}post_(i){this.port.postMessage(i)}async init_(i,r){const t=new WebAssembly.Memory({initial:128,maximum:4096,shared:!1}),a=await WebAssembly.compile(i),h=WebAssembly.Module.imports(a),n={};for(const e of h){n[e.module]||(n[e.module]={});const s=n[e.module];e.kind==="function"?(e.name,s[e.name]=(...o)=>0):e.kind==="memory"?s[e.name]=t:e.kind==="table"?s[e.name]=new WebAssembly.Table({element:"anyfunc",initial:0}):e.kind==="global"&&(s[e.name]=new WebAssembly.Global({value:"i32",mutable:!0},0))}for(const e of h){const s=n[e.module];e.name==="a"&&e.kind==="function"&&(s[e.name]=()=>{throw new Error("wasm aborted")}),e.name==="b"&&e.kind==="function"&&(s[e.name]=o=>0)}const w=await WebAssembly.instantiate(a,n),_=WebAssembly.Module.exports(a),P=new Map;for(const e of _)P.set(e.name,e.name);const p=w.exports;function u(...e){for(const s of e){const o=p[s];if(typeof o=="function")return o}throw new Error(`worklet: missing wasm export, tried: ${e.join(", ")}`)}function m(...e){return u(...e)}const l=u("_malloc","malloc"),g=m("_free","free"),y=u("_nisps_engine_create"),R=m("_nisps_engine_destroy"),b=m("_nisps_engine_set_params"),L=m("_nisps_engine_process_block");let d=null;for(const e of _)if(e.kind==="memory"){const s=p[e.name];if(s instanceof WebAssembly.Memory){d=s;break}}this.mem=d??t,this.instance={exports:{memory:this.mem,malloc:l,free:g,_nisps_engine_create:(e,s)=>y(e,s),_nisps_engine_destroy:e=>R(e),_nisps_engine_set_params:(e,s,o)=>b(e,s,o),_nisps_engine_process_block:(e,s,o,V,A,C)=>L(e,s,o,V,A,C)}},this.inLPtr=l(128*4),this.inRPtr=l(128*4),this.outLPtr=l(128*4),this.outRPtr=l(128*4),this.paramsPtr=l(256*4),this.idPtr=l(32);const c=this.mem.buffer;this.inLView=new Float32Array(c,this.inLPtr,128),this.inRView=new Float32Array(c,this.inRPtr,128),this.outLView=new Float32Array(c,this.outLPtr,128),this.outRView=new Float32Array(c,this.outRPtr,128),this.paramsView=new Float32Array(c,this.paramsPtr,256),this.idView=new Uint8Array(c,this.idPtr,32),this.spawnEngine_("thru",r),this.pendingParams&&(this.applyParams_(this.pendingParams),this.pendingParams=null),this.muted=!1}spawnEngine_(i,r){if(!this.instance||!this.idView)return;this.engineHandle&&(this.instance.exports._nisps_engine_destroy(this.engineHandle),this.engineHandle=0);const a=new TextEncoder().encode(i);this.idView.fill(0),this.idView.set(a.subarray(0,Math.min(a.length,31))),this.engineHandle=this.instance.exports._nisps_engine_create(this.idPtr,r),this.engineId=i}switchEngine_(i){this.spawnEngine_(i,sampleRate)}applyParams_(i){if(!this.instance||!this.paramsView){this.pendingParams=i;return}const r=Math.min(i.length,256);for(let t=0;t1?t[1]:t[0];if(this.muted||!this.instance||!this.engineHandle||!this.inLView||!this.outLView||!this.outRView||!this.inRView)return a.fill(0),t.length>1&&h.fill(0),!0;const n=i[0];return n&&n[0]?this.inLView.set(n[0].subarray(0,128)):this.inLView.fill(0),n&&n[1]?this.inRView.set(n[1].subarray(0,128)):n&&n[0]?this.inRView.set(n[0].subarray(0,128)):this.inRView.fill(0),this.instance.exports._nisps_engine_process_block(this.engineHandle,this.inLPtr,this.inRPtr,this.outLPtr,this.outRPtr,128),a.set(this.outLView.subarray(0,a.length)),t.length>1&&h.set(this.outRView.subarray(0,h.length)),!0}}registerProcessor("nisps-processor",f)})(); -//# sourceMappingURL=nisps-processor-BuKuNOY1.js.map diff --git a/playground/dist/assets/nisps-processor-BuKuNOY1.js.map b/playground/dist/assets/nisps-processor-BuKuNOY1.js.map deleted file mode 100644 index 86840d1..0000000 --- a/playground/dist/assets/nisps-processor-BuKuNOY1.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"nisps-processor-BuKuNOY1.js","sources":["../src/audio/worklet/nisps-processor.ts"],"sourcesContent":["/**\n * AudioWorkletProcessor that runs `nisps.wasm` engines.\n *\n * Why a separate WASM instance from the main thread? AudioWorklet runs in\n * its own thread + global scope; reusing a single instance would require\n * SharedArrayBuffer + locking on the heap. Architecture.md §6.4 specifies\n * separate instances connected by `port` messages instead.\n *\n * Wasm load path: AudioWorklet has NO `fetch` and NO ESM `import`. The\n * main thread fetches `nisps.wasm` once and posts the bytes here as an\n * ArrayBuffer; we then `WebAssembly.compile` and `instantiate` directly,\n * skipping the Emscripten glue entirely. This is fine because the\n * exported functions don't need any of the JS-side runtime.\n *\n * Block size: AudioWorklet ALWAYS calls process() with 128-sample blocks.\n * We allocate 128-sample input and output buffers in the WASM linear\n * memory and shuttle samples in/out per call.\n */\n\n/// \n\nimport type { EngineId } from '../../ml/types';\nimport type { HostToWorkletMessage, WorkletToHostMessage } from '../engine-host';\n\nconst PROC_BLOCK = 128;\nconst MAX_PARAMS = 256; // upper bound across all engines\n\ninterface WasmInstance {\n exports: {\n memory: WebAssembly.Memory;\n malloc: (n: number) => number;\n free: (p: number) => void;\n _nisps_engine_create: (id_ptr: number, sample_rate: number) => number;\n _nisps_engine_destroy: (engine: number) => void;\n _nisps_engine_set_params: (engine: number, params_ptr: number, n: number) => void;\n _nisps_engine_process_block: (\n engine: number,\n in_l: number, in_r: number,\n out_l: number, out_r: number,\n n_samples: number,\n ) => void;\n };\n}\n\nclass NispsProcessor extends AudioWorkletProcessor {\n private instance: WasmInstance | null = null;\n private engineHandle = 0;\n private engineId: EngineId = 'thru';\n private muted = true;\n\n // Pointers + buffer views (allocated once instance is up).\n private inLPtr = 0;\n private inRPtr = 0;\n private outLPtr = 0;\n private outRPtr = 0;\n private idPtr = 0;\n private paramsPtr = 0;\n private inLView: Float32Array | null = null;\n private inRView: Float32Array | null = null;\n private outLView: Float32Array | null = null;\n private outRView: Float32Array | null = null;\n private paramsView: Float32Array | null = null;\n private idView: Uint8Array | null = null;\n private mem: WebAssembly.Memory | null = null;\n\n // Pending params posted before the engine was ready.\n private pendingParams: Float32Array | null = null;\n\n constructor() {\n super();\n this.port.onmessage = (ev) => this.onMessage_(ev.data as HostToWorkletMessage);\n }\n\n private async onMessage_(msg: HostToWorkletMessage): Promise {\n if (msg.kind === 'init') {\n try {\n await this.init_(msg.wasmBinary, msg.sampleRate);\n this.post_({ kind: 'ready' });\n } catch (err) {\n this.post_({\n kind: 'error',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n } else if (msg.kind === 'engine') {\n this.switchEngine_(msg.engineId);\n } else if (msg.kind === 'params') {\n this.applyParams_(msg.params);\n } else if (msg.kind === 'mute') {\n this.muted = msg.muted;\n }\n }\n\n private post_(msg: WorkletToHostMessage): void {\n this.port.postMessage(msg);\n }\n\n /**\n * Compile + instantiate the wasm module. We provide minimal imports —\n * the Emscripten module needs `__abort_js` and `_emscripten_resize_heap`\n * (we keep memory non-resizing so the latter is a stub).\n */\n private async init_(bytes: ArrayBuffer, sampleRate: number): Promise {\n const memory = new WebAssembly.Memory({ initial: 128, maximum: 4096, shared: false });\n const imports: WebAssembly.Imports = {\n // Emscripten import \"a\" group; field names match the generated JS.\n a: {\n a: () => { throw new Error('wasm aborted'); },\n b: () => false, // _emscripten_resize_heap returning 0 disables growth\n },\n };\n\n const compiled = await WebAssembly.compile(bytes);\n // Discover the actual import shape from the module — names like \"a\",\n // \"b\" depend on emcc's mangling; we accept whatever it produces.\n const importDesc = WebAssembly.Module.imports(compiled);\n const reshaped: WebAssembly.Imports = {};\n for (const desc of importDesc) {\n if (!reshaped[desc.module]) reshaped[desc.module] = {} as WebAssembly.ModuleImports;\n const mod = reshaped[desc.module] as WebAssembly.ModuleImports;\n if (desc.kind === 'function') {\n if (desc.name === 'c') {\n // unused\n }\n mod[desc.name] = (() => {\n // Generic stub: log + return 0.\n return (..._args: unknown[]) => 0;\n })();\n } else if (desc.kind === 'memory') {\n mod[desc.name] = memory;\n } else if (desc.kind === 'table') {\n mod[desc.name] = new WebAssembly.Table({ element: 'anyfunc', initial: 0 });\n } else if (desc.kind === 'global') {\n mod[desc.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0);\n }\n }\n // For known-needed Emscripten imports, supply real implementations.\n for (const desc of importDesc) {\n const mod = reshaped[desc.module] as WebAssembly.ModuleImports;\n // __abort_js\n if (desc.name === 'a' && desc.kind === 'function') {\n mod[desc.name] = () => { throw new Error('wasm aborted'); };\n }\n // _emscripten_resize_heap\n if (desc.name === 'b' && desc.kind === 'function') {\n mod[desc.name] = (_size: number) => 0; // refuse growth in worklet\n }\n }\n\n void imports; // silence unused\n const wasmInst = await WebAssembly.instantiate(compiled, reshaped);\n\n // Many Emscripten exports use single-letter mangled names. Discover\n // by reading the export descriptors.\n const exDesc = WebAssembly.Module.exports(compiled);\n const exMap = new Map(); // logical name → mangled\n for (const e of exDesc) {\n // The exports list includes both the original (with leading\n // underscore for C funcs) and the mangled single-letter alias used\n // in the import section. We only see the export side here, but\n // Emscripten in modern versions also re-exports the C names with\n // their leading-underscore form. Walk both.\n exMap.set(e.name, e.name);\n }\n const exports = wasmInst.exports as Record;\n\n function pickFn(...names: string[]): (...args: number[]) => number {\n for (const n of names) {\n const v = exports[n];\n if (typeof v === 'function') return v as unknown as (...a: number[]) => number;\n }\n throw new Error(`worklet: missing wasm export, tried: ${names.join(', ')}`);\n }\n function pickFnVoid(...names: string[]): (...args: number[]) => void {\n return pickFn(...names) as unknown as (...args: number[]) => void;\n }\n\n // The exports we need.\n const malloc = pickFn('_malloc', 'malloc');\n const free = pickFnVoid('_free', 'free');\n const ec = pickFn('_nisps_engine_create');\n const ed = pickFnVoid('_nisps_engine_destroy');\n const esp = pickFnVoid('_nisps_engine_set_params');\n const epb = pickFnVoid('_nisps_engine_process_block');\n\n // The wasm-exported memory might be named `memory` or another mangled\n // alias. Find it.\n let wasmMemory: WebAssembly.Memory | null = null;\n for (const e of exDesc) {\n if (e.kind === 'memory') {\n const v = exports[e.name];\n if (v instanceof WebAssembly.Memory) { wasmMemory = v; break; }\n }\n }\n // If the module imports memory (which our build does — we passed it),\n // there will be no exported memory; use the imported one.\n this.mem = wasmMemory ?? memory;\n\n this.instance = {\n exports: {\n memory: this.mem,\n malloc,\n free,\n _nisps_engine_create: (id, sr) => ec(id, sr),\n _nisps_engine_destroy: (h) => ed(h),\n _nisps_engine_set_params: (h, p, n) => esp(h, p, n),\n _nisps_engine_process_block: (h, il, ir, ol, or_, n) => epb(h, il, ir, ol, or_, n),\n },\n };\n\n // Allocate buffers.\n this.inLPtr = malloc(PROC_BLOCK * 4);\n this.inRPtr = malloc(PROC_BLOCK * 4);\n this.outLPtr = malloc(PROC_BLOCK * 4);\n this.outRPtr = malloc(PROC_BLOCK * 4);\n this.paramsPtr = malloc(MAX_PARAMS * 4);\n // Engine ids are short ASCII; 32 bytes covers everything we have.\n this.idPtr = malloc(32);\n\n const buf = this.mem.buffer;\n this.inLView = new Float32Array(buf, this.inLPtr, PROC_BLOCK);\n this.inRView = new Float32Array(buf, this.inRPtr, PROC_BLOCK);\n this.outLView = new Float32Array(buf, this.outLPtr, PROC_BLOCK);\n this.outRView = new Float32Array(buf, this.outRPtr, PROC_BLOCK);\n this.paramsView = new Float32Array(buf, this.paramsPtr, MAX_PARAMS);\n this.idView = new Uint8Array(buf, this.idPtr, 32);\n\n // Default engine: thru.\n this.spawnEngine_('thru', sampleRate);\n\n // Apply pending params if any arrived before init completed.\n if (this.pendingParams) {\n this.applyParams_(this.pendingParams);\n this.pendingParams = null;\n }\n\n this.muted = false;\n }\n\n private spawnEngine_(id: EngineId, sampleRate: number): void {\n if (!this.instance || !this.idView) return;\n if (this.engineHandle) {\n this.instance.exports._nisps_engine_destroy(this.engineHandle);\n this.engineHandle = 0;\n }\n // Write engine_id as ASCII into idView, NUL-terminated.\n const enc = new TextEncoder();\n const bytes = enc.encode(id);\n this.idView.fill(0);\n this.idView.set(bytes.subarray(0, Math.min(bytes.length, 31)));\n this.engineHandle = this.instance.exports._nisps_engine_create(this.idPtr, sampleRate);\n this.engineId = id;\n }\n\n private switchEngine_(id: EngineId): void {\n // sampleRate global from AudioWorkletGlobalScope.\n this.spawnEngine_(id, sampleRate);\n }\n\n private applyParams_(params: Float32Array): void {\n if (!this.instance || !this.paramsView) {\n this.pendingParams = params;\n return;\n }\n const n = Math.min(params.length, MAX_PARAMS);\n for (let i = 0; i < n; ++i) this.paramsView[i] = params[i];\n if (this.engineHandle) {\n this.instance.exports._nisps_engine_set_params(this.engineHandle, this.paramsPtr, n);\n }\n }\n\n override process(inputs: Float32Array[][], outputs: Float32Array[][]): boolean {\n const out = outputs[0];\n if (!out || out.length === 0) return true;\n\n const outL = out[0];\n const outR = out.length > 1 ? out[1] : out[0];\n\n if (this.muted || !this.instance || !this.engineHandle ||\n !this.inLView || !this.outLView || !this.outRView || !this.inRView) {\n // Silence.\n outL.fill(0);\n if (out.length > 1) outR.fill(0);\n return true;\n }\n\n // Copy inputs into wasm buffers (zero-fill if missing).\n const inp = inputs[0];\n if (inp && inp[0]) this.inLView.set(inp[0].subarray(0, PROC_BLOCK));\n else this.inLView.fill(0);\n if (inp && inp[1]) this.inRView.set(inp[1].subarray(0, PROC_BLOCK));\n else if (inp && inp[0]) this.inRView.set(inp[0].subarray(0, PROC_BLOCK));\n else this.inRView.fill(0);\n\n this.instance.exports._nisps_engine_process_block(\n this.engineHandle,\n this.inLPtr, this.inRPtr,\n this.outLPtr, this.outRPtr,\n PROC_BLOCK,\n );\n\n outL.set(this.outLView.subarray(0, outL.length));\n if (out.length > 1) outR.set(this.outRView.subarray(0, outR.length));\n\n return true;\n }\n}\n\nregisterProcessor('nisps-processor', NispsProcessor);\n"],"names":["NispsProcessor","ev","msg","err","bytes","sampleRate","memory","compiled","importDesc","reshaped","desc","mod","_args","_size","wasmInst","exDesc","exMap","exports","pickFn","names","n","v","pickFnVoid","malloc","free","ec","ed","esp","epb","wasmMemory","id","sr","h","p","il","ir","ol","or_","buf","params","i","inputs","outputs","out","outL","outR","inp"],"mappings":"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"} \ No newline at end of file diff --git a/playground/dist/assets/wasm-iml-FZSgVMmn.js b/playground/dist/assets/wasm-iml-FZSgVMmn.js deleted file mode 100644 index 530dba0..0000000 --- a/playground/dist/assets/wasm-iml-FZSgVMmn.js +++ /dev/null @@ -1,2 +0,0 @@ -import{m as d,p,l as y}from"./index-C1-a2ZPT.js";class T{maxSize;inputSize_=0;outputSize_=0;features_=new Float32Array(0);labels_=new Float32Array(0);size_=0;constructor(t=100){if(t<=0)throw new Error("Dataset.maxSize must be > 0");this.maxSize=t}get size(){return this.size_}isEmpty(){return this.size_===0}add(t,e){if(this.size_===0&&(this.inputSize_=t.length,this.outputSize_=e.length,this.features_=new Float32Array(this.maxSize*this.inputSize_),this.labels_=new Float32Array(this.maxSize*this.outputSize_)),t.length!==this.inputSize_||e.length!==this.outputSize_)return!1;this.size_>=this.maxSize&&(this.features_.copyWithin(0,this.inputSize_),this.labels_.copyWithin(0,this.outputSize_),this.size_=this.maxSize-1);const s=this.size_*this.inputSize_,a=this.size_*this.outputSize_;for(let r=0;r=this.size_)throw new RangeError(`feature index ${t} out of bounds`);return this.features_.subarray(t*this.inputSize_,(t+1)*this.inputSize_)}label(t){if(t<0||t>=this.size_)throw new RangeError(`label index ${t} out of bounds`);return this.labels_.subarray(t*this.outputSize_,(t+1)*this.outputSize_)}featuresFlat(){return this.features_.subarray(0,this.size_*this.inputSize_)}labelsFlat(){return this.labels_.subarray(0,this.size_*this.outputSize_)}get inputSize(){return this.inputSize_}get outputSize(){return this.outputSize_}computeWeights(t="uniform",e={}){const s=this.size_;if(s===0)return new Float32Array(0);if(s===1)return new Float32Array([1]);const a=new Float32Array(s).fill(1);if(t==="global"||t==="combined"){const i=e.recencyBias??.6;if(i>0){const n=1-.3*i;for(let o=s-2;o>=0;--o)a[o]=a[o+1]*n}}if((t==="local"||t==="combined")&&e.queryInput){const i=e.queryInput,n=e.radius??.15,o=n*n,_=this.inputSize_;for(let u=0;u0&&(a[u]*=Math.pow(1-m,g))}}}let r=0;for(let i=0;i0)for(let i=0;ithis.onMessage_(t.data),this.worker.onerror=t=>{for(const{reject:e}of this.pending.values())e(t.message??"worker error");this.pending.clear()}}init_(){return new Promise((t,e)=>{const s=r=>{const i=r.data;i.kind==="ready"?(this.worker.removeEventListener("message",s),t()):i.kind==="error"&&(this.worker.removeEventListener("message",s),e(new Error(i.message)))};this.worker.addEventListener("message",s);const a=(Date.now()^Math.floor(Math.random()*4294967295))>>>0;this.worker.postMessage({kind:"init",seed:a})})}train(t){if(this.disposed)return Promise.reject(new Error("WasmTrainer disposed"));const e=this.nextId++;return new Promise((s,a)=>{this.pending.set(e,{resolve:s,reject:a});const r={kind:"train",requestId:e,weights:t.weights,features:t.features,labels:t.labels,sampleWeights:t.sampleWeights,lr:t.lr,maxIter:t.maxIter,minErr:t.minErr,inputSize:t.inputSize,outputSize:t.outputSize};this.worker.postMessage(r,[t.weights.buffer,t.features.buffer,t.labels.buffer,t.sampleWeights.buffer])})}dispose(){if(!this.disposed){this.disposed=!0;try{this.worker.postMessage({kind:"dispose"})}catch{}this.worker.terminate();for(const{reject:t}of this.pending.values())t(new Error("disposed"));this.pending.clear()}}onMessage_(t){if(t.kind==="result"){const e=this.pending.get(t.requestId);e&&(this.pending.delete(t.requestId),e.resolve({loss:t.loss,weights:t.weights,lossHistory:t.lossHistory}))}else if(t.kind==="error"){const e=this.pending.get(t.requestId);e&&(this.pending.delete(t.requestId),e.reject(new Error(t.message)))}}}function M(){return H.create()}const W=typeof window>"u"&&typeof self<"u"&&typeof self.importScripts<"u";if(W){let v=function(l,h,f,w){if(!s)throw new Error("worker module not loaded");n!==r&&(i&&s._free(i),i=s._malloc(r*4),n=r),l.length!==_&&(o&&s._free(o),o=s._malloc(l.length*4),_=l.length),h.length!==S&&(u&&s._free(u),u=s._malloc(h.length*4),S=h.length),f.length!==m&&(c&&s._free(c),c=f.length>0?s._malloc(f.length*4):0,m=f.length),new Float32Array(s.HEAPF32.buffer,i,r).set(w),new Float32Array(s.HEAPF32.buffer,o,l.length).set(l),new Float32Array(s.HEAPF32.buffer,u,h.length).set(h),c&&new Float32Array(s.HEAPF32.buffer,c,f.length).set(f)},t=function(l){if(!s)return{kind:"error",requestId:l.requestId,message:"worker not initialised"};try{v(l.features,l.labels,l.sampleWeights,l.weights),s._nisps_ml_set_weights(a,i),s._nisps_ml_clear_examples(a);const h=l.inputSize,f=l.outputSize,w=l.features.length/h;for(let B=0;B0?c:0,E=s._nisps_ml_train(a,l.lr,l.maxIter,l.minErr,A);s._nisps_ml_get_weights(a,i);const k=new Float32Array(s.HEAPF32.buffer,i,r),L=new Float32Array(k),x=new Float32Array([E]);return{kind:"result",requestId:l.requestId,loss:E,weights:L,lossHistory:x}}catch(h){return{kind:"error",requestId:l.requestId,message:h instanceof Error?h.message:String(h)}}},e=function(){s&&(a&&(s._nisps_ml_destroy(a),a=0),i&&(s._free(i),i=0),o&&(s._free(o),o=0),u&&(s._free(u),u=0),c&&(s._free(c),c=0),s=null)},s=null,a=0,r=0,i=0,n=0,o=0,_=0,u=0,S=0,c=0,m=0;async function g(l){const h=await import(new URL("/nisps.js",self.location.origin).toString());s=await(h.default??h.createNispsModule)({locateFile:w=>w.endsWith(".wasm")?new URL("/nisps.wasm",self.location.origin).toString():w}),a=s._nisps_ml_create(0,0,0,0,l>>>0),r=s._nisps_ml_weight_count(a)}self.addEventListener("message",async l=>{const h=l.data;if(h.kind==="init")try{await g(h.seed),self.postMessage({kind:"ready"})}catch(f){self.postMessage({kind:"error",requestId:0,message:f instanceof Error?f.message:String(f)})}else if(h.kind==="train"){const f=t(h);f.kind==="result"?self.postMessage(f,[f.weights.buffer,f.lossHistory.buffer]):self.postMessage(f)}else h.kind==="dispose"&&e()})}const C="/nisps.js",O="/nisps.wasm",U=2,j=126;let F=null;async function N(){if(F)return F;const t=await import(new URL(C,window.location.origin).toString()),e=t.default??t.createNispsModule;if(!e)throw new Error("[wasm-iml] nisps.js does not export a module factory");return F=e,e}class z{constructor(t,e){if(this.mod=t,this.count=e,this.ptr=t._malloc(e*4),!this.ptr)throw new Error(`malloc(${e*4}) failed`);this.view=new Float32Array(t.HEAPF32.buffer,this.ptr,e)}ptr;view;rebind(){Object.defineProperty(this,"view",{value:new Float32Array(this.mod.HEAPF32.buffer,this.ptr,this.count),writable:!1})}free(){this.mod._free(this.ptr)}}class ${constructor(t,e){if(this.mod=t,this.count=e,this.ptr=t._malloc(e),!this.ptr)throw new Error(`malloc(${e}) failed`);this.view=new Uint8Array(t.HEAPU8.buffer,this.ptr,e)}ptr;view;rebind(){Object.defineProperty(this,"view",{value:new Uint8Array(this.mod.HEAPU8.buffer,this.ptr,this.count),writable:!1})}free(){this.mod._free(this.ptr)}}class b{module;mlHandle=0;weightCount_=0;arch_={inputSize:U,hidden:[10,14,18],outputSize:j,numLayers:4};featuresBuf;labelsBuf;weightsBuf;statsBuf;batchInBuf;batchOutBuf;pinMaskBuf;describeBuf;describePtr=0;dataset;lastLoss_=null;trainer=null;storageKey;saveTimer=null;destroyed=!1;static MAX_BATCH=4096;constructor(t){this.dataset=new T(t.maxExamples??100),this.storageKey=t.storageKey??"nisps:wasm-iml"}static async create(t={}){const e=new b(t);return await e.init_(t),e}async init_(t){const e=await N();this.module=await e({locateFile:n=>n.endsWith(".wasm")?new URL(O,window.location.origin).toString():n}),this.describePtr=this.module._malloc(6*4),this.module._nisps_ml_describe(this.describePtr);const s=new Int32Array(this.module.HEAP32.buffer,this.describePtr,6);this.arch_={inputSize:s[0],hidden:[s[1],s[2],s[3]],outputSize:s[4],numLayers:s[5]};const a=t.inputSize??this.arch_.inputSize,r=t.outputSize??this.arch_.outputSize;(a!==this.arch_.inputSize||r!==this.arch_.outputSize)&&console.warn(`[wasm-iml] requested ${a}->${r} but WASM build is fixed at ${this.arch_.inputSize}->${this.arch_.outputSize}; extras are ignored.`);const i=(t.seed??Date.now()>>>0)>>>0;if(this.mlHandle=this.module._nisps_ml_create(this.arch_.inputSize,this.arch_.outputSize,0,0,i),!this.mlHandle)throw new Error("[wasm-iml] nisps_ml_create returned null");this.weightCount_=this.module._nisps_ml_weight_count(this.mlHandle),this.featuresBuf=new z(this.module,this.arch_.inputSize),this.labelsBuf=new z(this.module,this.arch_.outputSize),this.weightsBuf=new z(this.module,this.weightCount_),this.statsBuf=new z(this.module,this.arch_.numLayers*4),this.batchInBuf=new z(this.module,b.MAX_BATCH*this.arch_.inputSize),this.batchOutBuf=new z(this.module,b.MAX_BATCH*this.arch_.outputSize),this.pinMaskBuf=new $(this.module,this.arch_.outputSize),d.__setState(p(n=>{n.inputSize=this.arch_.inputSize,n.outputSize=this.arch_.outputSize,n.exampleCount=0,n.lastLoss=null,n.lossHistory=[],n.training=!1,n.ready=!0})),d.__setOutputs(new Float32Array(this.arch_.outputSize)),this.publishWeights_(),this.tryLoadFromStorage_()}dispose(){this.destroyed||(this.destroyed=!0,this.saveTimer!==null&&(clearTimeout(this.saveTimer),this.saveTimer=null),this.trainer&&(this.trainer.dispose(),this.trainer=null),this.module&&this.mlHandle&&(this.module._nisps_ml_destroy(this.mlHandle),this.mlHandle=0),this.featuresBuf&&this.featuresBuf.free(),this.labelsBuf&&this.labelsBuf.free(),this.weightsBuf&&this.weightsBuf.free(),this.statsBuf&&this.statsBuf.free(),this.batchInBuf&&this.batchInBuf.free(),this.batchOutBuf&&this.batchOutBuf.free(),this.pinMaskBuf&&this.pinMaskBuf.free(),this.describePtr&&this.module._free(this.describePtr),d.__setState(p(t=>{t.ready=!1})))}get architecture(){return this.arch_}get weightCount(){return this.weightCount_}get exampleCount(){return this.dataset.size}get lastLoss(){return this.lastLoss_}setInput(t,e){this.module._nisps_ml_set_input(this.mlHandle,t,e)}process(){this.module._nisps_ml_process(this.mlHandle);const t=this.module._nisps_ml_outputs(this.mlHandle),e=new Float32Array(this.module.HEAPF32.buffer,t,this.arch_.outputSize),s=new Float32Array(e);return d.__setOutputs(s),s}inferXY(t,e){return this.setInput(0,t),this.setInput(1,e),this.process()}inferBatch(t){const e=t.length,s=this.arch_.inputSize,a=this.arch_.outputSize,r=new Float32Array(e*a);let i=0;for(let n=0;n{a.exampleCount=this.dataset.size})),y.emit("ml.example_added",{count:this.dataset.size}),this.scheduleSave_(),!0):!1}copyExampleToWasm_(t,e){const s=this.featuresBuf.view,a=this.labelsBuf.view,r=this.arch_.inputSize,i=this.arch_.outputSize;for(let n=0;n{o.lastLoss=0})),0;let r=0,i=null;a&&a.length===this.dataset.size&&(i=new z(this.module,a.length),i.view.set(a),r=i.ptr),d.__setState(p(o=>{o.training=!0}));let n=0;try{n=this.module._nisps_ml_train(this.mlHandle,t,e,s,r)}finally{i&&i.free(),d.__setState(p(o=>{o.training=!1}))}return this.lastLoss_=n,d.__setState(p(o=>{o.lastLoss=n,o.lossHistory=[n]})),this.publishWeights_(),y.emit("ml.trained",{loss:n}),this.scheduleSave_(),n}async trainAsync(t=1,e=1e3,s=.001,a){if(this.dataset.isEmpty())return this.lastLoss_=0,0;this.trainer||(this.trainer=await M());const r=this.getWeights(),i=new Float32Array(this.dataset.featuresFlat()),n=new Float32Array(this.dataset.labelsFlat()),o=a?new Float32Array(a):new Float32Array(0);d.__setState(p(_=>{_.training=!0}));try{const _=await this.trainer.train({weights:r,features:i,labels:n,sampleWeights:o,lr:t,maxIter:e,minErr:s,inputSize:this.arch_.inputSize,outputSize:this.arch_.outputSize});return this.setWeights(_.weights),this.lastLoss_=_.loss,d.__setState(p(u=>{u.lastLoss=_.loss,u.lossHistory=Array.from(_.lossHistory)})),y.emit("ml.trained",{loss:_.loss}),this.scheduleSave_(),_.loss}finally{d.__setState(p(_=>{_.training=!1}))}}evalLoss(){return this.module._nisps_ml_eval_loss(this.mlHandle)}clearExamples(){this.dataset.clear(),this.module._nisps_ml_clear_examples(this.mlHandle),d.__setState(p(t=>{t.exampleCount=0})),y.emit("ml.examples_cleared",void 0),this.scheduleSave_()}randomiseWeights(t=.6){this.module._nisps_ml_draw_weights(this.mlHandle,t),this.publishWeights_(),y.emit("ml.delta_update",{reason:"randomize"}),this.scheduleSave_()}moveWeights(t,e,s){let a=0;if(s){const r=Math.min(s.length,this.arch_.outputSize);for(let i=0;i{t.exampleCount=0,t.lastLoss=null,t.lossHistory=[]})),this.publishWeights_(),y.emit("ml.examples_cleared",void 0),this.scheduleSave_()}scheduleSave_(){this.saveTimer!==null&&clearTimeout(this.saveTimer),this.saveTimer=window.setTimeout(()=>this.saveNow(),500)}saveNow(){if(!this.destroyed){this.saveTimer!==null&&(clearTimeout(this.saveTimer),this.saveTimer=null);try{const t=this.getWeights(),e={v:1,arch:this.arch_,weights:Array.from(t),features:Array.from(this.dataset.featuresFlat()),labels:Array.from(this.dataset.labelsFlat()),size:this.dataset.size,lastLoss:this.lastLoss_};localStorage.setItem(this.storageKey,JSON.stringify(e))}catch(t){console.warn("[wasm-iml] saveNow failed:",t)}}}tryLoadFromStorage_(){try{const t=localStorage.getItem(this.storageKey);if(!t)return;const e=JSON.parse(t);if(e.v!==1)return;const s=this.arch_.inputSize,a=this.arch_.outputSize;if(e.size>0&&e.features.length===e.size*s&&e.labels.length===e.size*a)for(let r=0;r{r.exampleCount=this.dataset.size,r.lastLoss=this.lastLoss_}))}catch(t){console.warn("[wasm-iml] tryLoadFromStorage failed:",t)}}publishWeights_(){const t=this.getWeights();d.__setWeights(t)}}export{b as WasmIML}; -//# sourceMappingURL=wasm-iml-FZSgVMmn.js.map diff --git a/playground/dist/assets/wasm-iml-FZSgVMmn.js.map b/playground/dist/assets/wasm-iml-FZSgVMmn.js.map deleted file mode 100644 index 42bf685..0000000 --- a/playground/dist/assets/wasm-iml-FZSgVMmn.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"wasm-iml-FZSgVMmn.js","sources":["../../src/ml/dataset.ts","../../src/ml/wasm-worker.ts","../../src/ml/wasm-iml.ts"],"sourcesContent":["/**\n * Dataset — JS-side training-example store.\n *\n * Why duplicate the C++ ring buffer? Two reasons:\n * 1. Sample-weight computation (recency / spatial / combined) lives in JS so\n * that adjusting weighting modes doesn't burn a WASM round-trip.\n * 2. The dataset is part of session state we serialize to localStorage —\n * the WASM heap is wiped on reload.\n *\n * On train() we ship features + labels into WASM via `addExample` calls. The\n * order of insertion is preserved; FIFO eviction matches the C++ MLP's\n * `dataset_head_` pointer so weighting stays consistent.\n *\n * The implementation is a faithful TypeScript port of the legacy\n * `playground/_archive/js/nisps/dataset.js` with:\n * - Float32Array backing instead of `Array>`\n * - Stricter types\n * - No `withBias` flag (the WASM bindings don't take a bias term)\n */\n\nexport type WeightMode = 'global' | 'local' | 'combined' | 'uniform';\n\nexport interface ComputeWeightsParams {\n /** [0,1] — how strongly to bias toward newest examples (global/combined). */\n recencyBias?: number;\n /** Current input position, used for local/combined spatial weighting. */\n queryInput?: ReadonlyArray;\n /** Spatial radius in input space (local/combined). */\n radius?: number;\n}\n\nexport class Dataset {\n /** Maximum number of examples retained. FIFO eviction beyond this. */\n readonly maxSize: number;\n /** Length of feature vectors. Set on first add(); locked thereafter. */\n private inputSize_ = 0;\n /** Length of label vectors. Set on first add(); locked thereafter. */\n private outputSize_ = 0;\n\n /** Flat arrays — entries `[i*inputSize, (i+1)*inputSize)` belong to example i. */\n private features_: Float32Array = new Float32Array(0);\n private labels_: Float32Array = new Float32Array(0);\n private size_ = 0;\n\n constructor(maxSize = 100) {\n if (maxSize <= 0) throw new Error('Dataset.maxSize must be > 0');\n this.maxSize = maxSize;\n }\n\n /** Number of examples currently stored. */\n get size(): number {\n return this.size_;\n }\n\n isEmpty(): boolean {\n return this.size_ === 0;\n }\n\n /**\n * Add a feature/label pair. Returns true on success, false if the\n * dimensions don't match a previously-added example.\n *\n * Eviction: when at capacity, the oldest example is removed (shift),\n * then the new one is appended. This matches the legacy JS behaviour\n * (and is conceptually equivalent to the C++ side's ring buffer with\n * `head_` advancement).\n */\n add(features: ReadonlyArray, labels: ReadonlyArray): boolean {\n if (this.size_ === 0) {\n this.inputSize_ = features.length;\n this.outputSize_ = labels.length;\n // Allocate full-capacity buffers up front to avoid growth thrash.\n this.features_ = new Float32Array(this.maxSize * this.inputSize_);\n this.labels_ = new Float32Array(this.maxSize * this.outputSize_);\n }\n\n if (features.length !== this.inputSize_ || labels.length !== this.outputSize_) {\n return false;\n }\n\n if (this.size_ >= this.maxSize) {\n // FIFO: shift left in place. This is O(n*dim) and could be replaced\n // with a head pointer; for maxSize ≤ a few hundred it's fine.\n this.features_.copyWithin(0, this.inputSize_);\n this.labels_.copyWithin(0, this.outputSize_);\n this.size_ = this.maxSize - 1;\n }\n\n const fOff = this.size_ * this.inputSize_;\n const lOff = this.size_ * this.outputSize_;\n for (let i = 0; i < this.inputSize_; ++i) this.features_[fOff + i] = features[i];\n for (let i = 0; i < this.outputSize_; ++i) this.labels_[lOff + i] = labels[i];\n this.size_++;\n return true;\n }\n\n clear(): void {\n this.size_ = 0;\n }\n\n /** Read-only view of the i-th feature vector. */\n feature(i: number): Float32Array {\n if (i < 0 || i >= this.size_) throw new RangeError(`feature index ${i} out of bounds`);\n return this.features_.subarray(i * this.inputSize_, (i + 1) * this.inputSize_);\n }\n\n /** Read-only view of the i-th label vector. */\n label(i: number): Float32Array {\n if (i < 0 || i >= this.size_) throw new RangeError(`label index ${i} out of bounds`);\n return this.labels_.subarray(i * this.outputSize_, (i + 1) * this.outputSize_);\n }\n\n /** Flat view of all features (size * inputSize). */\n featuresFlat(): Float32Array {\n return this.features_.subarray(0, this.size_ * this.inputSize_);\n }\n\n /** Flat view of all labels (size * outputSize). */\n labelsFlat(): Float32Array {\n return this.labels_.subarray(0, this.size_ * this.outputSize_);\n }\n\n get inputSize(): number {\n return this.inputSize_;\n }\n get outputSize(): number {\n return this.outputSize_;\n }\n\n /**\n * Compute per-sample training weights. Returns Float32Array (size=this.size)\n * normalized to sum to 1. For an empty dataset returns a 0-length array;\n * for a singleton, [1.0].\n *\n * Modes:\n * - `uniform` — every weight = 1/n.\n * - `global` — exponential recency decay over insertion order.\n * - `local` — within `radius` of `queryInput`, suppress older neighbours.\n * - `combined` — global × local.\n */\n computeWeights(mode: WeightMode = 'uniform', params: ComputeWeightsParams = {}): Float32Array {\n const n = this.size_;\n if (n === 0) return new Float32Array(0);\n if (n === 1) return new Float32Array([1.0]);\n\n const weights = new Float32Array(n).fill(1.0);\n\n if (mode === 'global' || mode === 'combined') {\n const bias = params.recencyBias ?? 0.6;\n if (bias > 0) {\n const decay = 1 - 0.3 * bias;\n for (let i = n - 2; i >= 0; --i) weights[i] = weights[i + 1] * decay;\n }\n }\n\n if ((mode === 'local' || mode === 'combined') && params.queryInput) {\n const query = params.queryInput;\n const radius = params.radius ?? 0.15;\n const radiusSq = radius * radius;\n const dim = this.inputSize_;\n\n for (let i = 0; i < n; ++i) {\n const fOffI = i * dim;\n let distSq = 0;\n for (let d = 0; d < dim; ++d) {\n const diff = this.features_[fOffI + d] - (query[d] ?? 0);\n distSq += diff * diff;\n }\n if (distSq < radiusSq) {\n const proximity = 1 - Math.sqrt(distSq) / radius;\n let newerNearby = 0;\n for (let j = i + 1; j < n; ++j) {\n const fOffJ = j * dim;\n let djSq = 0;\n for (let d = 0; d < dim; ++d) {\n const diff = this.features_[fOffI + d] - this.features_[fOffJ + d];\n djSq += diff * diff;\n }\n if (djSq < radiusSq) ++newerNearby;\n }\n if (newerNearby > 0) {\n weights[i] *= Math.pow(1 - proximity, newerNearby);\n }\n }\n }\n }\n\n let sum = 0;\n for (let i = 0; i < n; ++i) sum += weights[i];\n if (sum > 0) for (let i = 0; i < n; ++i) weights[i] /= sum;\n return weights;\n }\n}\n","/**\n * Disposable Web Worker that runs SGD off the main thread.\n *\n * The worker holds its own `nisps.wasm` instance (architecture.md §6.4).\n * The main thread sends:\n * - current weights (so the worker is in the same state as the UI),\n * - dataset (features + labels),\n * - SGD hyperparameters,\n * and receives updated weights + final loss.\n *\n * This module exposes:\n * - `createTrainer()` - factory that spawns the worker, loads its WASM,\n * and returns a `WasmTrainer` handle.\n * - The worker entry-point itself (when this file runs in a Worker).\n *\n * The worker is implemented inline so a single TS file becomes both the\n * main-thread API and the worker bundle. Vite's `new Worker(new URL(...,\n * import.meta.url))` pattern packs it correctly.\n *\n * Lifecycle: each `WasmTrainer` is disposable via `.dispose()` which\n * terminates the worker. Tests should always dispose to avoid leaks.\n */\n\nimport type { NispsModule, NispsModuleFactory, WorkerRequest, WorkerResponse } from './types';\n\n// ---------------------------------------------------------------------------\n// Main-thread side\n// ---------------------------------------------------------------------------\n\nexport interface TrainArgs {\n weights: Float32Array;\n features: Float32Array;\n labels: Float32Array;\n /** Optional; pass empty for uniform weighting. */\n sampleWeights: Float32Array;\n lr: number;\n maxIter: number;\n minErr: number;\n inputSize: number;\n outputSize: number;\n}\n\nexport interface TrainResult {\n loss: number;\n weights: Float32Array;\n lossHistory: Float32Array;\n}\n\nexport class WasmTrainer {\n private worker: Worker;\n private nextId = 1;\n private pending = new Map void; reject: (e: unknown) => void }>();\n private disposed = false;\n\n static async create(): Promise {\n const trainer = new WasmTrainer();\n await trainer.init_();\n return trainer;\n }\n\n private constructor() {\n this.worker = new Worker(new URL('./wasm-worker.ts', import.meta.url), { type: 'module' });\n this.worker.onmessage = (ev) => this.onMessage_(ev.data as WorkerResponse);\n this.worker.onerror = (ev) => {\n // Fail any pending requests.\n for (const { reject } of this.pending.values()) reject(ev.message ?? 'worker error');\n this.pending.clear();\n };\n }\n\n private init_(): Promise {\n return new Promise((resolve, reject) => {\n const handler = (ev: MessageEvent) => {\n const msg = ev.data as WorkerResponse;\n if (msg.kind === 'ready') {\n this.worker.removeEventListener('message', handler);\n resolve();\n } else if (msg.kind === 'error') {\n this.worker.removeEventListener('message', handler);\n reject(new Error(msg.message));\n }\n };\n this.worker.addEventListener('message', handler);\n const seed = (Date.now() ^ Math.floor(Math.random() * 0xffffffff)) >>> 0;\n this.worker.postMessage({ kind: 'init', seed } satisfies WorkerRequest);\n });\n }\n\n train(args: TrainArgs): Promise {\n if (this.disposed) return Promise.reject(new Error('WasmTrainer disposed'));\n const requestId = this.nextId++;\n return new Promise((resolve, reject) => {\n this.pending.set(requestId, { resolve, reject });\n const msg: WorkerRequest = {\n kind: 'train',\n requestId,\n weights: args.weights,\n features: args.features,\n labels: args.labels,\n sampleWeights: args.sampleWeights,\n lr: args.lr,\n maxIter: args.maxIter,\n minErr: args.minErr,\n inputSize: args.inputSize,\n outputSize: args.outputSize,\n };\n // Transfer all the typed-array buffers we no longer need on the\n // main thread; faster than copying. Caller has already cloned.\n this.worker.postMessage(msg, [\n args.weights.buffer,\n args.features.buffer,\n args.labels.buffer,\n args.sampleWeights.buffer,\n ]);\n });\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n try {\n this.worker.postMessage({ kind: 'dispose' } satisfies WorkerRequest);\n } catch {\n /* ignore */\n }\n this.worker.terminate();\n for (const { reject } of this.pending.values()) reject(new Error('disposed'));\n this.pending.clear();\n }\n\n private onMessage_(msg: WorkerResponse): void {\n if (msg.kind === 'result') {\n const p = this.pending.get(msg.requestId);\n if (p) {\n this.pending.delete(msg.requestId);\n p.resolve({ loss: msg.loss, weights: msg.weights, lossHistory: msg.lossHistory });\n }\n } else if (msg.kind === 'error') {\n const p = this.pending.get(msg.requestId);\n if (p) {\n this.pending.delete(msg.requestId);\n p.reject(new Error(msg.message));\n }\n }\n // 'ready' handled in init_().\n }\n}\n\nexport function createTrainer(): Promise {\n return WasmTrainer.create();\n}\n\n// ---------------------------------------------------------------------------\n// Worker-thread side\n// ---------------------------------------------------------------------------\n//\n// When this module is loaded in a Worker, `self` is `WorkerGlobalScope` and\n// `window` is undefined. We use that as the dispatch.\n//\n// We import the wasm via a same-origin fetch (no DOM available so we can't\n// use a regular import URL — but Vite's bundler treats `new Worker(...)`\n// specially and `?url` imports work for assets).\n\n// Inside a Worker, `self` is a `DedicatedWorkerGlobalScope`. To keep TS\n// happy in both build contexts we use a structural cast.\ndeclare const self: {\n postMessage: (msg: unknown, transfer?: Transferable[]) => void;\n addEventListener: (event: string, handler: (ev: MessageEvent) => void) => void;\n location: { origin: string };\n importScripts?: unknown;\n};\n\nconst isWorker =\n typeof window === 'undefined' &&\n typeof self !== 'undefined' &&\n typeof (self as { importScripts?: unknown }).importScripts !== 'undefined';\n\nif (isWorker) {\n // Module-level state in worker scope.\n let mod: NispsModule | null = null;\n let mlHandle = 0;\n let weightCount = 0;\n\n // Heap buffers (allocated on first train).\n let weightsPtr = 0;\n let weightsViewLen = 0;\n let featuresPtr = 0;\n let featuresLen = 0;\n let labelsPtr = 0;\n let labelsLen = 0;\n let sampleWeightsPtr = 0;\n let sampleWeightsLen = 0;\n\n async function loadModule(seed: number): Promise {\n // Same-origin fetch to /nisps.js. The worker is served by the dev\n // server with COOP/COEP set, so this works.\n const factoryMod = await import(/* @vite-ignore */ new URL('/nisps.js', self.location.origin).toString());\n const factory: NispsModuleFactory =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (factoryMod as any).default ?? (factoryMod as any).createNispsModule;\n mod = await factory({\n locateFile: (path: string) => {\n if (path.endsWith('.wasm')) return new URL('/nisps.wasm', self.location.origin).toString();\n return path;\n },\n });\n mlHandle = mod._nisps_ml_create(0, 0, 0, 0, seed >>> 0);\n weightCount = mod._nisps_ml_weight_count(mlHandle);\n }\n\n function ensureBuffers(features: Float32Array, labels: Float32Array, sampleWeights: Float32Array, weights: Float32Array): void {\n if (!mod) throw new Error('worker module not loaded');\n\n if (weightsViewLen !== weightCount) {\n if (weightsPtr) mod._free(weightsPtr);\n weightsPtr = mod._malloc(weightCount * 4);\n weightsViewLen = weightCount;\n }\n if (features.length !== featuresLen) {\n if (featuresPtr) mod._free(featuresPtr);\n featuresPtr = mod._malloc(features.length * 4);\n featuresLen = features.length;\n }\n if (labels.length !== labelsLen) {\n if (labelsPtr) mod._free(labelsPtr);\n labelsPtr = mod._malloc(labels.length * 4);\n labelsLen = labels.length;\n }\n if (sampleWeights.length !== sampleWeightsLen) {\n if (sampleWeightsPtr) mod._free(sampleWeightsPtr);\n sampleWeightsPtr = sampleWeights.length > 0 ? mod._malloc(sampleWeights.length * 4) : 0;\n sampleWeightsLen = sampleWeights.length;\n }\n\n new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount).set(weights);\n new Float32Array(mod.HEAPF32.buffer, featuresPtr, features.length).set(features);\n new Float32Array(mod.HEAPF32.buffer, labelsPtr, labels.length).set(labels);\n if (sampleWeightsPtr) {\n new Float32Array(mod.HEAPF32.buffer, sampleWeightsPtr, sampleWeights.length).set(sampleWeights);\n }\n }\n\n function trainOnce(req: Extract): WorkerResponse {\n if (!mod) {\n return { kind: 'error', requestId: req.requestId, message: 'worker not initialised' };\n }\n try {\n ensureBuffers(req.features, req.labels, req.sampleWeights, req.weights);\n // Push current weights into our MLP.\n mod._nisps_ml_set_weights(mlHandle, weightsPtr);\n\n // Seed the example ring buffer. We must clear first because past\n // train calls may have left examples there.\n mod._nisps_ml_clear_examples(mlHandle);\n const inSz = req.inputSize;\n const outSz = req.outputSize;\n const n = req.features.length / inSz;\n // Allocate small per-example scratch (re-used across iterations of\n // this loop).\n // We use stack-equivalents by allocating once, then shifting pointers.\n for (let i = 0; i < n; ++i) {\n const fPtr = featuresPtr + i * inSz * 4;\n const lPtr = labelsPtr + i * outSz * 4;\n mod._nisps_ml_add_example(mlHandle, fPtr, lPtr);\n }\n\n // Run training.\n const swPtr = req.sampleWeights.length > 0 ? sampleWeightsPtr : 0;\n const loss = mod._nisps_ml_train(mlHandle, req.lr, req.maxIter, req.minErr, swPtr);\n\n // Read out final weights.\n mod._nisps_ml_get_weights(mlHandle, weightsPtr);\n const view = new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount);\n const outWeights = new Float32Array(view); // copy\n\n // Loss history not yet plumbed via WASM; emit just final loss.\n const lossHistory = new Float32Array([loss]);\n\n return {\n kind: 'result',\n requestId: req.requestId,\n loss,\n weights: outWeights,\n lossHistory,\n };\n } catch (err) {\n return {\n kind: 'error',\n requestId: req.requestId,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n function disposeModule(): void {\n if (!mod) return;\n if (mlHandle) {\n mod._nisps_ml_destroy(mlHandle);\n mlHandle = 0;\n }\n if (weightsPtr) { mod._free(weightsPtr); weightsPtr = 0; }\n if (featuresPtr) { mod._free(featuresPtr); featuresPtr = 0; }\n if (labelsPtr) { mod._free(labelsPtr); labelsPtr = 0; }\n if (sampleWeightsPtr) { mod._free(sampleWeightsPtr); sampleWeightsPtr = 0; }\n mod = null;\n }\n\n self.addEventListener('message', async (ev: MessageEvent) => {\n const req = ev.data;\n if (req.kind === 'init') {\n try {\n await loadModule(req.seed);\n self.postMessage({ kind: 'ready' } satisfies WorkerResponse);\n } catch (err) {\n self.postMessage({\n kind: 'error',\n requestId: 0,\n message: err instanceof Error ? err.message : String(err),\n } satisfies WorkerResponse);\n }\n } else if (req.kind === 'train') {\n const res = trainOnce(req);\n // Transfer weights back to main thread to avoid copy.\n if (res.kind === 'result') {\n self.postMessage(res, [res.weights.buffer, res.lossHistory.buffer]);\n } else {\n self.postMessage(res);\n }\n } else if (req.kind === 'dispose') {\n disposeModule();\n // Worker terminates from main thread side via .terminate().\n }\n });\n}\n","/**\n * WasmIML — main-thread ML interface backed by `nisps.wasm`.\n *\n * Owns:\n * - One `nisps.wasm` instance.\n * - One MLP handle.\n * - A JS-side `Dataset` (mirrors the C++ ring buffer; see dataset.ts).\n * - Pre-allocated heap buffers for inputs/outputs/weights/etc.\n * - A lazy `WasmTrainer` worker for off-thread async training.\n *\n * Threading model (architecture.md §6.4):\n * - Inference + sync training run on this main-thread instance.\n * - `trainAsync()` spawns/uses a worker which holds a SECOND wasm\n * instance; weights round-trip through `getWeights()`/`setWeights()`.\n *\n * Side effects: every mutation that should be visible to the UI calls into\n * `mlStore`. The store is the single source of truth for Solid components.\n *\n * Concept compatibility: this class re-implements the legacy WasmIML\n * surface area documented in `recon/04-playground.md §6` so the existing\n * Playwright debug-probe tests can keep passing once the new probe is\n * wired up.\n */\n\nimport { produce } from 'solid-js/store';\n\nimport { mlStore } from '../stores/ml-store';\nimport { coreBus } from '../stores/bus';\nimport { Dataset } from './dataset';\nimport type {\n LayerStats,\n MLArchitecture,\n NispsModule,\n NispsModuleFactory,\n} from './types';\nimport { createTrainer, type WasmTrainer } from './wasm-worker';\n\nconst NISPS_JS_URL = '/nisps.js';\nconst NISPS_WASM_URL = '/nisps.wasm';\n\n/** Default architecture matches `nisps/wasm/bindings.cpp` instantiation. */\nconst DEFAULT_INPUT_SIZE = 2;\nconst DEFAULT_OUTPUT_SIZE = 126;\n\nlet cachedFactory: NispsModuleFactory | null = null;\n\n/**\n * Load the Emscripten glue once, cache the factory.\n *\n * The glue is served from `playground/public/nisps.js` (committed). We\n * dynamically import it so the WASM only loads when the ML system is first\n * used — `/dev/primitives` doesn't pay for it.\n */\nasync function getFactory(): Promise {\n if (cachedFactory) return cachedFactory;\n // Vite serves `/nisps.js` as a normal asset; we use a dynamic-eval import\n // to avoid Vite trying to resolve it at build time.\n const url = new URL(NISPS_JS_URL, window.location.origin).toString();\n const mod = await import(/* @vite-ignore */ url);\n // Emscripten MODULARIZE=1 default export key is `default`.\n // tslint:disable-next-line:no-any\n const factory = (mod as { default?: NispsModuleFactory; createNispsModule?: NispsModuleFactory })\n .default ?? (mod as { createNispsModule?: NispsModuleFactory }).createNispsModule;\n if (!factory) throw new Error('[wasm-iml] nisps.js does not export a module factory');\n cachedFactory = factory;\n return factory;\n}\n\n/** Aligned float-array allocation helper. Returns ptr + a view. */\nclass HeapBuffer {\n readonly ptr: number;\n readonly view: Float32Array;\n constructor(private mod: NispsModule, public readonly count: number) {\n this.ptr = mod._malloc(count * 4);\n if (!this.ptr) throw new Error(`malloc(${count * 4}) failed`);\n this.view = new Float32Array(mod.HEAPF32.buffer, this.ptr, count);\n }\n /** After memory growth, refresh the view onto the new ArrayBuffer. */\n rebind(): void {\n // Re-create the view with the (possibly new) underlying buffer.\n Object.defineProperty(this, 'view', {\n value: new Float32Array(this.mod.HEAPF32.buffer, this.ptr, this.count),\n writable: false,\n });\n }\n free(): void {\n this.mod._free(this.ptr);\n }\n}\n\nclass HeapU8 {\n readonly ptr: number;\n readonly view: Uint8Array;\n constructor(private mod: NispsModule, public readonly count: number) {\n this.ptr = mod._malloc(count);\n if (!this.ptr) throw new Error(`malloc(${count}) failed`);\n this.view = new Uint8Array(mod.HEAPU8.buffer, this.ptr, count);\n }\n rebind(): void {\n Object.defineProperty(this, 'view', {\n value: new Uint8Array(this.mod.HEAPU8.buffer, this.ptr, this.count),\n writable: false,\n });\n }\n free(): void {\n this.mod._free(this.ptr);\n }\n}\n\nexport interface WasmIMLOptions {\n inputSize?: number;\n outputSize?: number;\n hiddenLayers?: ReadonlyArray;\n seed?: number;\n /** localStorage key the loaded weights/dataset will be persisted under. */\n storageKey?: string;\n maxExamples?: number;\n}\n\nexport class WasmIML {\n // WASM binding state.\n private module!: NispsModule;\n private mlHandle = 0;\n private weightCount_ = 0;\n\n // Architecture descriptor (resolved post-init from the WASM build).\n private arch_: MLArchitecture = {\n inputSize: DEFAULT_INPUT_SIZE,\n hidden: [10, 14, 18],\n outputSize: DEFAULT_OUTPUT_SIZE,\n numLayers: 4,\n };\n\n // Pre-allocated heap buffers.\n private featuresBuf!: HeapBuffer;\n private labelsBuf!: HeapBuffer;\n private weightsBuf!: HeapBuffer;\n private statsBuf!: HeapBuffer;\n private batchInBuf!: HeapBuffer;\n private batchOutBuf!: HeapBuffer;\n private pinMaskBuf!: HeapU8;\n private describeBuf!: HeapBuffer; // 6 ints, reused as 6 floats on the heap is wrong;\n // We use HEAP32 directly via a tiny scratch malloc:\n private describePtr = 0;\n\n // JS-side state.\n readonly dataset: Dataset;\n private lastLoss_: number | null = null;\n private trainer: WasmTrainer | null = null;\n private storageKey: string;\n private saveTimer: number | null = null;\n private destroyed = false;\n\n static MAX_BATCH = 4096;\n\n private constructor(opts: WasmIMLOptions) {\n this.dataset = new Dataset(opts.maxExamples ?? 100);\n this.storageKey = opts.storageKey ?? 'nisps:wasm-iml';\n }\n\n /**\n * Async factory. Loads the wasm, creates the MLP handle, allocates heap\n * buffers, and rehydrates persisted state.\n */\n static async create(opts: WasmIMLOptions = {}): Promise {\n const inst = new WasmIML(opts);\n await inst.init_(opts);\n return inst;\n }\n\n private async init_(opts: WasmIMLOptions): Promise {\n const factory = await getFactory();\n this.module = await factory({\n // Vite serves /nisps.wasm at the root; the default locateFile would\n // resolve relative to nisps.js (also at root) so this is the same\n // result, but explicit is better.\n locateFile: (path: string) => {\n if (path.endsWith('.wasm')) return new URL(NISPS_WASM_URL, window.location.origin).toString();\n return path;\n },\n });\n\n // Resolve architecture via the WASM module (compile-time fixed; we\n // stash the values for callers that need them).\n this.describePtr = this.module._malloc(6 * 4);\n this.module._nisps_ml_describe(this.describePtr);\n const dims = new Int32Array(this.module.HEAP32.buffer, this.describePtr, 6);\n this.arch_ = {\n inputSize: dims[0],\n hidden: [dims[1], dims[2], dims[3]],\n outputSize: dims[4],\n numLayers: dims[5],\n };\n\n // Caller-supplied dimensions are accepted but ignored; warn if mismatch.\n const wantedIn = opts.inputSize ?? this.arch_.inputSize;\n const wantedOut = opts.outputSize ?? this.arch_.outputSize;\n if (wantedIn !== this.arch_.inputSize || wantedOut !== this.arch_.outputSize) {\n console.warn(\n `[wasm-iml] requested ${wantedIn}->${wantedOut} but WASM build is fixed at ` +\n `${this.arch_.inputSize}->${this.arch_.outputSize}; extras are ignored.`,\n );\n }\n\n // Create the MLP. We pass dummy hidden ptr/count — the binding ignores them.\n const seed = (opts.seed ?? (Date.now() >>> 0)) >>> 0;\n this.mlHandle = this.module._nisps_ml_create(\n this.arch_.inputSize,\n this.arch_.outputSize,\n 0, // hidden ptr (unused)\n 0, // n_hidden (unused)\n seed,\n );\n if (!this.mlHandle) throw new Error('[wasm-iml] nisps_ml_create returned null');\n\n this.weightCount_ = this.module._nisps_ml_weight_count(this.mlHandle);\n\n // Heap buffers (created after we know the architecture).\n this.featuresBuf = new HeapBuffer(this.module, this.arch_.inputSize);\n this.labelsBuf = new HeapBuffer(this.module, this.arch_.outputSize);\n this.weightsBuf = new HeapBuffer(this.module, this.weightCount_);\n this.statsBuf = new HeapBuffer(this.module, this.arch_.numLayers * 4);\n this.batchInBuf = new HeapBuffer(this.module, WasmIML.MAX_BATCH * this.arch_.inputSize);\n this.batchOutBuf = new HeapBuffer(this.module, WasmIML.MAX_BATCH * this.arch_.outputSize);\n this.pinMaskBuf = new HeapU8(this.module, this.arch_.outputSize);\n\n // Push initial state to the store.\n mlStore.__setState(produce((s) => {\n s.inputSize = this.arch_.inputSize;\n s.outputSize = this.arch_.outputSize;\n s.exampleCount = 0;\n s.lastLoss = null;\n s.lossHistory = [];\n s.training = false;\n s.ready = true;\n }));\n mlStore.__setOutputs(new Float32Array(this.arch_.outputSize));\n this.publishWeights_();\n\n // Load persisted state if present (best-effort).\n this.tryLoadFromStorage_();\n }\n\n // -------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------\n\n dispose(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n if (this.saveTimer !== null) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n if (this.trainer) {\n this.trainer.dispose();\n this.trainer = null;\n }\n if (this.module && this.mlHandle) {\n this.module._nisps_ml_destroy(this.mlHandle);\n this.mlHandle = 0;\n }\n if (this.featuresBuf) this.featuresBuf.free();\n if (this.labelsBuf) this.labelsBuf.free();\n if (this.weightsBuf) this.weightsBuf.free();\n if (this.statsBuf) this.statsBuf.free();\n if (this.batchInBuf) this.batchInBuf.free();\n if (this.batchOutBuf) this.batchOutBuf.free();\n if (this.pinMaskBuf) this.pinMaskBuf.free();\n if (this.describePtr) this.module._free(this.describePtr);\n mlStore.__setState(produce((s) => {\n s.ready = false;\n }));\n }\n\n get architecture(): MLArchitecture {\n return this.arch_;\n }\n get weightCount(): number {\n return this.weightCount_;\n }\n get exampleCount(): number {\n return this.dataset.size;\n }\n get lastLoss(): number | null {\n return this.lastLoss_;\n }\n\n // -------------------------------------------------------------------\n // Inference\n // -------------------------------------------------------------------\n\n setInput(idx: number, value: number): void {\n this.module._nisps_ml_set_input(this.mlHandle, idx, value);\n }\n\n process(): Float32Array {\n this.module._nisps_ml_process(this.mlHandle);\n const ptr = this.module._nisps_ml_outputs(this.mlHandle);\n // Copy out so the caller can hold onto it across memory growth.\n const view = new Float32Array(this.module.HEAPF32.buffer, ptr, this.arch_.outputSize);\n const out = new Float32Array(view); // copy\n mlStore.__setOutputs(out);\n return out;\n }\n\n /** Convenience: setInput(0,x); setInput(1,y); process(). */\n inferXY(x: number, y: number): Float32Array {\n this.setInput(0, x);\n this.setInput(1, y);\n return this.process();\n }\n\n /**\n * Batch inference. `points` is an array of [x,y] tuples (or any vector\n * length <= inputSize; trailing entries zero-padded). Returns a flat\n * Float32Array of length n * outputSize.\n *\n * Larger requests than `MAX_BATCH` are chunked transparently.\n */\n inferBatch(points: ReadonlyArray>): Float32Array {\n const n = points.length;\n const inSz = this.arch_.inputSize;\n const outSz = this.arch_.outputSize;\n const result = new Float32Array(n * outSz);\n\n let written = 0;\n for (let offset = 0; offset < n; offset += WasmIML.MAX_BATCH) {\n const chunk = Math.min(WasmIML.MAX_BATCH, n - offset);\n // Pack into batchInBuf.\n for (let i = 0; i < chunk; ++i) {\n const src = points[offset + i];\n const base = i * inSz;\n for (let j = 0; j < inSz; ++j) this.batchInBuf.view[base + j] = src[j] ?? 0;\n }\n this.module._nisps_ml_infer_batch(\n this.mlHandle,\n this.batchInBuf.ptr,\n chunk,\n this.batchOutBuf.ptr,\n );\n // Copy out into result.\n const slice = this.batchOutBuf.view.subarray(0, chunk * outSz);\n result.set(slice, written);\n written += chunk * outSz;\n }\n return result;\n }\n\n // -------------------------------------------------------------------\n // Training\n // -------------------------------------------------------------------\n\n /**\n * Add a feature/label pair to BOTH the JS dataset and the WASM ring\n * buffer. The two stay in sync because every train() call pushes the\n * full JS dataset back into WASM (in case of weight recompute, undo\n * restore, etc.). For the sake of correctness, we re-sync on each add\n * too — cheap relative to training.\n */\n addExample(features: ReadonlyArray, labels: ReadonlyArray): boolean {\n const ok = this.dataset.add(features, labels);\n if (!ok) return false;\n this.copyExampleToWasm_(features, labels);\n mlStore.__setState(produce((s) => {\n s.exampleCount = this.dataset.size;\n }));\n coreBus.emit('ml.example_added', { count: this.dataset.size });\n this.scheduleSave_();\n return true;\n }\n\n private copyExampleToWasm_(features: ReadonlyArray, labels: ReadonlyArray): void {\n const fv = this.featuresBuf.view;\n const lv = this.labelsBuf.view;\n const inSz = this.arch_.inputSize;\n const outSz = this.arch_.outputSize;\n for (let i = 0; i < inSz; ++i) fv[i] = features[i] ?? 0;\n for (let i = 0; i < outSz; ++i) lv[i] = labels[i] ?? 0;\n this.module._nisps_ml_add_example(this.mlHandle, this.featuresBuf.ptr, this.labelsBuf.ptr);\n }\n\n /**\n * Synchronous training. Returns the final loss (also stored in\n * `lastLoss`). Updates `mlStore.lastLoss` and emits `ml.trained`.\n *\n * Caller can pass per-sample weights; if omitted the WASM side uses\n * uniform 1/n weighting.\n */\n train(lr = 1.0, maxIter = 1000, minErr = 0.001, sampleWeights?: Float32Array): number {\n if (this.dataset.isEmpty()) {\n this.lastLoss_ = 0;\n mlStore.__setState(produce((s) => { s.lastLoss = 0; }));\n return 0;\n }\n\n let weightsPtr = 0;\n let weightsHandle: HeapBuffer | null = null;\n if (sampleWeights && sampleWeights.length === this.dataset.size) {\n weightsHandle = new HeapBuffer(this.module, sampleWeights.length);\n weightsHandle.view.set(sampleWeights);\n weightsPtr = weightsHandle.ptr;\n }\n\n mlStore.__setState(produce((s) => { s.training = true; }));\n let loss = 0;\n try {\n loss = this.module._nisps_ml_train(this.mlHandle, lr, maxIter, minErr, weightsPtr);\n } finally {\n if (weightsHandle) weightsHandle.free();\n mlStore.__setState(produce((s) => { s.training = false; }));\n }\n\n this.lastLoss_ = loss;\n mlStore.__setState(produce((s) => {\n s.lastLoss = loss;\n // The C++ MLP stores per-iter history but we don't currently expose\n // it via the WASM bindings. Stream 9 may add nisps_ml_loss_history.\n s.lossHistory = [loss];\n }));\n this.publishWeights_();\n coreBus.emit('ml.trained', { loss });\n this.scheduleSave_();\n return loss;\n }\n\n /**\n * Async training via worker. The worker holds a SECOND wasm instance,\n * receives current weights + dataset, runs SGD, and returns updated\n * weights. Main-thread weights are then `setWeights()`-restored.\n */\n async trainAsync(lr = 1.0, maxIter = 1000, minErr = 0.001, sampleWeights?: Float32Array): Promise {\n if (this.dataset.isEmpty()) {\n this.lastLoss_ = 0;\n return 0;\n }\n if (!this.trainer) this.trainer = await createTrainer();\n\n const weights = this.getWeights();\n const features = new Float32Array(this.dataset.featuresFlat());\n const labels = new Float32Array(this.dataset.labelsFlat());\n const sw = sampleWeights ? new Float32Array(sampleWeights) : new Float32Array(0);\n\n mlStore.__setState(produce((s) => { s.training = true; }));\n try {\n const result = await this.trainer.train({\n weights,\n features,\n labels,\n sampleWeights: sw,\n lr,\n maxIter,\n minErr,\n inputSize: this.arch_.inputSize,\n outputSize: this.arch_.outputSize,\n });\n this.setWeights(result.weights);\n this.lastLoss_ = result.loss;\n mlStore.__setState(produce((s) => {\n s.lastLoss = result.loss;\n s.lossHistory = Array.from(result.lossHistory);\n }));\n coreBus.emit('ml.trained', { loss: result.loss });\n this.scheduleSave_();\n return result.loss;\n } finally {\n mlStore.__setState(produce((s) => { s.training = false; }));\n }\n }\n\n /** Non-destructive loss query. */\n evalLoss(): number {\n return this.module._nisps_ml_eval_loss(this.mlHandle);\n }\n\n clearExamples(): void {\n this.dataset.clear();\n this.module._nisps_ml_clear_examples(this.mlHandle);\n mlStore.__setState(produce((s) => { s.exampleCount = 0; }));\n coreBus.emit('ml.examples_cleared', undefined);\n this.scheduleSave_();\n }\n\n // -------------------------------------------------------------------\n // RL ops\n // -------------------------------------------------------------------\n\n randomiseWeights(spread = 0.6): void {\n this.module._nisps_ml_draw_weights(this.mlHandle, spread);\n this.publishWeights_();\n coreBus.emit('ml.delta_update', { reason: 'randomize' });\n this.scheduleSave_();\n }\n\n moveWeights(speed: number, spread: number, pinMask?: Uint8Array): void {\n let maskPtr = 0;\n if (pinMask) {\n const sz = Math.min(pinMask.length, this.arch_.outputSize);\n for (let i = 0; i < sz; ++i) this.pinMaskBuf.view[i] = pinMask[i];\n for (let i = sz; i < this.arch_.outputSize; ++i) this.pinMaskBuf.view[i] = 0;\n maskPtr = this.pinMaskBuf.ptr;\n }\n this.module._nisps_ml_move_weights(this.mlHandle, speed, spread, maskPtr);\n this.publishWeights_();\n // The caller (RL handler) decides whether this is a thumbs-up/down;\n // we emit a generic delta_update.\n coreBus.emit('ml.delta_update', { reason: 'thumbs_down' });\n }\n\n // -------------------------------------------------------------------\n // Weights I/O\n // -------------------------------------------------------------------\n\n getWeights(): Float32Array {\n this.module._nisps_ml_get_weights(this.mlHandle, this.weightsBuf.ptr);\n // Copy out so caller can mutate freely.\n return new Float32Array(this.weightsBuf.view);\n }\n\n setWeights(w: Float32Array | Uint8Array): void {\n if (w.length < this.weightCount_) {\n throw new Error(`setWeights: expected ${this.weightCount_} floats, got ${w.length}`);\n }\n this.weightsBuf.view.set(w as Float32Array, 0);\n this.module._nisps_ml_set_weights(this.mlHandle, this.weightsBuf.ptr);\n this.publishWeights_();\n }\n\n /** Per-layer weight stats. Returns one record per layer. */\n getLayerStats(): LayerStats[] {\n this.module._nisps_ml_get_layer_stats(this.mlHandle, this.statsBuf.ptr);\n const out: LayerStats[] = [];\n for (let i = 0; i < this.arch_.numLayers; ++i) {\n const base = i * 4;\n out.push({\n meanAbs: this.statsBuf.view[base],\n maxAbs: this.statsBuf.view[base + 1],\n deadFrac: this.statsBuf.view[base + 2],\n saturatingFrac: this.statsBuf.view[base + 3],\n });\n }\n return out;\n }\n\n /** Flat layer-stats Float32Array (numLayers * 4). For probe API. */\n getLayerStatsFlat(): Float32Array {\n this.module._nisps_ml_get_layer_stats(this.mlHandle, this.statsBuf.ptr);\n return new Float32Array(this.statsBuf.view);\n }\n\n // -------------------------------------------------------------------\n // Misc\n // -------------------------------------------------------------------\n\n reset(): void {\n this.module._nisps_ml_reset(this.mlHandle);\n this.dataset.clear();\n this.lastLoss_ = null;\n mlStore.__setState(produce((s) => {\n s.exampleCount = 0;\n s.lastLoss = null;\n s.lossHistory = [];\n }));\n this.publishWeights_();\n coreBus.emit('ml.examples_cleared', undefined);\n this.scheduleSave_();\n }\n\n // -------------------------------------------------------------------\n // Persistence\n // -------------------------------------------------------------------\n\n private scheduleSave_(): void {\n if (this.saveTimer !== null) clearTimeout(this.saveTimer);\n this.saveTimer = window.setTimeout(() => this.saveNow(), 500);\n }\n\n saveNow(): void {\n if (this.destroyed) return;\n if (this.saveTimer !== null) {\n clearTimeout(this.saveTimer);\n this.saveTimer = null;\n }\n try {\n const weights = this.getWeights();\n const payload = {\n v: 1,\n arch: this.arch_,\n weights: Array.from(weights),\n features: Array.from(this.dataset.featuresFlat()),\n labels: Array.from(this.dataset.labelsFlat()),\n size: this.dataset.size,\n lastLoss: this.lastLoss_,\n };\n localStorage.setItem(this.storageKey, JSON.stringify(payload));\n } catch (err) {\n // localStorage might be full or unavailable; not fatal.\n console.warn('[wasm-iml] saveNow failed:', err);\n }\n }\n\n private tryLoadFromStorage_(): void {\n try {\n const raw = localStorage.getItem(this.storageKey);\n if (!raw) return;\n const payload = JSON.parse(raw) as {\n v: number;\n weights: number[];\n features: number[];\n labels: number[];\n size: number;\n lastLoss: number | null;\n };\n if (payload.v !== 1) return;\n // Restore dataset (rebuild via add()).\n const inSz = this.arch_.inputSize;\n const outSz = this.arch_.outputSize;\n if (payload.size > 0 && payload.features.length === payload.size * inSz &&\n payload.labels.length === payload.size * outSz) {\n for (let i = 0; i < payload.size; ++i) {\n const f = payload.features.slice(i * inSz, (i + 1) * inSz);\n const l = payload.labels.slice(i * outSz, (i + 1) * outSz);\n this.dataset.add(f, l);\n // Also push to the WASM ring buffer.\n this.copyExampleToWasm_(f, l);\n }\n }\n // Restore weights.\n if (payload.weights.length === this.weightCount_) {\n this.setWeights(new Float32Array(payload.weights));\n }\n this.lastLoss_ = payload.lastLoss;\n mlStore.__setState(produce((s) => {\n s.exampleCount = this.dataset.size;\n s.lastLoss = this.lastLoss_;\n }));\n } catch (err) {\n console.warn('[wasm-iml] tryLoadFromStorage failed:', err);\n }\n }\n\n // -------------------------------------------------------------------\n // Helpers\n // -------------------------------------------------------------------\n\n private publishWeights_(): void {\n const w = this.getWeights();\n mlStore.__setWeights(w);\n }\n}\n"],"names":["Dataset","maxSize","features","labels","fOff","lOff","i","mode","params","n","weights","bias","decay","query","radius","radiusSq","dim","fOffI","distSq","d","diff","proximity","newerNearby","j","fOffJ","djSq","sum","WasmTrainer","trainer","ev","reject","resolve","handler","msg","seed","args","requestId","p","createTrainer","isWorker","ensureBuffers","sampleWeights","mod","weightsViewLen","weightCount","weightsPtr","featuresLen","featuresPtr","labelsLen","labelsPtr","sampleWeightsLen","sampleWeightsPtr","trainOnce","req","mlHandle","inSz","outSz","fPtr","lPtr","swPtr","loss","view","outWeights","lossHistory","err","disposeModule","loadModule","factoryMod","path","res","NISPS_JS_URL","NISPS_WASM_URL","DEFAULT_INPUT_SIZE","DEFAULT_OUTPUT_SIZE","cachedFactory","getFactory","factory","HeapBuffer","count","HeapU8","WasmIML","opts","inst","dims","wantedIn","wantedOut","mlStore","produce","s","idx","value","ptr","out","x","y","points","result","written","offset","chunk","src","base","slice","coreBus","fv","lv","lr","maxIter","minErr","weightsHandle","sw","spread","speed","pinMask","maskPtr","sz","w","payload","raw","f","l"],"mappings":"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"} \ No newline at end of file diff --git a/playground/dist/assets/wasm-worker-OATBBHaT.js b/playground/dist/assets/wasm-worker-OATBBHaT.js deleted file mode 100644 index 295739c..0000000 --- a/playground/dist/assets/wasm-worker-OATBBHaT.js +++ /dev/null @@ -1,2 +0,0 @@ -(function(){"use strict";if(typeof window>"u"&&typeof self<"u"&&typeof self.importScripts<"u"){let h=function(t,s,n,u){if(!e)throw new Error("worker module not loaded");c!==a&&(i&&e._free(i),i=e._malloc(a*4),c=a),t.length!==g&&(l&&e._free(l),l=e._malloc(t.length*4),g=t.length),s.length!==_&&(f&&e._free(f),f=e._malloc(s.length*4),_=s.length),n.length!==w&&(o&&e._free(o),o=n.length>0?e._malloc(n.length*4):0,w=n.length),new Float32Array(e.HEAPF32.buffer,i,a).set(u),new Float32Array(e.HEAPF32.buffer,l,t.length).set(t),new Float32Array(e.HEAPF32.buffer,f,s.length).set(s),o&&new Float32Array(e.HEAPF32.buffer,o,n.length).set(n)},p=function(t){if(!e)return{kind:"error",requestId:t.requestId,message:"worker not initialised"};try{h(t.features,t.labels,t.sampleWeights,t.weights),e._nisps_ml_set_weights(r,i),e._nisps_ml_clear_examples(r);const s=t.inputSize,n=t.outputSize,u=t.features.length/s;for(let d=0;d0?o:0,m=e._nisps_ml_train(r,t.lr,t.maxIter,t.minErr,F);e._nisps_ml_get_weights(r,i);const A=new Float32Array(e.HEAPF32.buffer,i,a),P=new Float32Array(A),E=new Float32Array([m]);return{kind:"result",requestId:t.requestId,loss:m,weights:P,lossHistory:E}}catch(s){return{kind:"error",requestId:t.requestId,message:s instanceof Error?s.message:String(s)}}},y=function(){e&&(r&&(e._nisps_ml_destroy(r),r=0),i&&(e._free(i),i=0),l&&(e._free(l),l=0),f&&(e._free(f),f=0),o&&(e._free(o),o=0),e=null)},e=null,r=0,a=0,i=0,c=0,l=0,g=0,f=0,_=0,o=0,w=0;async function k(t){const s=await import(new URL("/nisps.js",self.location.origin).toString());e=await(s.default??s.createNispsModule)({locateFile:u=>u.endsWith(".wasm")?new URL("/nisps.wasm",self.location.origin).toString():u}),r=e._nisps_ml_create(0,0,0,0,t>>>0),a=e._nisps_ml_weight_count(r)}self.addEventListener("message",async t=>{const s=t.data;if(s.kind==="init")try{await k(s.seed),self.postMessage({kind:"ready"})}catch(n){self.postMessage({kind:"error",requestId:0,message:n instanceof Error?n.message:String(n)})}else if(s.kind==="train"){const n=p(s);n.kind==="result"?self.postMessage(n,[n.weights.buffer,n.lossHistory.buffer]):self.postMessage(n)}else s.kind==="dispose"&&y()})}})(); -//# sourceMappingURL=wasm-worker-OATBBHaT.js.map diff --git a/playground/dist/assets/wasm-worker-OATBBHaT.js.map b/playground/dist/assets/wasm-worker-OATBBHaT.js.map deleted file mode 100644 index 289c674..0000000 --- a/playground/dist/assets/wasm-worker-OATBBHaT.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"wasm-worker-OATBBHaT.js","sources":["../src/ml/wasm-worker.ts"],"sourcesContent":["/**\n * Disposable Web Worker that runs SGD off the main thread.\n *\n * The worker holds its own `nisps.wasm` instance (architecture.md §6.4).\n * The main thread sends:\n * - current weights (so the worker is in the same state as the UI),\n * - dataset (features + labels),\n * - SGD hyperparameters,\n * and receives updated weights + final loss.\n *\n * This module exposes:\n * - `createTrainer()` - factory that spawns the worker, loads its WASM,\n * and returns a `WasmTrainer` handle.\n * - The worker entry-point itself (when this file runs in a Worker).\n *\n * The worker is implemented inline so a single TS file becomes both the\n * main-thread API and the worker bundle. Vite's `new Worker(new URL(...,\n * import.meta.url))` pattern packs it correctly.\n *\n * Lifecycle: each `WasmTrainer` is disposable via `.dispose()` which\n * terminates the worker. Tests should always dispose to avoid leaks.\n */\n\nimport type { NispsModule, NispsModuleFactory, WorkerRequest, WorkerResponse } from './types';\n\n// ---------------------------------------------------------------------------\n// Main-thread side\n// ---------------------------------------------------------------------------\n\nexport interface TrainArgs {\n weights: Float32Array;\n features: Float32Array;\n labels: Float32Array;\n /** Optional; pass empty for uniform weighting. */\n sampleWeights: Float32Array;\n lr: number;\n maxIter: number;\n minErr: number;\n inputSize: number;\n outputSize: number;\n}\n\nexport interface TrainResult {\n loss: number;\n weights: Float32Array;\n lossHistory: Float32Array;\n}\n\nexport class WasmTrainer {\n private worker: Worker;\n private nextId = 1;\n private pending = new Map void; reject: (e: unknown) => void }>();\n private disposed = false;\n\n static async create(): Promise {\n const trainer = new WasmTrainer();\n await trainer.init_();\n return trainer;\n }\n\n private constructor() {\n this.worker = new Worker(new URL('./wasm-worker.ts', import.meta.url), { type: 'module' });\n this.worker.onmessage = (ev) => this.onMessage_(ev.data as WorkerResponse);\n this.worker.onerror = (ev) => {\n // Fail any pending requests.\n for (const { reject } of this.pending.values()) reject(ev.message ?? 'worker error');\n this.pending.clear();\n };\n }\n\n private init_(): Promise {\n return new Promise((resolve, reject) => {\n const handler = (ev: MessageEvent) => {\n const msg = ev.data as WorkerResponse;\n if (msg.kind === 'ready') {\n this.worker.removeEventListener('message', handler);\n resolve();\n } else if (msg.kind === 'error') {\n this.worker.removeEventListener('message', handler);\n reject(new Error(msg.message));\n }\n };\n this.worker.addEventListener('message', handler);\n const seed = (Date.now() ^ Math.floor(Math.random() * 0xffffffff)) >>> 0;\n this.worker.postMessage({ kind: 'init', seed } satisfies WorkerRequest);\n });\n }\n\n train(args: TrainArgs): Promise {\n if (this.disposed) return Promise.reject(new Error('WasmTrainer disposed'));\n const requestId = this.nextId++;\n return new Promise((resolve, reject) => {\n this.pending.set(requestId, { resolve, reject });\n const msg: WorkerRequest = {\n kind: 'train',\n requestId,\n weights: args.weights,\n features: args.features,\n labels: args.labels,\n sampleWeights: args.sampleWeights,\n lr: args.lr,\n maxIter: args.maxIter,\n minErr: args.minErr,\n inputSize: args.inputSize,\n outputSize: args.outputSize,\n };\n // Transfer all the typed-array buffers we no longer need on the\n // main thread; faster than copying. Caller has already cloned.\n this.worker.postMessage(msg, [\n args.weights.buffer,\n args.features.buffer,\n args.labels.buffer,\n args.sampleWeights.buffer,\n ]);\n });\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n try {\n this.worker.postMessage({ kind: 'dispose' } satisfies WorkerRequest);\n } catch {\n /* ignore */\n }\n this.worker.terminate();\n for (const { reject } of this.pending.values()) reject(new Error('disposed'));\n this.pending.clear();\n }\n\n private onMessage_(msg: WorkerResponse): void {\n if (msg.kind === 'result') {\n const p = this.pending.get(msg.requestId);\n if (p) {\n this.pending.delete(msg.requestId);\n p.resolve({ loss: msg.loss, weights: msg.weights, lossHistory: msg.lossHistory });\n }\n } else if (msg.kind === 'error') {\n const p = this.pending.get(msg.requestId);\n if (p) {\n this.pending.delete(msg.requestId);\n p.reject(new Error(msg.message));\n }\n }\n // 'ready' handled in init_().\n }\n}\n\nexport function createTrainer(): Promise {\n return WasmTrainer.create();\n}\n\n// ---------------------------------------------------------------------------\n// Worker-thread side\n// ---------------------------------------------------------------------------\n//\n// When this module is loaded in a Worker, `self` is `WorkerGlobalScope` and\n// `window` is undefined. We use that as the dispatch.\n//\n// We import the wasm via a same-origin fetch (no DOM available so we can't\n// use a regular import URL — but Vite's bundler treats `new Worker(...)`\n// specially and `?url` imports work for assets).\n\n// Inside a Worker, `self` is a `DedicatedWorkerGlobalScope`. To keep TS\n// happy in both build contexts we use a structural cast.\ndeclare const self: {\n postMessage: (msg: unknown, transfer?: Transferable[]) => void;\n addEventListener: (event: string, handler: (ev: MessageEvent) => void) => void;\n location: { origin: string };\n importScripts?: unknown;\n};\n\nconst isWorker =\n typeof window === 'undefined' &&\n typeof self !== 'undefined' &&\n typeof (self as { importScripts?: unknown }).importScripts !== 'undefined';\n\nif (isWorker) {\n // Module-level state in worker scope.\n let mod: NispsModule | null = null;\n let mlHandle = 0;\n let weightCount = 0;\n\n // Heap buffers (allocated on first train).\n let weightsPtr = 0;\n let weightsViewLen = 0;\n let featuresPtr = 0;\n let featuresLen = 0;\n let labelsPtr = 0;\n let labelsLen = 0;\n let sampleWeightsPtr = 0;\n let sampleWeightsLen = 0;\n\n async function loadModule(seed: number): Promise {\n // Same-origin fetch to /nisps.js. The worker is served by the dev\n // server with COOP/COEP set, so this works.\n const factoryMod = await import(/* @vite-ignore */ new URL('/nisps.js', self.location.origin).toString());\n const factory: NispsModuleFactory =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (factoryMod as any).default ?? (factoryMod as any).createNispsModule;\n mod = await factory({\n locateFile: (path: string) => {\n if (path.endsWith('.wasm')) return new URL('/nisps.wasm', self.location.origin).toString();\n return path;\n },\n });\n mlHandle = mod._nisps_ml_create(0, 0, 0, 0, seed >>> 0);\n weightCount = mod._nisps_ml_weight_count(mlHandle);\n }\n\n function ensureBuffers(features: Float32Array, labels: Float32Array, sampleWeights: Float32Array, weights: Float32Array): void {\n if (!mod) throw new Error('worker module not loaded');\n\n if (weightsViewLen !== weightCount) {\n if (weightsPtr) mod._free(weightsPtr);\n weightsPtr = mod._malloc(weightCount * 4);\n weightsViewLen = weightCount;\n }\n if (features.length !== featuresLen) {\n if (featuresPtr) mod._free(featuresPtr);\n featuresPtr = mod._malloc(features.length * 4);\n featuresLen = features.length;\n }\n if (labels.length !== labelsLen) {\n if (labelsPtr) mod._free(labelsPtr);\n labelsPtr = mod._malloc(labels.length * 4);\n labelsLen = labels.length;\n }\n if (sampleWeights.length !== sampleWeightsLen) {\n if (sampleWeightsPtr) mod._free(sampleWeightsPtr);\n sampleWeightsPtr = sampleWeights.length > 0 ? mod._malloc(sampleWeights.length * 4) : 0;\n sampleWeightsLen = sampleWeights.length;\n }\n\n new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount).set(weights);\n new Float32Array(mod.HEAPF32.buffer, featuresPtr, features.length).set(features);\n new Float32Array(mod.HEAPF32.buffer, labelsPtr, labels.length).set(labels);\n if (sampleWeightsPtr) {\n new Float32Array(mod.HEAPF32.buffer, sampleWeightsPtr, sampleWeights.length).set(sampleWeights);\n }\n }\n\n function trainOnce(req: Extract): WorkerResponse {\n if (!mod) {\n return { kind: 'error', requestId: req.requestId, message: 'worker not initialised' };\n }\n try {\n ensureBuffers(req.features, req.labels, req.sampleWeights, req.weights);\n // Push current weights into our MLP.\n mod._nisps_ml_set_weights(mlHandle, weightsPtr);\n\n // Seed the example ring buffer. We must clear first because past\n // train calls may have left examples there.\n mod._nisps_ml_clear_examples(mlHandle);\n const inSz = req.inputSize;\n const outSz = req.outputSize;\n const n = req.features.length / inSz;\n // Allocate small per-example scratch (re-used across iterations of\n // this loop).\n // We use stack-equivalents by allocating once, then shifting pointers.\n for (let i = 0; i < n; ++i) {\n const fPtr = featuresPtr + i * inSz * 4;\n const lPtr = labelsPtr + i * outSz * 4;\n mod._nisps_ml_add_example(mlHandle, fPtr, lPtr);\n }\n\n // Run training.\n const swPtr = req.sampleWeights.length > 0 ? sampleWeightsPtr : 0;\n const loss = mod._nisps_ml_train(mlHandle, req.lr, req.maxIter, req.minErr, swPtr);\n\n // Read out final weights.\n mod._nisps_ml_get_weights(mlHandle, weightsPtr);\n const view = new Float32Array(mod.HEAPF32.buffer, weightsPtr, weightCount);\n const outWeights = new Float32Array(view); // copy\n\n // Loss history not yet plumbed via WASM; emit just final loss.\n const lossHistory = new Float32Array([loss]);\n\n return {\n kind: 'result',\n requestId: req.requestId,\n loss,\n weights: outWeights,\n lossHistory,\n };\n } catch (err) {\n return {\n kind: 'error',\n requestId: req.requestId,\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n function disposeModule(): void {\n if (!mod) return;\n if (mlHandle) {\n mod._nisps_ml_destroy(mlHandle);\n mlHandle = 0;\n }\n if (weightsPtr) { mod._free(weightsPtr); weightsPtr = 0; }\n if (featuresPtr) { mod._free(featuresPtr); featuresPtr = 0; }\n if (labelsPtr) { mod._free(labelsPtr); labelsPtr = 0; }\n if (sampleWeightsPtr) { mod._free(sampleWeightsPtr); sampleWeightsPtr = 0; }\n mod = null;\n }\n\n self.addEventListener('message', async (ev: MessageEvent) => {\n const req = ev.data;\n if (req.kind === 'init') {\n try {\n await loadModule(req.seed);\n self.postMessage({ kind: 'ready' } satisfies WorkerResponse);\n } catch (err) {\n self.postMessage({\n kind: 'error',\n requestId: 0,\n message: err instanceof Error ? err.message : String(err),\n } satisfies WorkerResponse);\n }\n } else if (req.kind === 'train') {\n const res = trainOnce(req);\n // Transfer weights back to main thread to avoid copy.\n if (res.kind === 'result') {\n self.postMessage(res, [res.weights.buffer, res.lossHistory.buffer]);\n } else {\n self.postMessage(res);\n }\n } else if (req.kind === 'dispose') {\n disposeModule();\n // Worker terminates from main thread side via .terminate().\n }\n });\n}\n"],"names":["ensureBuffers","features","labels","sampleWeights","weights","mod","weightsViewLen","weightCount","weightsPtr","featuresLen","featuresPtr","labelsLen","labelsPtr","sampleWeightsLen","sampleWeightsPtr","trainOnce","req","mlHandle","inSz","outSz","n","i","fPtr","lPtr","swPtr","loss","view","outWeights","lossHistory","err","disposeModule","loadModule","seed","factoryMod","path","ev","res"],"mappings":"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"} \ No newline at end of file diff --git a/playground/dist/index.html b/playground/dist/index.html deleted file mode 100644 index 5aaf5b0..0000000 --- a/playground/dist/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - MEMLNaut Playground - - - - -
- - diff --git a/playground/dist/nisps.js b/playground/dist/nisps.js deleted file mode 100644 index 3efe02b..0000000 --- a/playground/dist/nisps.js +++ /dev/null @@ -1,2 +0,0 @@ -var createNispsModule=(()=>{var _scriptName=globalThis.document?.currentScript?.src;return async function(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var arguments_=[];var thisProgram="./this.program";if(ENVIRONMENT_IS_WORKER){_scriptName=self.location.href}var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["d"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("nisps.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var __abort_js=()=>abort("");var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var getCFunc=ident=>{var func=Module["_"+ident];return func};var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}Module["ccall"]=ccall;Module["cwrap"]=cwrap;var _nisps_ml_create,_nisps_ml_destroy,_nisps_ml_set_input,_nisps_ml_process,_nisps_ml_outputs,_nisps_ml_infer_batch,_nisps_ml_add_example,_nisps_ml_train,_nisps_ml_eval_loss,_nisps_ml_weight_count,_nisps_ml_get_weights,_nisps_ml_set_weights,_nisps_ml_draw_weights,_nisps_ml_move_weights,_nisps_ml_get_layer_stats,_nisps_ml_example_count,_nisps_ml_clear_examples,_nisps_ml_reset,_nisps_ml_describe,_nisps_engine_create,_nisps_engine_destroy,_nisps_engine_set_params,_nisps_engine_process_block,_malloc,_free,__emscripten_stack_restore,__emscripten_stack_alloc,_emscripten_stack_get_current,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_nisps_ml_create=Module["_nisps_ml_create"]=wasmExports["e"];_nisps_ml_destroy=Module["_nisps_ml_destroy"]=wasmExports["f"];_nisps_ml_set_input=Module["_nisps_ml_set_input"]=wasmExports["g"];_nisps_ml_process=Module["_nisps_ml_process"]=wasmExports["h"];_nisps_ml_outputs=Module["_nisps_ml_outputs"]=wasmExports["i"];_nisps_ml_infer_batch=Module["_nisps_ml_infer_batch"]=wasmExports["j"];_nisps_ml_add_example=Module["_nisps_ml_add_example"]=wasmExports["k"];_nisps_ml_train=Module["_nisps_ml_train"]=wasmExports["l"];_nisps_ml_eval_loss=Module["_nisps_ml_eval_loss"]=wasmExports["m"];_nisps_ml_weight_count=Module["_nisps_ml_weight_count"]=wasmExports["n"];_nisps_ml_get_weights=Module["_nisps_ml_get_weights"]=wasmExports["o"];_nisps_ml_set_weights=Module["_nisps_ml_set_weights"]=wasmExports["p"];_nisps_ml_draw_weights=Module["_nisps_ml_draw_weights"]=wasmExports["q"];_nisps_ml_move_weights=Module["_nisps_ml_move_weights"]=wasmExports["r"];_nisps_ml_get_layer_stats=Module["_nisps_ml_get_layer_stats"]=wasmExports["s"];_nisps_ml_example_count=Module["_nisps_ml_example_count"]=wasmExports["t"];_nisps_ml_clear_examples=Module["_nisps_ml_clear_examples"]=wasmExports["u"];_nisps_ml_reset=Module["_nisps_ml_reset"]=wasmExports["v"];_nisps_ml_describe=Module["_nisps_ml_describe"]=wasmExports["w"];_nisps_engine_create=Module["_nisps_engine_create"]=wasmExports["x"];_nisps_engine_destroy=Module["_nisps_engine_destroy"]=wasmExports["y"];_nisps_engine_set_params=Module["_nisps_engine_set_params"]=wasmExports["z"];_nisps_engine_process_block=Module["_nisps_engine_process_block"]=wasmExports["A"];_malloc=Module["_malloc"]=wasmExports["B"];_free=Module["_free"]=wasmExports["C"];__emscripten_stack_restore=wasmExports["D"];__emscripten_stack_alloc=wasmExports["E"];_emscripten_stack_get_current=wasmExports["F"];memory=wasmMemory=wasmExports["c"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={a:__abort_js,b:_emscripten_resize_heap};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})} -;return moduleRtn}})();if(typeof exports==="object"&&typeof module==="object"){module.exports=createNispsModule;module.exports.default=createNispsModule}else if(typeof define==="function"&&define["amd"])define([],()=>createNispsModule); diff --git a/playground/dist/nisps.wasm b/playground/dist/nisps.wasm deleted file mode 100755 index 1eecd696149741a8513c5e28ccc39c78688be0a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95864 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 diff --git a/playwright.config.js b/playwright.config.js deleted file mode 100644 index be794e9..0000000 --- a/playwright.config.js +++ /dev/null @@ -1,27 +0,0 @@ -const { defineConfig } = require('@playwright/test'); - -module.exports = defineConfig({ - testDir: './tests/e2e', - timeout: 30_000, - expect: { timeout: 10_000 }, - use: { - baseURL: 'http://localhost:7331', - headless: true, - // WASM + AudioContext require a stable origin - ignoreHTTPSErrors: true, - }, - // Start a static file server against the playground/ dir before tests run. - // python3 -m http.server serves directory listings and static assets fine. - // WASM files need correct MIME type — Python's server handles .wasm correctly. - webServer: { - command: 'python3 -m http.server 7331', - cwd: './playground', - url: 'http://localhost:7331', - reuseExistingServer: true, - timeout: 10_000, - }, - projects: [ - { name: 'chromium', use: { browserName: 'chromium' } }, - ], - reporter: [['list'], ['html', { open: 'never' }]], -}); diff --git a/tests/e2e/engine-switching.spec.js b/tests/e2e/engine-switching.spec.js deleted file mode 100644 index 2151c7e..0000000 --- a/tests/e2e/engine-switching.spec.js +++ /dev/null @@ -1,420 +0,0 @@ -/** - * Engine switching e2e tests — verify that all three synth engines - * (C15 Shaper-Feedback, Additive, FM Matrix) can be selected, initialised, - * and driven by the NISPS ML engine correctly. - * - * Also tests the EOC chain integration across engine switches. - */ -const { test, expect } = require('@playwright/test'); -const { loadApp, statusText } = require('./helpers'); - -// Engine metadata expected from the switcher -const ENGINES = { - 'shaper-feedback': { displayName: 'C15 Shaper-Feedback', paramCount: 126 }, - 'additive': { displayName: 'Additive', paramCount: 48 }, - 'fm': { displayName: 'FM Matrix', paramCount: 55 }, -}; - -/** - * Helper: switch to an engine via the engine switcher UI. - * Opens the synth drawer, clicks the engine card, accepts any confirm dialog. - */ -async function switchEngine(page, engineId) { - // Open synth drawer if not already open - const drawer = page.locator('#drawer-synth'); - if (await drawer.evaluate(el => el.classList.contains('hidden'))) { - await page.click('[data-drawer="synth"]'); - } - // Click the engine card - page.once('dialog', dialog => dialog.accept()); - await page.click(`.engine-card[data-engine-id="${engineId}"]`); - // Wait for the switcher to mark it active - await expect(page.locator(`.engine-card[data-engine-id="${engineId}"]`)).toHaveClass(/active/, { timeout: 15_000 }); -} - -/** - * Helper: get the current engine id from the debug probe. - */ -async function getActiveEngineId(page) { - return page.evaluate(() => window.__nisps?.activeEngine?.id ?? null); -} - -/** - * Helper: get the current MLP output count. - */ -async function getOutputCount(page) { - return page.evaluate(() => window.__nisps?.getOutputs()?.length ?? 0); -} - -// --------------------------------------------------------------------------- -// Expose activeEngine on the debug probe so tests can query it -// --------------------------------------------------------------------------- - -test.describe('Engine switching', () => { - - test.beforeEach(async ({ page }) => { - await loadApp(page); - // Extend the debug probe with engine-related getters - await page.evaluate(() => { - if (window.__nisps) { - // These are closures over the module-scoped vars in a-app.js, - // but we can read them via the existing probe's eocChain and other refs. - // The probe already exposes getOutputs() which reflects N_OUTPUTS. - } - }); - }); - - test.describe('Default state', () => { - test('default engine is C15 Shaper-Feedback', async ({ page }) => { - const btnText = await page.locator('#synth-mode-btn').textContent(); - // The synth mode button should reflect the default engine (may say "Synth" or the engine name) - expect(btnText).toBeTruthy(); - }); - - test('default output count is 126 (C15)', async ({ page }) => { - const count = await getOutputCount(page); - // Default mode is visual (20 outputs), not synth - // Switch to synth mode first - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(500); - const synthCount = await getOutputCount(page); - expect(synthCount).toBe(126); - }); - - test('heatmap shows 126 cells in synth mode', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(500); - const cellCount = await page.locator('#heatmap-cells .heatmap-cell').count(); - expect(cellCount).toBe(126); - }); - }); - - test.describe('Engine switcher UI', () => { - test('synth drawer contains engine cards', async ({ page }) => { - await page.click('[data-drawer="synth"]'); - const cards = page.locator('.engine-card'); - // C15 shaper-feedback, additive, fm, modular - await expect(cards).toHaveCount(4); - }); - - test('C15 card is active by default', async ({ page }) => { - await page.click('[data-drawer="synth"]'); - const c15Card = page.locator('.engine-card[data-engine-id="shaper-feedback"]'); - await expect(c15Card).toHaveClass(/active/); - }); - - test('additive and FM cards are not active by default', async ({ page }) => { - await page.click('[data-drawer="synth"]'); - await expect(page.locator('.engine-card[data-engine-id="additive"]')).not.toHaveClass(/active/); - await expect(page.locator('.engine-card[data-engine-id="fm"]')).not.toHaveClass(/active/); - }); - }); - - test.describe('Switch to Additive engine', () => { - test('switching resizes MLP to 48 outputs', async ({ page }) => { - // Go to synth mode first - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - // Switch engine - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - - const count = await getOutputCount(page); - expect(count).toBe(48); - }); - - test('heatmap rebuilds with 48 cells', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - - const cellCount = await page.locator('#heatmap-cells .heatmap-cell').count(); - expect(cellCount).toBe(48); - }); - - test('outputs are bounded [0, 1]', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - - const outputs = await page.evaluate(() => window.__nisps.getOutputs()); - expect(outputs).toHaveLength(48); - for (const v of outputs) { - expect(v).toBeGreaterThanOrEqual(0); - expect(v).toBeLessThanOrEqual(1); - } - }); - - test('different inputs produce different outputs', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - - const out1 = await page.evaluate(() => { - window.__nisps.setInputs(0.1, 0.1); - return window.__nisps.getOutputs(); - }); - const out2 = await page.evaluate(() => { - window.__nisps.setInputs(0.9, 0.9); - return window.__nisps.getOutputs(); - }); - const anyDiff = out1.some((v, i) => Math.abs(v - out2[i]) > 0.001); - expect(anyDiff).toBe(true); - }); - - test('training works with additive param count', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - - const loss = await page.evaluate(() => { - const nisps = window.__nisps; - // Add two contrasting examples - nisps.iml.addExample([0.1, 0.1], new Array(48).fill(0.2)); - nisps.iml.addExample([0.9, 0.9], new Array(48).fill(0.8)); - return nisps.train(); - }); - expect(loss).toBeGreaterThanOrEqual(0); - expect(loss).toBeLessThan(1); - }); - }); - - test.describe('Switch to FM Matrix engine', () => { - test('switching resizes MLP to 55 outputs', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - await switchEngine(page, 'fm'); - await page.waitForTimeout(500); - - const count = await getOutputCount(page); - expect(count).toBe(55); - }); - - test('heatmap rebuilds with 55 cells', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - await switchEngine(page, 'fm'); - await page.waitForTimeout(500); - - const cellCount = await page.locator('#heatmap-cells .heatmap-cell').count(); - expect(cellCount).toBe(55); - }); - - test('outputs are bounded [0, 1]', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await switchEngine(page, 'fm'); - await page.waitForTimeout(500); - - const outputs = await page.evaluate(() => window.__nisps.getOutputs()); - expect(outputs).toHaveLength(55); - for (const v of outputs) { - expect(v).toBeGreaterThanOrEqual(0); - expect(v).toBeLessThanOrEqual(1); - } - }); - - test('training works with FM param count', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await switchEngine(page, 'fm'); - await page.waitForTimeout(500); - - const loss = await page.evaluate(() => { - const nisps = window.__nisps; - nisps.iml.addExample([0.2, 0.8], new Array(55).fill(0.3)); - nisps.iml.addExample([0.8, 0.2], new Array(55).fill(0.7)); - return nisps.train(); - }); - expect(loss).toBeGreaterThanOrEqual(0); - expect(loss).toBeLessThan(1); - }); - }); - - test.describe('Round-trip switching', () => { - test('switching C15 → Additive → C15 restores 126 outputs', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - // Switch to additive - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - expect(await getOutputCount(page)).toBe(48); - - // Switch back to C15 - await switchEngine(page, 'shaper-feedback'); - await page.waitForTimeout(500); - expect(await getOutputCount(page)).toBe(126); - }); - - test('switching C15 → FM → Additive → C15 restores correctly each time', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - await switchEngine(page, 'fm'); - await page.waitForTimeout(500); - expect(await getOutputCount(page)).toBe(55); - - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - expect(await getOutputCount(page)).toBe(48); - - await switchEngine(page, 'shaper-feedback'); - await page.waitForTimeout(500); - expect(await getOutputCount(page)).toBe(126); - }); - }); - - test.describe('Warm-start weight preservation', () => { - test('hidden layer weights are preserved across resize', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - // Get weights before switch (first 100 hidden-layer weights) - const beforeWeights = await page.evaluate(() => { - return window.__nisps.getWeights().slice(0, 100); - }); - - // Switch to additive (48 outputs, hidden layers unchanged) - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - - // Get weights after — first 100 should be identical (hidden layer prefix) - const afterWeights = await page.evaluate(() => { - return window.__nisps.getWeights().slice(0, 100); - }); - - // Hidden layer weights should be preserved (warm-start) - let matchCount = 0; - for (let i = 0; i < 100; i++) { - if (Math.abs(beforeWeights[i] - afterWeights[i]) < 1e-6) matchCount++; - } - // Allow some tolerance — at least 90% should match - expect(matchCount).toBeGreaterThan(90); - }); - }); - - test.describe('EOC chain across engine switches', () => { - test('EOC drawer is accessible from all engines', async ({ page }) => { - // Open EOC drawer with default C15 - await page.click('[data-drawer="eoc"]'); - await expect(page.locator('#drawer-eoc')).not.toHaveClass(/hidden/); - - // Switch to additive - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - - // EOC drawer should still be accessible — engine switch may have closed it - // so click the dock icon; if already open, clicking toggles closed then re-open - const eocDrawer = page.locator('#drawer-eoc'); - if (await eocDrawer.evaluate(el => el.classList.contains('hidden'))) { - await page.click('[data-drawer="eoc"]'); - } - await expect(eocDrawer).not.toHaveClass(/hidden/); - }); - - test('NISPS mode selector is present in EOC drawer', async ({ page }) => { - await page.click('[data-drawer="eoc"]'); - const modeButtons = page.locator('.eoc-nisps-bar .pill-opt'); - const count = await modeButtons.count(); - expect(count).toBe(4); // Bypass, Shared, Linked, Independent - }); - }); - - test.describe('SynthVisualizer with different engines', () => { - test('synth-vis-canvas is visible in synth mode for all engines', async ({ page }) => { - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - // Check canvas is visible for C15 - const canvas = page.locator('#synth-vis-canvas'); - await expect(canvas).toBeVisible(); - - // Switch to additive — canvas should remain visible - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - await expect(canvas).toBeVisible(); - - // Switch to FM — canvas should remain visible - await switchEngine(page, 'fm'); - await page.waitForTimeout(500); - await expect(canvas).toBeVisible(); - }); - }); - - test.describe('No console errors during engine switch', () => { - test('switching to additive produces no errors', async ({ page }) => { - const errors = []; - page.on('pageerror', err => errors.push(err.message)); - - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - await switchEngine(page, 'additive'); - await page.waitForTimeout(1000); - - // Filter out known non-critical errors - const critical = errors.filter(e => - !e.includes('ResizeObserver') && !e.includes('net::ERR') - ); - expect(critical).toEqual([]); - }); - - test('switching to FM produces no errors', async ({ page }) => { - const errors = []; - page.on('pageerror', err => errors.push(err.message)); - - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - await switchEngine(page, 'fm'); - await page.waitForTimeout(1000); - - const critical = errors.filter(e => - !e.includes('ResizeObserver') && !e.includes('net::ERR') - ); - expect(critical).toEqual([]); - }); - - test('round-trip switching produces no errors', async ({ page }) => { - const errors = []; - page.on('pageerror', err => errors.push(err.message)); - - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.waitForTimeout(300); - - await switchEngine(page, 'additive'); - await page.waitForTimeout(500); - await switchEngine(page, 'fm'); - await page.waitForTimeout(500); - await switchEngine(page, 'shaper-feedback'); - await page.waitForTimeout(500); - - const critical = errors.filter(e => - !e.includes('ResizeObserver') && !e.includes('net::ERR') - ); - expect(critical).toEqual([]); - }); - }); -}); diff --git a/tests/e2e/helpers.js b/tests/e2e/helpers.js deleted file mode 100644 index 1a1b319..0000000 --- a/tests/e2e/helpers.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Shared helpers for e2e tests. - */ -const { expect } = require('@playwright/test'); - -/** - * Navigate to a-immersive with ?debug=1 and wait for the WASM engine to - * initialise and expose window.__nisps. - * - * @param {import('@playwright/test').Page} page - * @param {string} extraParams - additional query string, e.g. '&preset=beginner-1' - */ -async function loadApp(page, extraParams = '') { - // Clear app state but mark help as seen so the overlay doesn't block clicks. - await page.addInitScript(() => { - localStorage.removeItem('nisps-a-immersive'); - localStorage.setItem('nisps-help-seen', '1'); - }); - await page.goto(`/a-immersive.html?debug=1${extraParams}`); - // Wait until the debug probe is ready (WASM init is async). - await page.waitForFunction(() => window.__nisps !== undefined, { timeout: 20_000 }); -} - -/** - * Returns the current text content of the status line. - * @param {import('@playwright/test').Page} page - */ -async function statusText(page) { - return page.locator('#status-text').textContent(); -} - -module.exports = { loadApp, statusText }; diff --git a/tests/e2e/input-pipeline.spec.js b/tests/e2e/input-pipeline.spec.js deleted file mode 100644 index c099a30..0000000 --- a/tests/e2e/input-pipeline.spec.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Input pipeline tests — joystick → MLP inputs → outputs → heatmap. - */ -const { test, expect } = require('@playwright/test'); -const { loadApp } = require('./helpers'); - -test.describe('Joystick → output pipeline', () => { - test('different input positions produce different outputs', async ({ page }) => { - await loadApp(page); - - await page.evaluate(() => window.__nisps.setInputs(0.1, 0.1)); - const out1 = await page.evaluate(() => window.__nisps.getOutputs()); - - await page.evaluate(() => window.__nisps.setInputs(0.9, 0.9)); - const out2 = await page.evaluate(() => window.__nisps.getOutputs()); - - const anyChanged = out1.some((v, i) => Math.abs(v - out2[i]) > 0.0001); - expect(anyChanged).toBe(true); - }); - - test('all outputs remain in [0, 1] across input positions', async ({ page }) => { - await loadApp(page); - const corners = [[0, 0], [0, 1], [1, 0], [1, 1], [0.5, 0.5]]; - for (const [x, y] of corners) { - await page.evaluate(([x, y]) => window.__nisps.setInputs(x, y), [x, y]); - const outputs = await page.evaluate(() => window.__nisps.getOutputs()); - for (const v of outputs) { - expect(v).toBeGreaterThanOrEqual(0); - expect(v).toBeLessThanOrEqual(1); - } - } - }); - - test('heatmap bar widths change when inputs change', async ({ page }) => { - await loadApp(page); - - const widths1 = await page.evaluate(() => - Array.from(document.querySelectorAll('.heatmap-cell-bar')).map(el => el.style.width) - ); - - // Move to far corner - await page.evaluate(() => window.__nisps.setInputs(0.95, 0.05)); - - const widths2 = await page.evaluate(() => - Array.from(document.querySelectorAll('.heatmap-cell-bar')).map(el => el.style.width) - ); - - const anyChanged = widths1.some((w, i) => w !== widths2[i]); - expect(anyChanged).toBe(true); - }); - - test('mouse drag on joystick container updates MLP inputs', async ({ page }) => { - await loadApp(page); - - const box = await page.locator('#joystick-container').boundingBox(); - const cx = box.x + box.width / 2; - const cy = box.y + box.height / 2; - - // Drag from center towards bottom-right - await page.mouse.move(cx, cy); - await page.mouse.down(); - await page.mouse.move(cx + box.width * 0.3, cy + box.height * 0.3, { steps: 10 }); - await page.mouse.up(); - - // After drag, at least one input axis should have moved from 0.5 - const [x, y] = await page.evaluate(() => [ - window.__nisps.iml.inputState[0], - window.__nisps.iml.inputState[1], - ]); - const moved = Math.abs(x - 0.5) > 0.01 || Math.abs(y - 0.5) > 0.01; - expect(moved).toBe(true); - }); - - test('setInputs clamps values to [0, 1]', async ({ page }) => { - await loadApp(page); - await page.evaluate(() => window.__nisps.setInputs(-5, 99)); - const [x, y] = await page.evaluate(() => [ - window.__nisps.iml.inputState[0], - window.__nisps.iml.inputState[1], - ]); - expect(x).toBeGreaterThanOrEqual(0); - expect(x).toBeLessThanOrEqual(1); - expect(y).toBeGreaterThanOrEqual(0); - expect(y).toBeLessThanOrEqual(1); - }); - - test('after training, moving inputs produces smoothly varying outputs', async ({ page }) => { - await loadApp(page); - - // Load calm-to-chaotic preset (3 examples spanning the input space) - await page.click('[data-drawer="training"]'); - await page.click('[data-preset="calm-to-chaotic"]'); - await page.waitForFunction( - () => document.getElementById('status-text').textContent.includes('loss'), - { timeout: 15_000 } - ); - - // Sample 5 positions and verify all outputs are bounded - const positions = [0.0, 0.25, 0.5, 0.75, 1.0]; - for (const t of positions) { - await page.evaluate((t) => window.__nisps.setInputs(t, 1 - t), t); - const outputs = await page.evaluate(() => window.__nisps.getOutputs()); - expect(outputs).toHaveLength(126); - for (const v of outputs) { - expect(v).toBeGreaterThanOrEqual(0); - expect(v).toBeLessThanOrEqual(1); - } - } - }); -}); diff --git a/tests/e2e/ml-engine.spec.js b/tests/e2e/ml-engine.spec.js deleted file mode 100644 index 17fa358..0000000 --- a/tests/e2e/ml-engine.spec.js +++ /dev/null @@ -1,170 +0,0 @@ -/** - * ML engine sanity tests — verify the WASM IML behaves correctly: - * - outputs are always bounded [0, 1] - * - randomize produces different outputs - * - thumbs-up captures the current rawParamValues as the training label - * - training completes and produces a finite loss - * - thumbs-down moves weights and changes outputs - * - async training (triggered by thumbs-up) updates the status line - */ -const { test, expect } = require('@playwright/test'); -const { loadApp, statusText } = require('./helpers'); - -// Two contrasting examples with known inputs and all-low / all-high targets. -const EXAMPLE_LOW = { input: [0.1, 0.9], output: new Array(126).fill(0.1) }; -const EXAMPLE_HIGH = { input: [0.9, 0.1], output: new Array(126).fill(0.9) }; - -test.describe('ML engine (WASM IML)', () => { - test('probe is exposed after WASM init', async ({ page }) => { - await loadApp(page); - const probe = await page.evaluate(() => typeof window.__nisps); - expect(probe).toBe('object'); - }); - - test('initial outputs are all in [0, 1]', async ({ page }) => { - await loadApp(page); - const outputs = await page.evaluate(() => window.__nisps.getOutputs()); - expect(outputs).toHaveLength(126); - for (const v of outputs) { - expect(v).toBeGreaterThanOrEqual(0); - expect(v).toBeLessThanOrEqual(1); - } - }); - - test('initial state is 0 examples, untrained', async ({ page }) => { - await loadApp(page); - const count = await page.evaluate(() => window.__nisps.getExampleCount()); - expect(count).toBe(0); - const loss = await page.evaluate(() => window.__nisps.getLoss()); - expect(loss).toBeNull(); - }); - - test('randomize changes outputs', async ({ page }) => { - await loadApp(page); - const before = await page.evaluate(() => window.__nisps.getOutputs()); - await page.evaluate(() => window.__nisps.randomise()); - const after = await page.evaluate(() => window.__nisps.getOutputs()); - const anyChanged = before.some((v, i) => Math.abs(v - after[i]) > 0.001); - expect(anyChanged).toBe(true); - }); - - test('thumbs-up increments example count by 1', async ({ page }) => { - await loadApp(page); - await page.evaluate(() => window.__nisps.thumbsUp()); - // Give async training a moment to start but we only need to check example count - await page.waitForTimeout(100); - const count = await page.evaluate(() => window.__nisps.getExampleCount()); - expect(count).toBe(1); - }); - - test('thumbs-up captures current input position and all 126 output values', async ({ page }) => { - await loadApp(page); - // Set a known joystick position via the probe - await page.evaluate(() => window.__nisps.setInputs(0.25, 0.75)); - await page.evaluate(() => window.__nisps.thumbsUp()); - await page.waitForTimeout(100); - - const { features, labels } = await page.evaluate(() => ({ - features: window.__nisps.iml.dataset.features, - labels: window.__nisps.iml.dataset.labels, - })); - - expect(features).toHaveLength(1); - expect(labels).toHaveLength(1); - - // Input dimension = 2 (joystick x/y, pipeline-processed) - expect(features[0]).toHaveLength(2); - // The input pipeline may transform values; inputs must stay in [0, 1] - expect(features[0][0]).toBeGreaterThanOrEqual(0); - expect(features[0][0]).toBeLessThanOrEqual(1); - expect(features[0][1]).toBeGreaterThanOrEqual(0); - expect(features[0][1]).toBeLessThanOrEqual(1); - - // Labels = all 126 output values, captured from rawParamValues at click time - expect(labels[0]).toHaveLength(126); - for (const v of labels[0]) { - expect(v).toBeGreaterThanOrEqual(0); - expect(v).toBeLessThanOrEqual(1); - } - }); - - test('sync train() returns a finite non-negative loss', async ({ page }) => { - await loadApp(page); - await page.evaluate(([low, high]) => { - window.__nisps.iml.addExample(low.input, low.output); - window.__nisps.iml.addExample(high.input, high.output); - }, [EXAMPLE_LOW, EXAMPLE_HIGH]); - - const loss = await page.evaluate(() => window.__nisps.train()); - expect(typeof loss).toBe('number'); - expect(isFinite(loss)).toBe(true); - expect(loss).toBeGreaterThanOrEqual(0); - }); - - test('training with contrasting examples produces a lower loss than initial', async ({ page }) => { - await loadApp(page); - // Initial inference — loss is null (never trained), so randomise to get a baseline - await page.evaluate(() => window.__nisps.randomise()); - - await page.evaluate(([low, high]) => { - window.__nisps.iml.addExample(low.input, low.output); - window.__nisps.iml.addExample(high.input, high.output); - }, [EXAMPLE_LOW, EXAMPLE_HIGH]); - - const loss1 = await page.evaluate(() => window.__nisps.train()); - const loss2 = await page.evaluate(() => window.__nisps.train()); - - // Second training run on same data should converge further (loss2 <= loss1) - expect(loss2).toBeLessThanOrEqual(loss1 + 1e-6); - }); - - test('status line reflects example count and loss after training', async ({ page }) => { - await loadApp(page); - await page.evaluate(([low, high]) => { - window.__nisps.iml.addExample(low.input, low.output); - window.__nisps.iml.addExample(high.input, high.output); - window.__nisps.train(); - }, [EXAMPLE_LOW, EXAMPLE_HIGH]); - - // updateStatus() is called inside trainModel() - const text = await page.locator('#status-text').textContent(); - expect(text).toContain('2 examples'); - expect(text).toContain('loss'); - }); - - test('thumbs-down changes outputs (weight noise)', async ({ page }) => { - await loadApp(page); - const before = await page.evaluate(() => window.__nisps.getOutputs()); - await page.evaluate(() => window.__nisps.thumbsDown()); - const after = await page.evaluate(() => window.__nisps.getOutputs()); - const anyChanged = before.some((v, i) => Math.abs(v - after[i]) > 0.0001); - expect(anyChanged).toBe(true); - }); - - test('async training via thumbs-up button updates status with loss', async ({ page }) => { - await loadApp(page); - await page.click('#btn-thumbsup'); - // Wait for the async training to complete and status to update - await page.waitForFunction( - () => document.getElementById('status-text').textContent.includes('loss'), - { timeout: 15_000 } - ); - const text = await page.locator('#status-text').textContent(); - expect(text).toContain('1 example'); - expect(text).toContain('loss'); - }); - - test('clear examples resets to 0 and marks untrained', async ({ page }) => { - await loadApp(page); - await page.evaluate(([low]) => { - window.__nisps.iml.addExample(low.input, low.output); - window.__nisps.train(); - }, [EXAMPLE_LOW]); - expect(await page.evaluate(() => window.__nisps.getExampleCount())).toBe(1); - - await page.evaluate(() => window.__nisps.clearExamples()); - expect(await page.evaluate(() => window.__nisps.getExampleCount())).toBe(0); - const text = await page.locator('#status-text').textContent(); - expect(text).toContain('0 examples'); - }); -}); diff --git a/tests/e2e/modular-mode.spec.js b/tests/e2e/modular-mode.spec.js deleted file mode 100644 index 10a0c2e..0000000 --- a/tests/e2e/modular-mode.spec.js +++ /dev/null @@ -1,294 +0,0 @@ -/** - * Modular mode e2e tests. - * - * These tests exercise the modular engine end-to-end via the debug probe - * (?debug=1, window.__nisps). - * - * NB: modular paramMeta = 4 ADSR × 4 + 8 LFO × 2 + 48 × 10 matrix = 512. - * Every matrix cell is in paramMeta so `modular-ui.updateLive()` can - * mirror live MLP outputs into the matrix DOM. Silence-on-joystick - * regressions are prevented at the DSP level: each sub-engine's - * `amp_val = clamp(base_amp + max(0, mod_amp))`, so matrix d08_amp - * cells can only add to the amp floor and base_amp=1.0 (default) - * keeps the voice audible regardless of what the MLP outputs. - */ -const { test, expect } = require('@playwright/test'); -const { loadApp } = require('./helpers'); - -/** - * Switch the active engine to `modular` via the engine-switcher UI. - * Waits until window.__nisps.activeEngineId === 'modular'. - */ -async function switchToModular(page) { - // Open the synth drawer first (needed to see the engine cards). - const drawer = page.locator('#drawer-synth'); - if (await drawer.evaluate(el => el.classList.contains('hidden'))) { - await page.click('[data-drawer="synth"]'); - } - page.once('dialog', d => d.accept()); - await page.click('.engine-card[data-engine-id="modular"]'); - // Wait until setActiveEngine completes AND the modular dock icon becomes - // visible — the dock icon is revealed from the tail end of setActiveEngine - // so this guarantees the initial modular-ui.refresh() restore pass has run. - // Without this we race: test code can fire before modularUI.show() - // reaches refresh()'s pendingRestore branch, which then wipes the test's - // subsequent sub-engine swap. - await page.waitForFunction( - () => window.__nisps?.activeEngineId === 'modular', - null, - { timeout: 20_000 } - ); - await page.waitForFunction( - () => !document.querySelector('.dock-icon[data-drawer="modular"]')?.classList.contains('hidden'), - null, - { timeout: 20_000 } - ); - // Also yield one extra microtask so any synchronous deferred work queued - // inside setActiveEngine settles before we start poking the engine. - await page.evaluate(() => new Promise(r => setTimeout(r, 0))); -} - -test.describe('Modular mode', () => { - - test('switching to modular yields paramCount = 512', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - const count = await page.evaluate(() => window.__nisps.paramCount); - // 4 ADSR × (attack, decay, sustain, release) = 16 - // 8 LFO × (rate, morph) = 16 - // 48 × 10 matrix cells = 480 - expect(count).toBe(512); - }); - - test('matrix cells are in paramMeta so modular-ui updateLive can read them', async ({ page }) => { - // Regression guard: modular-ui.js `updateLive()` depends on every - // matrix cell being present in paramMeta so it can map MLP output - // indices to cell DOM. A previous fix that gated matrix cells on - // an opt-in flag broke the matrix UI's live visualisation when the - // joystick moved. If this test fails, inspect _rebuildParamMeta(). - await loadApp(page); - await switchToModular(page); - const info = await page.evaluate(() => { - const meta = window.__nisps.activeEngine.paramMeta; - const matrix = meta.filter(m => m.group && m.group.startsWith('Matrix/')); - const destBuckets = {}; - for (const m of matrix) { - const d = m.group.split('/')[1]; - destBuckets[d] = (destBuckets[d] || 0) + 1; - } - return { total: meta.length, matrixCount: matrix.length, destBuckets }; - }); - expect(info.total).toBe(512); - expect(info.matrixCount).toBe(480); - // 10 destinations × 48 sources each - expect(Object.keys(info.destBuckets).length).toBe(10); - for (const count of Object.values(info.destBuckets)) { - expect(count).toBe(48); - } - }); - - test('debug probe exposes modular hooks', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - const hooks = await page.evaluate(() => ({ - hasGet: typeof window.__nisps.getModularState === 'function', - hasSet: typeof window.__nisps.setModularState === 'function', - hasSwap: typeof window.__nisps.setModularSubEngine === 'function', - hasPreset: typeof window.__nisps.applyModularPreset === 'function', - hasCounts: typeof window.__nisps.setModularSourceCount === 'function', - presetList: window.__nisps.listModularPresets?.()?.length ?? 0, - })); - expect(hooks.hasGet).toBe(true); - expect(hooks.hasSet).toBe(true); - expect(hooks.hasSwap).toBe(true); - expect(hooks.hasPreset).toBe(true); - expect(hooks.hasCounts).toBe(true); - expect(hooks.presetList).toBe(6); - }); - - test('sub-engine swap keeps paramCount = 512', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - - for (const sub of ['additive', 'fm', 'subtractive']) { - await page.evaluate(async (id) => { - await window.__nisps.setModularSubEngine(id); - }, sub); - const info = await page.evaluate(() => ({ - paramCount: window.__nisps.paramCount, - subId: window.__nisps.activeEngine?.activeSubEngineId, - })); - expect(info.subId).toBe(sub); - expect(info.paramCount).toBe(512); - } - }); - - test('destNames differ between sub-engines', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - const sub = await page.evaluate(() => window.__nisps.activeEngine?.destNames); - await page.evaluate(async () => { - await window.__nisps.setModularSubEngine('fm'); - }); - const fm = await page.evaluate(() => window.__nisps.activeEngine?.destNames); - expect(sub).toBeTruthy(); - expect(fm).toBeTruthy(); - expect(sub).toEqual(['pitch','osc2_detune','osc3_detune','osc_mix_bal','noise_level','cutoff','resonance','filter_env_amt','amp','pan']); - expect(fm[1]).toBe('op1_level'); // fm-specific - expect(sub).not.toEqual(fm); - }); - - test('ADSR count change rebuilds paramMeta', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - - const baseline = await page.evaluate(() => window.__nisps.paramCount); - expect(baseline).toBe(512); - - await page.evaluate(() => window.__nisps.setModularSourceCount(6, 8)); - const after = await page.evaluate(() => window.__nisps.paramCount); - // 6 ADSR × 4 + 8 LFO × 2 + 48 × 10 = 24 + 16 + 480 = 520 - expect(after).toBe(520); - - await page.evaluate(() => window.__nisps.setModularSourceCount(4, 8)); - const reset = await page.evaluate(() => window.__nisps.paramCount); - expect(reset).toBe(512); - }); - - test('getState returns a snapshot with raw dsp values', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - const snap = await page.evaluate(() => window.__nisps.getModularState()); - expect(snap).toBeTruthy(); - expect(snap.version).toBe(1); - expect(snap.subEngine).toBe('subtractive'); - expect(typeof snap.dsp).toBe('object'); - // Default patch pre-arms ADSR1 but does not route it to amp — voice - // stays audible because base_amp defaults to 1.0. - expect(snap.dsp['MM_ADSR/00_adsr01_enable']).toBeCloseTo(1.0, 4); - expect(snap.dsp['4_Master/04_base_amp']).toBeCloseTo(1.0, 4); - }); - - test('matrix cell persistence across setState', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - - // Pick a distinctive cell: ADSR2 (s=1) → cutoff (d=5) on subtractive. - await page.evaluate(() => { - const engine = window.__nisps.activeEngine; - const idx = engine.paramMeta.findIndex(m => - m.label === 'MM_Matrix/s01_d05_cutoff'); - if (idx < 0) throw new Error('no s01_d05_cutoff cell in paramMeta'); - // paramMeta min=-1 max=1; 0.9 in norm = 0.8 raw. - engine.setParam(idx, 0.9); - }); - - const snap = await page.evaluate(() => window.__nisps.getModularState()); - expect(snap.dsp['MM_Matrix/s01_d05_cutoff']).toBeCloseTo(0.8, 4); - - // Mutate further, then restore. - await page.evaluate(() => { - const engine = window.__nisps.activeEngine; - const idx = engine.paramMeta.findIndex(m => - m.label === 'MM_Matrix/s01_d05_cutoff'); - engine.setParam(idx, 0.1); - }); - - const midSnap = await page.evaluate(() => window.__nisps.getModularState()); - expect(midSnap.dsp['MM_Matrix/s01_d05_cutoff']).not.toBeCloseTo(0.8, 4); - - await page.evaluate(async (s) => { - await window.__nisps.setModularState(s); - }, snap); - - const restored = await page.evaluate(() => window.__nisps.getModularState()); - expect(restored.dsp['MM_Matrix/s01_d05_cutoff']).toBeCloseTo(0.8, 4); - }); - - test('modular DSP state survives a page reload', async ({ page }) => { - // NOTE: don't use loadApp() because it installs an addInitScript that - // clears nisps-a-immersive on every navigation — including our reload. - // Replicate loadApp's bootstrap inline, using a localStorage sentinel - // (NOT window.__x) so the "first nav only" guard survives subsequent - // navigations on the same origin. - await page.addInitScript(() => { - if (!localStorage.getItem('__nisps-test-bootstrapped')) { - localStorage.setItem('__nisps-test-bootstrapped', '1'); - localStorage.removeItem('nisps-a-immersive'); - } - localStorage.setItem('nisps-help-seen', '1'); - }); - await page.goto('/a-immersive.html?debug=1'); - await page.waitForFunction(() => window.__nisps !== undefined, { timeout: 20_000 }); - - await switchToModular(page); - - // Set a distinctive value, save, then reload the page (localStorage - // is now preserved across the nav because __nispsTestBootstrapped is set). - await page.evaluate(() => { - const engine = window.__nisps.activeEngine; - engine._setRawByLabel('3_Filter/01_resonance', 0.73); - }); - await page.evaluate(() => window.__nisps.saveState()); - - await page.goto('/a-immersive.html?debug=1'); - await page.waitForFunction(() => window.__nisps !== undefined, { timeout: 20_000 }); - - // Engine is deferred-constructed; clicking the modular card re-instantiates - // it and the pending DSP state should be applied before setActiveEngine. - await switchToModular(page); - - const restored = await page.evaluate(() => window.__nisps.getModularState()); - expect(restored).toBeTruthy(); - expect(restored.dsp['3_Filter/01_resonance']).toBeCloseTo(0.73, 4); - }); - - test('preset apply: plucky bass sets the expected matrix routes', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - - const ok = await page.evaluate(async () => { - return await window.__nisps.applyModularPreset('modular-plucky-bass'); - }); - expect(ok).toBe(true); - - const snap = await page.evaluate(() => window.__nisps.getModularState()); - expect(snap.subEngine).toBe('subtractive'); - // ADSR2 fast decay - expect(snap.dsp['MM_ADSR/01_adsr02_decay']).toBeCloseTo(0.15, 4); - // Matrix: ADSR2 (s01) → cutoff (d05) at raw 0.8 - expect(snap.dsp['MM_Matrix/s01_d05_cutoff']).toBeCloseTo(0.8, 4); - // Filter cutoff moved to 400 Hz - expect(snap.dsp['3_Filter/00_cutoff']).toBeCloseTo(400, 2); - }); - - test('preset apply: DX bell swaps to fm sub-engine', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - - await page.evaluate(async () => { - await window.__nisps.applyModularPreset('modular-dx-bell'); - }); - const snap = await page.evaluate(() => window.__nisps.getModularState()); - expect(snap.subEngine).toBe('fm'); - expect(snap.dsp['MM_Matrix/s02_d03_op3_level']).toBeCloseTo(1.0, 4); - - // paramCount should still be 512 after the cross-engine swap. - const count = await page.evaluate(() => window.__nisps.paramCount); - expect(count).toBe(512); - }); - - test('initial outputs are in [0,1] after modular swap', async ({ page }) => { - await loadApp(page); - await switchToModular(page); - - // Set inputs so the MLP runs a forward pass. - await page.evaluate(() => window.__nisps.setInputs(0.3, 0.7)); - const outputs = await page.evaluate(() => window.__nisps.getOutputs()); - expect(outputs.length).toBe(512); - for (const v of outputs) { - expect(v).toBeGreaterThanOrEqual(0); - expect(v).toBeLessThanOrEqual(1); - } - }); -}); diff --git a/tests/e2e/persistence.spec.js b/tests/e2e/persistence.spec.js deleted file mode 100644 index 571a703..0000000 --- a/tests/e2e/persistence.spec.js +++ /dev/null @@ -1,118 +0,0 @@ -/** - * State persistence tests — localStorage round-trip and URL param application. - */ -const { test, expect } = require('@playwright/test'); -const { loadApp, statusText } = require('./helpers'); - -const STORAGE_KEY = 'nisps-a-immersive'; - -test.describe('State persistence', () => { - test('fresh load with no localStorage starts untrained', async ({ page }) => { - await loadApp(page); // helpers.js clears localStorage before load - const text = await statusText(page); - expect(text).toContain('0 examples'); - expect(text).toContain('untrained'); - }); - - test('URL ?preset=beginner-1 applies preset on load', async ({ page }) => { - await loadApp(page, '&preset=beginner-1'); - const selected = await page.locator('#synth-preset-select').inputValue(); - expect(selected).toBe('beginner-1'); - }); - - test('URL ?preset=advanced-2 applies advanced preset', async ({ page }) => { - await loadApp(page, '&preset=advanced-2'); - const selected = await page.locator('#synth-preset-select').inputValue(); - expect(selected).toBe('advanced-2'); - }); - - test('URL ?spread=0 is accepted without crash', async ({ page }) => { - await loadApp(page, '&spread=0'); - // App should be functional — probe must still exist - const probe = await page.evaluate(() => typeof window.__nisps); - expect(probe).toBe('object'); - }); - - test('URL ?spread=1 is accepted without crash', async ({ page }) => { - await loadApp(page, '&spread=1'); - const probe = await page.evaluate(() => typeof window.__nisps); - expect(probe).toBe('object'); - }); - - test('examples + weights persist across reload via localStorage', async ({ page }) => { - // Navigate fresh — no init script so localStorage is untouched between loads. - // Suppress the help overlay by pre-setting nisps-help-seen via evaluate. - await page.goto(`/a-immersive.html?debug=1`); - await page.evaluate(() => localStorage.removeItem('nisps-a-immersive')); - await page.evaluate(() => localStorage.setItem('nisps-help-seen', '1')); - await page.waitForFunction(() => window.__nisps !== undefined, { timeout: 20_000 }); - - // Write 2 examples directly to localStorage in the app's save format. - await page.evaluate(([key]) => { - const state = { - features: [[0.1, 0.9], [0.9, 0.1]], - labels: [new Array(126).fill(0.2), new Array(126).fill(0.8)], - handFeatures: [], - handLabels: [], - noiseLevel: 0.05, - outputMode: 'visual', - inputMode: 'joystick', - joyX: 0.5, joyY: 0.5, - groupOverrides: null, - visualOverrides: null, - midiCCOverrides: null, - audioCanvasState: null, - synthPresetId: null, - }; - localStorage.setItem(key, JSON.stringify(state)); - }, [STORAGE_KEY]); - - // Reload — no addInitScript registered, so localStorage is preserved. - await page.reload(); - await page.waitForFunction(() => window.__nisps !== undefined, { timeout: 20_000 }); - - // loadState() runs sync training; wait for status to show a loss value. - await page.waitForFunction( - () => document.getElementById('status-text').textContent.includes('loss'), - { timeout: 15_000 } - ); - - const count = await page.evaluate(() => window.__nisps.getExampleCount()); - expect(count).toBe(2); - const text = await statusText(page); - expect(text).toContain('2 examples'); - expect(text).toContain('loss'); - }); - - test('saveState probe writes valid JSON to localStorage', async ({ page }) => { - await loadApp(page); - await page.evaluate(() => window.__nisps.saveState()); - - const raw = await page.evaluate(([key]) => localStorage.getItem(key), [STORAGE_KEY]); - expect(raw).not.toBeNull(); - - const state = JSON.parse(raw); - expect(Array.isArray(state.features)).toBe(true); - expect(Array.isArray(state.labels)).toBe(true); - expect(typeof state.outputMode).toBe('string'); - expect(typeof state.noiseLevel).toBe('number'); - }); - - test('localStorage state is overwritten on next saveState call', async ({ page }) => { - await loadApp(page); - await page.evaluate(() => window.__nisps.saveState()); - - const before = await page.evaluate(([key]) => localStorage.getItem(key), [STORAGE_KEY]); - - // Add an example and save again - await page.evaluate(() => { - window.__nisps.iml.addExample([0.3, 0.7], new Array(126).fill(0.5)); - window.__nisps.saveState(); - }); - - const after = await page.evaluate(([key]) => localStorage.getItem(key), [STORAGE_KEY]); - // State changed — the feature array should now include our example - const parsed = JSON.parse(after); - expect(parsed.features.length).toBeGreaterThan(0); - }); -}); diff --git a/tests/e2e/ui-interactions.spec.js b/tests/e2e/ui-interactions.spec.js deleted file mode 100644 index b928139..0000000 --- a/tests/e2e/ui-interactions.spec.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * UI state machine tests — drawers, mode switching, preset chips, keyboard shortcuts. - */ -const { test, expect } = require('@playwright/test'); -const { loadApp, statusText } = require('./helpers'); - -test.describe('UI interactions', () => { - test.describe('Dock drawers', () => { - test('Train dock button opens training drawer', async ({ page }) => { - await loadApp(page); - await expect(page.locator('#drawer-training')).toHaveClass(/hidden/); - await page.click('[data-drawer="training"]'); - await expect(page.locator('#drawer-training')).not.toHaveClass(/hidden/); - }); - - test('Drawer close button hides the drawer', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="training"]'); - await expect(page.locator('#drawer-training')).not.toHaveClass(/hidden/); - await page.click('#drawer-training .drawer-close'); - await expect(page.locator('#drawer-training')).toHaveClass(/hidden/); - }); - - test('Mode dock button opens mode drawer', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="mode"]'); - await expect(page.locator('#drawer-mode')).not.toHaveClass(/hidden/); - }); - - test('Engine dock button opens engine drawer', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="params"]'); - await expect(page.locator('#drawer-params')).not.toHaveClass(/hidden/); - }); - - test('multiple drawers can be open simultaneously', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="training"]'); - await page.click('[data-drawer="mode"]'); - // The app doesn't auto-close drawers — both can be open - await expect(page.locator('#drawer-training')).not.toHaveClass(/hidden/); - await expect(page.locator('#drawer-mode')).not.toHaveClass(/hidden/); - }); - }); - - test.describe('Output mode switching', () => { - test('default mode is visual — synth quick controls hidden', async ({ page }) => { - await loadApp(page); - await expect(page.locator('#synth-quick-controls')).toHaveClass(/hidden/); - await expect(page.locator('#midi-cc-quick-controls')).toHaveClass(/hidden/); - }); - - test('switching to Synth shows synth quick controls', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await expect(page.locator('#synth-quick-controls')).not.toHaveClass(/hidden/); - }); - - test('switching to MIDI CC shows midi-cc quick controls', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="midi-cc"]'); - await expect(page.locator('#midi-cc-quick-controls')).not.toHaveClass(/hidden/); - }); - - test('switching back to Visual hides synth controls', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - await page.click('[data-mode="visual"]'); - await expect(page.locator('#synth-quick-controls')).toHaveClass(/hidden/); - }); - - test('heatmap shows 20 bars in visual mode', async ({ page }) => { - await loadApp(page); - const count = await page.locator('.heatmap-cell').count(); - expect(count).toBe(20); - }); - - test('heatmap shows 126 bars in synth mode', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="mode"]'); - await page.click('[data-mode="synth"]'); - const count = await page.locator('.heatmap-cell').count(); - expect(count).toBe(126); - }); - }); - - test.describe('Status line', () => { - test('shows 0 examples · untrained on fresh load', async ({ page }) => { - await loadApp(page); - const text = await statusText(page); - expect(text).toContain('0 examples'); - expect(text).toContain('untrained'); - }); - }); - - test.describe('Training controls', () => { - test('Randomize button changes heatmap bar widths', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="training"]'); - - const widthsBefore = await page.evaluate(() => - Array.from(document.querySelectorAll('.heatmap-cell-bar')).map(el => el.style.width) - ); - await page.click('#btn-randomize'); - const widthsAfter = await page.evaluate(() => - Array.from(document.querySelectorAll('.heatmap-cell-bar')).map(el => el.style.width) - ); - - const anyChanged = widthsBefore.some((w, i) => w !== widthsAfter[i]); - expect(anyChanged).toBe(true); - }); - - test('preset chip loads examples and trains', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="training"]'); - await page.click('[data-preset="calm-to-chaotic"]'); - - // calm-to-chaotic has 3 examples; training is async - await page.waitForFunction( - () => document.getElementById('status-text').textContent.includes('loss'), - { timeout: 15_000 } - ); - const text = await statusText(page); - expect(text).toContain('3 examples'); - expect(text).toContain('loss'); - }); - - test('Clear Ex resets example count to 0', async ({ page }) => { - await loadApp(page); - await page.click('[data-drawer="training"]'); - await page.click('[data-preset="calm-to-chaotic"]'); - await page.waitForFunction( - () => document.getElementById('status-text').textContent.includes('loss'), - { timeout: 15_000 } - ); - - await page.click('#btn-clear-examples'); - const text = await statusText(page); - expect(text).toContain('0 examples'); - }); - }); - - test.describe('Keyboard shortcuts', () => { - test('key 2 triggers thumbs-up (adds example)', async ({ page }) => { - await loadApp(page); - const before = await page.evaluate(() => window.__nisps.getExampleCount()); - await page.keyboard.press('2'); - await page.waitForTimeout(200); - const after = await page.evaluate(() => window.__nisps.getExampleCount()); - expect(after).toBe(before + 1); - }); - - test('key 1 triggers thumbs-down (changes outputs)', async ({ page }) => { - await loadApp(page); - const before = await page.evaluate(() => window.__nisps.getOutputs()); - await page.keyboard.press('1'); - const after = await page.evaluate(() => window.__nisps.getOutputs()); - const anyChanged = before.some((v, i) => Math.abs(v - after[i]) > 0.0001); - expect(anyChanged).toBe(true); - }); - - test('key Z triggers undo after thumbs-down', async ({ page }) => { - await loadApp(page); - const before = await page.evaluate(() => window.__nisps.getWeights()); - await page.keyboard.press('1'); // thumbs-down - await page.keyboard.press('z'); // undo - const after = await page.evaluate(() => window.__nisps.getWeights()); - // Weights should be restored (approximately) - const maxDiff = before.reduce((m, v, i) => Math.max(m, Math.abs(v - after[i])), 0); - expect(maxDiff).toBeLessThan(1e-4); - }); - }); -}); diff --git a/tests/e2e/wasm-api.spec.js b/tests/e2e/wasm-api.spec.js deleted file mode 100644 index b110ee8..0000000 --- a/tests/e2e/wasm-api.spec.js +++ /dev/null @@ -1,231 +0,0 @@ -/** - * WASM API tests — verify new WasmIML methods: - * - inferBatch: batch inference across multiple input points - * - evalLoss: MSE loss evaluation without training - * - getLayerStats: per-layer weight statistics - * - lossHistory: full per-iteration loss curve after training - * - moveWeights with pin mask: pinned outputs stay unchanged - */ -const { test, expect } = require('@playwright/test'); -const { loadApp } = require('./helpers'); - -const EXAMPLE_LOW = { input: [0.1, 0.9], output: new Array(126).fill(0.1) }; -const EXAMPLE_HIGH = { input: [0.9, 0.1], output: new Array(126).fill(0.9) }; - -test.describe('inferBatch', () => { - test('returns correct count of output arrays, each length 126', async ({ page }) => { - await loadApp(page); - const result = await page.evaluate(() => { - const outputs = window.__nisps.iml.inferBatch([[0.2, 0.8], [0.5, 0.5], [0.9, 0.1]]); - return outputs.map(o => Array.from(o)); - }); - expect(result).toHaveLength(3); - for (const arr of result) { - expect(arr).toHaveLength(126); - } - }); - - test('matches individual inference within 1e-5', async ({ page }) => { - await loadApp(page); - const points = [[0.1, 0.2], [0.3, 0.7], [0.5, 0.5], [0.8, 0.1], [0.0, 1.0]]; - const { batchResults, individualResults } = await page.evaluate((pts) => { - const iml = window.__nisps.iml; - const batch = iml.inferBatch(pts).map(o => Array.from(o)); - const individual = pts.map(([x, y]) => { - iml.setInput(0, x); - iml.setInput(1, y); - iml.process(); - return Array.from(iml.getOutputs()); - }); - return { batchResults: batch, individualResults: individual }; - }, points); - - expect(batchResults).toHaveLength(5); - for (let i = 0; i < 5; i++) { - for (let j = 0; j < 126; j++) { - expect(Math.abs(batchResults[i][j] - individualResults[i][j])).toBeLessThan(1e-5); - } - } - }); - - test('all batch outputs are in [0, 1]', async ({ page }) => { - await loadApp(page); - const corners = [ - [0, 0], [0, 1], [1, 0], [1, 1], - [0.5, 0], [0.5, 1], [0, 0.5], [1, 0.5], - [0.25, 0.75], [0.75, 0.25], - ]; - const results = await page.evaluate((pts) => { - return window.__nisps.iml.inferBatch(pts).map(o => Array.from(o)); - }, corners); - - expect(results).toHaveLength(10); - for (const arr of results) { - for (const v of arr) { - expect(v).toBeGreaterThanOrEqual(0); - expect(v).toBeLessThanOrEqual(1); - } - } - }); -}); - -test.describe('evalLoss', () => { - test('returns null when no examples exist', async ({ page }) => { - await loadApp(page); - const loss = await page.evaluate(() => window.__nisps.iml.evalLoss()); - expect(loss).toBeNull(); - }); - - test('returns finite non-negative value when examples exist', async ({ page }) => { - await loadApp(page); - await page.evaluate(([low, high]) => { - window.__nisps.iml.addExample(low.input, low.output); - window.__nisps.iml.addExample(high.input, high.output); - }, [EXAMPLE_LOW, EXAMPLE_HIGH]); - - const loss = await page.evaluate(() => window.__nisps.iml.evalLoss()); - expect(typeof loss).toBe('number'); - expect(isFinite(loss)).toBe(true); - expect(loss).toBeGreaterThanOrEqual(0); - }); - - test('does not change weights', async ({ page }) => { - await loadApp(page); - await page.evaluate(([low, high]) => { - window.__nisps.iml.addExample(low.input, low.output); - window.__nisps.iml.addExample(high.input, high.output); - }, [EXAMPLE_LOW, EXAMPLE_HIGH]); - - const { before, after } = await page.evaluate(() => { - const weightsBefore = window.__nisps.getWeights(); - window.__nisps.iml.evalLoss(); - const weightsAfter = window.__nisps.getWeights(); - return { before: Array.from(weightsBefore), after: Array.from(weightsAfter) }; - }); - - expect(before).toEqual(after); - }); -}); - -test.describe('getLayerStats', () => { - test('returns 4 layers for [3, 32, 48, 64, 126] architecture', async ({ page }) => { - await loadApp(page); - const stats = await page.evaluate(() => window.__nisps.iml.getLayerStats()); - expect(stats).toHaveLength(4); - }); - - test('each layer has all 4 stat fields with valid ranges', async ({ page }) => { - await loadApp(page); - const stats = await page.evaluate(() => window.__nisps.iml.getLayerStats()); - for (const layer of stats) { - expect(typeof layer.meanAbs).toBe('number'); - expect(typeof layer.maxAbs).toBe('number'); - expect(typeof layer.deadFrac).toBe('number'); - expect(typeof layer.satFrac).toBe('number'); - - expect(layer.meanAbs).toBeGreaterThanOrEqual(0); - expect(layer.maxAbs).toBeGreaterThanOrEqual(0); - expect(layer.deadFrac).toBeGreaterThanOrEqual(0); - expect(layer.deadFrac).toBeLessThanOrEqual(1); - expect(layer.satFrac).toBeGreaterThanOrEqual(0); - expect(layer.satFrac).toBeLessThanOrEqual(1); - } - }); -}); - -test.describe('lossHistory', () => { - test('training populates lossHistory with multiple entries', async ({ page }) => { - await loadApp(page); - await page.evaluate(([low, high]) => { - window.__nisps.iml.addExample(low.input, low.output); - window.__nisps.iml.addExample(high.input, high.output); - }, [EXAMPLE_LOW, EXAMPLE_HIGH]); - - const histLen = await page.evaluate(() => { - window.__nisps.train(); - return window.__nisps.iml.lossHistory.length; - }); - expect(histLen).toBeGreaterThan(1); - }); - - test('all loss history entries are finite non-negative', async ({ page }) => { - await loadApp(page); - await page.evaluate(([low, high]) => { - window.__nisps.iml.addExample(low.input, low.output); - window.__nisps.iml.addExample(high.input, high.output); - }, [EXAMPLE_LOW, EXAMPLE_HIGH]); - - const history = await page.evaluate(() => { - window.__nisps.train(); - return [...window.__nisps.iml.lossHistory]; - }); - - expect(history.length).toBeGreaterThan(0); - for (const v of history) { - expect(typeof v).toBe('number'); - expect(isFinite(v)).toBe(true); - expect(v).toBeGreaterThanOrEqual(0); - } - }); -}); - -test.describe('moveWeights with pin mask', () => { - test('pinned output-layer weights unchanged, unpinned weights changed', async ({ page }) => { - await loadApp(page); - // The pin mask protects output-layer weights for pinned nodes. - // Hidden layer weights still change (shared), so we compare flat weight - // vectors and check that the output-layer segment for pinned nodes is - // identical while unpinned weights differ. - const { pinnedWeightsMatch, anyUnpinnedWeightChanged } = await page.evaluate(() => { - const iml = window.__nisps.iml; - const weightsBefore = Array.from(window.__nisps.getWeights()); - - // Pin first 10 outputs - const pinMask = new Uint8Array(126); - for (let i = 0; i < 10; i++) pinMask[i] = 1; - - // Apply noise (spread=0 means no decay, just additive noise) - iml.moveWeights(0.3, 0, pinMask); - - const weightsAfter = Array.from(window.__nisps.getWeights()); - - // The architecture is [3, 32, 48, 64, 126]. - // Output layer: 126 nodes, each with 64+1=65 weights (64 inputs + bias). - // The output layer weights are at the end of the flat array. - const outputLayerWeights = 126 * 65; // 8190 - const outputLayerStart = weightsBefore.length - outputLayerWeights; - const weightsPerNode = 65; - - // Check pinned nodes (first 10) have identical weights - let pinnedAllMatch = true; - for (let n = 0; n < 10; n++) { - const nodeStart = outputLayerStart + n * weightsPerNode; - for (let w = 0; w < weightsPerNode; w++) { - if (weightsBefore[nodeStart + w] !== weightsAfter[nodeStart + w]) { - pinnedAllMatch = false; - break; - } - } - if (!pinnedAllMatch) break; - } - - // Check unpinned output nodes (10-125) have at least some changed weights - let unpinnedChanged = false; - for (let n = 10; n < 126; n++) { - const nodeStart = outputLayerStart + n * weightsPerNode; - for (let w = 0; w < weightsPerNode; w++) { - if (weightsBefore[nodeStart + w] !== weightsAfter[nodeStart + w]) { - unpinnedChanged = true; - break; - } - } - if (unpinnedChanged) break; - } - - return { pinnedWeightsMatch: pinnedAllMatch, anyUnpinnedWeightChanged: unpinnedChanged }; - }); - - expect(pinnedWeightsMatch).toBe(true); - expect(anyUnpinnedWeightChanged).toBe(true); - }); -});