diff --git a/viz/index.html b/viz/index.html
index 13770d9..c5e3992 100644
--- a/viz/index.html
+++ b/viz/index.html
@@ -378,6 +378,10 @@
#godspeakrec.on { display: flex; animation: gspk-pulse 1.1s ease-in-out infinite; }
@keyframes gspk-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
+ /* πΌ TESTAMENT β the record button; red + gently pulsing while it bears witness */
+ #testament.on { color: #ff8a8a !important; border-color: rgba(240,110,110,0.6) !important;
+ background: rgba(230,90,90,0.2) !important; animation: gspk-pulse 1.5s ease-in-out infinite; font-variant-numeric: tabular-nums; }
+
/* right-click context menu β every setting, on everything */
#ctxmenu {
position: fixed; z-index: 30; display: none; width: 240px; max-height: 72vh;
@@ -7797,6 +7801,8 @@
eventTicker.unshift({ text: ok ? "π GODSONIQ β sampling the world for 3sβ¦ then every pluck replays it, pitched across the keys (πΈ string voice to exit)"
: "π GODSONIQ needs the audio worklet β press βͺ first, then try again", tAdded: now(), src: "sys" }); });
ctxItem("πΎ AMPLER β there is ample (sample the world into the kit)", () => openAmpler());
+ ctxItem((Synth.testamentState && Synth.testamentState().on) ? "πΌ TESTAMENT β stop & save the take" : "πΌ TESTAMENT β record what god does",
+ () => { if (Synth.testament) Synth.testament(); });
ctxItem("β‘ GODSPEED β hold a chord, the machine runs", () => openGodspeed());
ctxItem("π " + (Synth.feeding() ? "tab feed: live β click to stop" : "feed a tab (YouTube, a streamβ¦) into the world"),
async () => { const r = await Synth.feedTab();
@@ -9202,7 +9208,7 @@
g.connect(pan); pan.connect(drumBus); drums[dv] = { g, pan };
}
- return { out, squash, squashMakeup, preSat, glitch, revSend, fb, satDry, satWet, leadA, leadB, morphA, morphB, leadFilt, leadAmp,
+ return { out, squash, squashMakeup, lim, 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 };
@@ -9626,7 +9632,69 @@
}
});
+ // ---- πΌ TESTAMENT β record what god does ("it is written") --------------
+ // A MediaRecorder tapped off N.lim: the TRUE final mix, post-squash and
+ // post-limiter β exactly what you hear. Lazy (destination + recorder built
+ // on first roll), timesliced so a crash loses β€1s. The GODSONIQ worklet
+ // Recorder is a different machine for a different purpose β untouched.
+ let testDest = null, testRec = null, testChunks = [], testT0 = 0, testUiTimer = null;
+ function testamentStart() {
+ if (!ctx) start(); // wake the graph β a recording can begin the session
+ if (testRec) return false; // already rolling
+ if (!testDest) { testDest = ctx.createMediaStreamDestination(); N.lim.connect(testDest); }
+ const mime = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"]
+ .find(m => window.MediaRecorder && MediaRecorder.isTypeSupported(m));
+ if (!mime) return false; // no MediaRecorder / no supported container (old Safari)
+ testChunks = []; testRec = new MediaRecorder(testDest.stream, { mimeType: mime, audioBitsPerSecond: 256000 });
+ testRec.ondataavailable = (e) => { if (e.data && e.data.size) testChunks.push(e.data); };
+ testRec.start(1000); // 1s timeslices
+ testT0 = ctx.currentTime; return true;
+ }
+ function testamentStop() {
+ return new Promise((resolve) => {
+ if (!testRec) { resolve(null); return; }
+ const rec = testRec, secs = ctx.currentTime - testT0;
+ const ext = rec.mimeType.indexOf("mp4") >= 0 ? "m4a" : "webm";
+ rec.onstop = () => { const blob = new Blob(testChunks, { type: rec.mimeType }); testChunks = []; resolve({ blob, secs, ext }); };
+ testRec = null; rec.stop();
+ });
+ }
+ function testamentState() { return { on: !!testRec, secs: testRec ? ctx.currentTime - testT0 : 0 }; }
+
+ const testBtn = document.createElement("div");
+ testBtn.id = "testament"; testBtn.textContent = "βΊ"; testBtn.title = "TESTAMENT β record the master mix to a file";
+ Object.assign(testBtn.style, { position: "fixed", top: "12px", right: "132px", zIndex: 10,
+ height: "30px", minWidth: "30px", padding: "0 7px", borderRadius: "8px", display: "flex",
+ alignItems: "center", justifyContent: "center", background: "rgba(20,28,44,0.7)", color: "#cba6a6",
+ cursor: "pointer", fontSize: "13px", border: "1px solid rgba(120,150,200,0.25)",
+ backdropFilter: "blur(6px)", WebkitBackdropFilter: "blur(6px)" });
+ if (!AC || !window.MediaRecorder) { testBtn.style.opacity = "0.3"; testBtn.title = "this browser cannot record"; }
+ document.body.appendChild(testBtn);
+ const testFmt = (s) => { s = Math.max(0, Math.floor(s)); return Math.floor(s / 60) + ":" + String(s % 60).padStart(2, "0"); };
+ function testPaint() { const st = testamentState(); testBtn.classList.toggle("on", st.on); testBtn.textContent = st.on ? "βΊ " + testFmt(st.secs) : "βΊ"; }
+ function testamentToggle() {
+ if (testamentState().on) {
+ testamentStop().then((r) => {
+ if (testUiTimer) { clearInterval(testUiTimer); testUiTimer = null; }
+ testPaint();
+ if (!r || !r.blob || !r.blob.size) return;
+ const d = new Date(), pad = (n) => String(n).padStart(2, "0");
+ const stamp = "" + d.getFullYear() + pad(d.getMonth() + 1) + pad(d.getDate()) + "-" + pad(d.getHours()) + pad(d.getMinutes());
+ const a = document.createElement("a"); a.href = URL.createObjectURL(r.blob);
+ a.download = "godstrument-testament-" + stamp + "." + r.ext;
+ document.body.appendChild(a); a.click(); document.body.removeChild(a);
+ setTimeout(() => URL.revokeObjectURL(a.href), 4000);
+ eventTicker.unshift({ text: "πΌ testament: " + testFmt(r.secs) + " β it is written", tAdded: now(), src: "sys" });
+ });
+ } else {
+ if (!testamentStart()) { eventTicker.unshift({ text: "πΌ this browser cannot bear witness", tAdded: now(), src: "sys" }); return; }
+ testPaint(); testUiTimer = setInterval(testPaint, 1000);
+ }
+ }
+ testBtn.addEventListener("click", testamentToggle);
+
return { update(dests) { if (dests) Object.assign(D, dests); }, pluck,
+ testamentStart, testamentStop, testamentState, testament: testamentToggle,
toggle() { on ? stop() : start(); paint(); return on; }, playing() { return on; },
stringVoice(v) { if (v === undefined) return ksMode; if (!ctx) start(); ksMode = !!v; sampleMode = false; return ksMode; },
godsoniq(secs) { return godsoniq(secs); }, sampling() { return sampleMode && !!sampleBuf; },