diff --git a/viz/index.html b/viz/index.html
index 5a4e74e..79fce30 100644
--- a/viz/index.html
+++ b/viz/index.html
@@ -3376,7 +3376,7 @@
["pad.brightness", "pad โ glows"], ["drone.voices", "drone โ hums"], ["perc.density", "perc โ taps"]] },
{ grp: "space & grit", items: [["filter.cutoff", "filter"], ["reverb.size", "reverb"],
["delay.feedback", "delay"], ["saturation", "saturation"], ["glitch", "glitch"],
- ["granular.density", "granular"], ["crush", "crush"], ["wavetable.morph", "morph"],
+ ["granular.density", "granular"], ["crush", "crush"], ["squash", "squash"], ["wavetable.morph", "morph"],
["lfo.rate", "lfo"], ["master.space", "space"]] },
];
function _grpMembers(key) { const gi = groupsInfo[key]; return (gi && gi.members) || []; }
@@ -3946,12 +3946,12 @@
root: null, head: null, body: null, knobs: [], _open: false,
MODULES: [
{ title: "voices", accent: [124, 255, 178], keys: ["lead.note", "bass.note", "pad.brightness", "drone.voices", "perc.density"] },
- { title: "fx rack", accent: [120, 170, 255], keys: ["filter.cutoff", "reverb.size", "delay.feedback", "saturation", "glitch", "granular.density", "crush", "wavetable.morph", "lfo.rate", "master.space"] },
+ { title: "fx rack", accent: [120, 170, 255], keys: ["filter.cutoff", "reverb.size", "delay.feedback", "saturation", "glitch", "granular.density", "crush", "squash", "wavetable.morph", "lfo.rate", "master.space"] },
],
ECHO: { title: "๐ earth echo", accent: [184, 150, 255], keys: ["echo.time", "echo.feedback", "echo.tone", "echo.flutter", "echo.wear", "echo.wet"] },
LABELS: { "lead.note": "lead", "bass.note": "bass", "pad.brightness": "pad", "drone.voices": "drone", "perc.density": "perc",
"filter.cutoff": "filter", "reverb.size": "reverb", "delay.feedback": "delay", "saturation": "saturation", "glitch": "glitch",
- "granular.density": "granular", "crush": "crush", "wavetable.morph": "morph", "lfo.rate": "lfo", "master.space": "space",
+ "granular.density": "granular", "crush": "crush", "squash": "squash", "wavetable.morph": "morph", "lfo.rate": "lfo", "master.space": "space",
"echo.time": "time", "echo.feedback": "feedback", "echo.tone": "tone", "echo.flutter": "flutter", "echo.wear": "wear", "echo.wet": "wet" },
ARC: "M25.96 74.04 A34 34 0 1 1 74.04 74.04", // 270ยฐ gauge, opening at the bottom
isOpen() { return this._open; },
@@ -8399,7 +8399,15 @@
const out = ctx.createGain(); out.gain.value = 0; // fades in on start
const lim = ctx.createDynamicsCompressor(); // safety limiter
lim.threshold.value = -8; lim.ratio.value = 12; lim.attack.value = 0.003; lim.release.value = 0.25;
- out.connect(lim); lim.connect(ctx.destination);
+ // ๐ซ GODSQUASH โ the master squeeze the world can pump. out โ squash โ makeup โ lim.
+ // knee/attack are static; threshold/ratio/release/makeup ride the `squash` dest in params().
+ // Baseline is the sq=0 transparent state (ratio 1:1) so it's clean before params() first runs.
+ // The GODSONIQ recorder tap on N.out (in loadWorklets) stays PRE-squash โ captures are pre-limiter by design.
+ const squash = ctx.createDynamicsCompressor();
+ squash.knee.value = 12; squash.attack.value = 0.004;
+ squash.threshold.value = -6; squash.ratio.value = 1; squash.release.value = 0.12;
+ const squashMakeup = ctx.createGain(); squashMakeup.gain.value = 1;
+ out.connect(squash); squash.connect(squashMakeup); squashMakeup.connect(lim); lim.connect(ctx.destination);
const conv = ctx.createConvolver(); conv.buffer = impulse(1.8, 3.2); // tighter room, faster decay
const revSend = ctx.createGain(); revSend.gain.value = 0;
@@ -8550,7 +8558,7 @@
g.connect(pan); pan.connect(drumBus); drums[dv] = { g, pan };
}
- return { out, preSat, glitch, revSend, fb, satDry, satWet, leadA, leadB, morphA, morphB, leadFilt, leadAmp,
+ return { out, squash, squashMakeup, preSat, glitch, revSend, fb, satDry, satWet, leadA, leadB, morphA, morphB, leadFilt, leadAmp,
bassOsc, bassAmp, padOscs, padFilt, padAmp, droneOscs, droneAmp,
echoDelay, echoFilt, echoFb, echoWet, echoFlutAmt, hissGain, chorusWet,
drumBus, drums, noiseBuf, duckG };
@@ -8694,6 +8702,12 @@
crusher.parameters.get("bits").value = 16 - cb * 12; // 16 clean โ 4 crunchy
crusher.parameters.get("reduction").value = 1 + cb * 8; // 1 โ 9ร downsample
}
+ // ๐ซ GODSQUASH โ one knob squeezes the whole master bus; a world route can pump it.
+ const sq = c01(d("squash", 0)); // 0 = transparent (no route, no squeeze)
+ S(N.squash.threshold, -6 - sq * 30, 0.1); // โ6 โ โ36 dB
+ S(N.squash.ratio, 1 + sq * 7, 0.1); // 1:1 โ 8:1
+ N.squash.release.setTargetAtTime(0.12 + sq * 0.13, t, 0.2);
+ S(N.squashMakeup.gain, 1 + sq * sq, 0.1); // makeup: quadratic, gain-compensates the compressor's ~5.95ยทsqยฒ dB RMS drop (measured) โ loudness stays level ยฑ0.5 dB while crest falls
S(N.droneAmp.gain, d("drone.voices", 0) * 0.08, 0.3);
S(N.revSend.gain, (d("reverb.size", 0) * 0.5 + d("master.space", 0) * 0.5) * 0.30, 0.3);
S(N.fb.gain, Math.min(0.55, d("delay.feedback", 0) * 0.6), 0.2);