diff --git a/viz/index.html b/viz/index.html index 371522c..f16ee20 100644 --- a/viz/index.html +++ b/viz/index.html @@ -2448,6 +2448,10 @@ return v; } + // Synth-side idle for the two dests whose "no value" default isn't 0 (see the + // d("filter.cutoff",0.3) / d("tempo.nudge",0.5) fallbacks). The cableless-tweak + // floor seeds THIS, so a neutral/reset knob returns a voice to its idle, not to 0. + const DEST_IDLE = { "filter.cutoff": 0.3, "tempo.nudge": 0.5 }; let clientMatrix = false; // true once the house patch has been cloned in function computeDests() { if (!clientMatrix) return; @@ -2491,8 +2495,8 @@ for (const k in destVals) destVals[k] = clamp(destVals[k], 0, 1); for (const k in notes) destVals[k] = notes[k]; for (const [k, n] of dests) { // any hand-tweaked cableless voice: emit its ACTUAL value — - if (n.isNote || (k in destVals)) continue; // an offset floor gives it a level from nothing, and a - if (tweaks[k]) destVals[k] = 0; // reset/mute/lower writes 0 too (else the last value sticks in the synth) + if (n.isNote || (k in destVals)) continue; // an offset floor gives it a level from its idle, and a + if (tweaks[k]) destVals[k] = DEST_IDLE[k] || 0; // reset/mute/lower re-emit too — seed the synth idle (not a blank 0), so a neutral filter/tempo doesn't pin dark } for (const k in destVals) { const tk = tweaks[k]; @@ -3500,7 +3504,7 @@ const val = wrap.querySelector(".val"), dot = wrap.querySelector(".dot"); if (!isNote) { let dragging = false, sy = 0, so = 0; - wrap.addEventListener("pointerdown", (e) => { if (e.button !== 0) return; dragging = true; sy = e.clientY; so = (tw(key).offset || 0); try { wrap.setPointerCapture(e.pointerId); } catch (x) {} e.preventDefault(); }); + wrap.addEventListener("pointerdown", (e) => { if (e.button !== 0) return; dragging = true; sy = e.clientY; so = ((tweaks[key] && tweaks[key].offset) || 0); try { wrap.setPointerCapture(e.pointerId); } catch (x) {} e.preventDefault(); }); wrap.addEventListener("pointermove", (e) => { if (!dragging) return; setParamLocal(key, "offset", clamp(so + (sy - e.clientY) / 150, -1, 1)); }); const end = (e) => { dragging = false; try { wrap.releasePointerCapture(e.pointerId); } catch (x) {} if (zeroMode) zeroUI.persist(); }; // a knob shape is part of the zero build wrap.addEventListener("pointerup", end); wrap.addEventListener("pointercancel", end); @@ -3519,7 +3523,7 @@ const frac = n ? clamp(n.normDisp != null ? n.normDisp : (n.norm || 0), 0, 1) : 0; k.val.style.strokeDashoffset = (1000 * (1 - frac)).toFixed(1); if (!k.isNote) { - const off = tw(k.key).offset || 0; + const off = (tweaks[k.key] && tweaks[k.key].offset) || 0; // read-only — don't create a neutral tweak just by rendering const a = (135 + ((off + 1) / 2) * 270) * Math.PI / 180; k.dot.setAttribute("cx", (50 + 34 * Math.cos(a)).toFixed(1)); k.dot.setAttribute("cy", (50 + 34 * Math.sin(a)).toFixed(1));