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