fix: robust wire connections via data attributes + chiptune demo preset

- Replace fragile DOM position matching in finishConnection with
  data-module-id/data-port-name/data-port-direction attributes
- Add nearby port detection (8px radius) for easier connections
- Wire glow effects with drop-shadow filters
- Port dots z-index above wires for reliable click targeting
- Chiptune demo preset: 2x square osc, envelopes, VCAs, mixer,
  filter, delay, distortion, scope — full 8-bit signal chain
- "Chiptune Demo" toolbar button to load the example

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-21 01:17:56 +01:00
parent 95054a70df
commit 4a003f2af2
4 changed files with 156 additions and 99 deletions

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback } from 'react';
import { getModuleDef } from '../engine/moduleRegistry.js';
import { state, removeModule, updateModuleParam, updateModulePosition, isPortConnected, emit } from '../engine/state.js';
import { state, removeModule, updateModuleParam, isPortConnected, emit } from '../engine/state.js';
import { updateParam } from '../engine/audioEngine.js';
import Knob from './Knob.jsx';
import ScopeDisplay from './ScopeDisplay.jsx';
@@ -66,7 +66,12 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }
<div
className={`module ${isSelected ? 'selected' : ''}`}
style={{ left: mod.x * zoom, top: mod.y * zoom, transform: `scale(${zoom})`, transformOrigin: 'top left' }}
onPointerDown={() => { state.selectedModuleId = mod.id; emit(); }}
data-module-id={mod.id}
onPointerDown={(e) => {
// Don't deselect when clicking inside a module
e.stopPropagation();
state.selectedModuleId = mod.id; emit();
}}
>
<div className="module-header" onPointerDown={handleHeaderDown}>
<span className="type-icon">{def.icon}</span>
@@ -81,6 +86,10 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }
<div
className={`port-dot ${port.type} ${isPortConnected(mod.id, port.name, 'input') ? 'connected' : ''}`}
ref={el => portRef(el, port.name, 'input')}
data-module-id={mod.id}
data-port-name={port.name}
data-port-direction="input"
data-port-type={port.type}
onPointerDown={e => handlePortMouseDown(e, port.name, 'input')}
/>
<span className="port-label">{port.label}</span>
@@ -139,6 +148,10 @@ export default function ModuleNode({ mod, zoom, onStartConnect, onPortPosition }
<div
className={`port-dot ${port.type} ${isPortConnected(mod.id, port.name, 'output') ? 'connected' : ''}`}
ref={el => portRef(el, port.name, 'output')}
data-module-id={mod.id}
data-port-name={port.name}
data-port-direction="output"
data-port-type={port.type}
onPointerDown={e => handlePortMouseDown(e, port.name, 'output')}
/>
<span className="port-label">{port.label}</span>