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));