📼 revelation: TESTAMENT — record what god does (Stage 1T)
A MediaRecorder tapped off N.lim (the true final mix — post-squash, post-limiter). Lazy dest+recorder built on first roll, 1s timeslices. The #testament ⏺ button beside ♪ turns red and counts m:ss while rolling; on stop it downloads godstrument-testament-<stamp>.<ext> and tickers "it is written". A ctxItem mirrors it near GODSONIQ. testamentStart/Stop/State exposed on the Synth return. Verified: button/timer/ticker/download all fire, blob is valid opus decoding to the right duration, and an oscillator probe proved the record→decode pipeline captures real audio (RMS 0.35). The Synth take is silent only because the headless preview suspends its AudioContext — the tap is correct; on a real machine it records what sounds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e6c464daae
commit
6cb355e6b0
@ -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; },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user