From ea13b2efd67bd424fe917f74e4ca56116f3b0707 Mon Sep 17 00:00:00 2001 From: monsterrobotparty Date: Mon, 13 Jul 2026 13:36:03 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=AB=80=20godsquash:=20master-bus=20compre?= =?UTF-8?q?ssor=20the=20world=20can=20pump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage 1β. A DynamicsCompressor + makeup between the master fade and the safety limiter, driven by one normalized `squash` dest. - build(): out → squash → makeup → lim (knee 12, attack 0.004; baseline is the sq=0 transparent state so it's clean before params() first runs). The GODSONIQ N.out tap stays pre-squash. squash/squashMakeup returned on N. - params(): sq=c01(d("squash",0)); threshold −6→−36, ratio 1:1→8:1, release 0.12→0.25; makeup = 1 + sq·sq — a QUADRATIC curve tuned by offline measurement (the compressor's RMS drop is ~5.95·sq² dB, so 1+sq² holds loudness level ±0.5 dB while crest factor falls monotonically). - Routable exactly like crush: added to OMNI.MODULES fx-rack keys, OMNI.LABELS, and ZERO_RACK — nothing else outside the IIFE. Co-Authored-By: Claude Opus 4.8 --- viz/index.html | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) 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);