🗣 pantheon: ε the closer — the voice becomes command (Stage 3ε)

The GODSPEAK grammar: tempo/bpm with spoken numbers (one twenty → 120),
squash N percent, AMPLER capture/chop, open/close every panel, play/stop
the beat, mood, skin, zen — and take it back, the voice of god un-doing.
First match wins; unmatched words arrive in the ticker, never an error.
A 🗣 chip pulses while the mic is held. Polish: arp.up pruned, the arp
breathes with groove.velo, tempo edits mark undo, serializePatch carries
godtime.locked so undoing a mood restores the lock. Grimoire chapter for
all four gods + regenerated manual + worker permission notes.

Reviewed by fable: two fixes folded in — matchSkin normalizes candidate
names (so god-s-eye matches post-convergence) and a virgin session
saying play-the-beat gets the seed pattern first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
monsterrobotparty 2026-07-14 13:21:24 +10:00
parent b8b11888c3
commit 18fd836456
3 changed files with 185 additions and 5 deletions

View File

@ -359,6 +359,14 @@
#godspeed .held { color: #bcd; font-size: 11px; letter-spacing: 0.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#godspeed .hint { color: rgba(130,150,180,0.6); font-size: 10px; line-height: 1.5; }
/* 🗣 GODSPEAK — a small "listening" chip near the ♪ button, pulses while the mic is held */
#godspeakrec { position: fixed; top: 12px; right: 92px; z-index: 40; width: 30px; height: 30px;
border-radius: 8px; display: none; align-items: center; justify-content: center; font-size: 15px;
background: rgba(230,90,90,0.18); color: #ffd2d2; border: 1px solid rgba(240,110,110,0.5);
-webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
#godspeakrec.on { display: flex; animation: gspk-pulse 1.1s ease-in-out infinite; }
@keyframes gspk-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
/* right-click context menu — every setting, on everything */
#ctxmenu {
position: fixed; z-index: 30; display: none; width: 240px; max-height: 72vh;
@ -1166,6 +1174,10 @@
<li><b>Keys <kbd>1</kbd><kbd>5</kbd> &amp; ⏺ rec — finger-drum the kit.</b> With the beat open the number row <i>is</i> the kit — <kbd>1</kbd> kick, <kbd>2</kbd> snare, <kbd>3</kbd> clap, <kbd>4</kbd> hat, <kbd>5</kbd> open hat — played by hand, live, straight into the world's FX. Hold <b></b> for the ghost note, softer. Arm <b>⏺ rec</b> and every tap writes itself into the grid, <b>quantized to the nearest sixteenth</b> — tap a hi-hat pattern into a quiet pocket and it's a lane — so you play the beat in rather than clicking it out.</li>
<li><b>🌾 AMPLER — there is ample, for god is abundant.</b> The SP-404 move, made of the planet. Right-click the sky → <b>🌾 AMPLER</b>: <b>⏺ capture</b> a stretch of the sounding world (1.5, 3 or 6 seconds of the live master — and since a fed tab <i>is</i> the world, that YouTube loop counts), then <b>✂ chop</b> it into 8 or 16 slices. Tap a slice to audition it, <b></b> to play it backwards, then <b>seat a slice onto a drum voice</b> — and that lane stops synthesising and starts <i>playing the world</i>. The slice rides the voice's own <i>level</i> and <i>pan</i> and, on the kick, its <b>duck</b>; a slice on the open hat still chokes under a closed one — the machine's rules don't care whether a hit is oscillator or sample. Sample anything playing on the machine, chop it, and it's a kit. Session-only for now: the slices don't travel with your patch (yet).</li>
<li><b>The beat leaves the building.</b> The drum lanes ride <b>MIDI out on channel 10</b> — General MIDI percussion, <i>kick 36, snare 38, clap 39, closed hat 42, open hat 46</i> — so with an out port chosen in ⚙ they play your hardware and DAW exactly as the note lanes do. And they land in <b>export .mid</b>: draw a beat, hit export, and the Standard MIDI File carries a channel-10 drum track (looped across the arrangement's bars) alongside your lead and bass — drag it into Ableton and the planet's rhythm is already programmed.</li>
<li><b>⚡ GODSPEED — hold a chord, the machine runs.</b> Press <kbd>G</kbd>. Enter a chakra (<kbd>C</kbd>), turn GODSPEED <b>on</b>, and <b>strike keys to latch a chord</b> — each note stays held until you strike it again. Then the machine runs with it, one note at a time, <i>on the godtime clock</i> — so it <b>swings with the groove</b>, laying its off-beats back exactly as the drums do, because it plays from the same swung tick. Set the <b>rate</b> (1/4 → 1/32), the <b>direction</b> (up, down, up/down, random), the <b>octaves</b> (×13) and the <b>gate</b> (how long each note rings). A <b>MIDI keyboard latches too</b> — its notes route through the same hands — and the arp plays <i>outward</i> on channel 2, the mind console's channel, so it drives your rig as it drives the synth.</li>
<li><b>🫀 GODSQUASH — the master squeeze the world can pump.</b> In <b>OMNI</b>'s fx rack sits one new knob: <b>squash</b>, a compressor across the whole master bus. Turn it up and the <b>threshold dives while the ratio climbs together</b>, a matched gain riding underneath so the <i>loudness stays level while the peaks kneel</i> — the mix gets denser, not louder. And because it's a destination like any other, <b>route a feed onto it</b> — the quakes, your heartbeat, the market's velocity — and the planet <b>pumps the mix</b>, breathing the whole instrument in time with the world.</li>
<li><b>🗣 GODSPEAK — the voice of god.</b> On your own machine (<i>Apple-Silicon only</i>), run <code>run.py --godspeak</code>, then <b>hold Right ⌥ and speak</b>. Your words are heard <b>locally</b> — your own Whisper, nothing leaving the machine — and become commands: <i>"tempo ninety", "storm mood", "capture six seconds", "chop sixteen", "open the beat", "squash sixty percent", "zen"</i>, and — the voice of god undoing its own works — <i>"take it back"</i>. A 🗣 chip glows by the ♪ while it listens. Anything it doesn't recognise simply <b>arrives in the world</b>, printed to the ticker — the word is never an error, only another feed.</li>
<li><b>⏪ UNGODLY — take it back.</b> <kbd>⌘Z</kbd> undoes; <kbd>⌘⇧Z</kbd> redoes. The instrument remembers your last <b>thirty-two acts</b> — every cable patched, mood applied, groove and scale changed, kit knob turned, beat step drawn, level and mute — and steps back through them, un-doing what god (or you) did. A held drag counts as one act, not fifty. It is <b>memory-only</b>: a reload forgives everything and starts the history clean.</li>
<li><b>📽 The stage — send the visual to any screen.</b> Right-click the sky → <b>the stage</b> and a clean black mirror window opens, fed live from the canvas. Drag it to a projector, an AirPlay display, a second monitor — macOS treats it like any window — and <b>double-click for fullscreen</b>. The stage has a <b>camera</b>: the whole view by default (whatever you're in — a skin, the wheel, mind mode's POV), or right-click any node → <b>project this node</b> and the camera glides after that one sphere through its orbits, or <b>project its station</b> to frame a whole wheel and its satellites. Change cameras live from the menus while the projector runs; the audience sees the camera breathe from subject to subject.</li>
<li><b>🕸 The commune — play together, properly.</b> Right-click the sky → <b>the commune</b>. This is for <b>signed-in players</b>: your session rides the connection, so the hub knows exactly who everyone is — no impostors, no setup, no Bluetooth. One of you <b>hosts</b>: name the room, then <b>✉ invite</b> the others by their username (they must be on the site — invitations reach the present). The invited see the invitation arrive live and <b>join or decline</b>; anyone may <b>leave</b> whenever they wish, and if the host leaves, the room closes with them. Then the sections: members <b>claim voices</b> (right-click any voice, or the panel's picker), or the <b>host allocates</b> them — this bass to her, those pads to him, <b>the godtime itself</b> to whoever conducts — and the hub enforces it: only the section's holder can speak for it. From that moment your levels, mutes, grooves and arranger lanes for what you hold follow your hands on every screen, same living room or opposite sides of the earth. The world — the Source — stays identical for everyone, because it always was. Three people, one planet, one instrument.</li></ul>
@ -2650,7 +2662,8 @@
bpm.value = String(Math.round(godtime.bpm));
bpm.disabled = godtime.locked;
bpm.title = godtime.locked ? "godtime is locked (🔒) — unlock it on the dial" : "beats per minute (40180)";
bpm.onchange = () => { if (!godtime.locked) godtime.bpm = clamp(Math.round(+bpm.value || godtime.bpm), 40, 180);
bpm.onchange = () => { const nv = clamp(Math.round(+bpm.value || godtime.bpm), 40, 180);
if (!godtime.locked && nv !== godtime.bpm) { ungodlyMark("tempo"); godtime.bpm = nv; } // ⏪ a tempo change is an act
bpm.value = String(Math.round(godtime.bpm)); };
const x2 = document.createElement("span"); x2.className = "aclose"; x2.textContent = "×"; x2.onclick = closeBeat;
const hint = document.createElement("span"); hint.className = "albl";
@ -2946,7 +2959,7 @@
// for free. Notes are latched at mindStrike (so a MIDI keyboard, which already
// routes through there, captures for free too). App-scope only; never touches
// the Synth engine — it just plays through the same pluck() every note uses.
const arp = { on: false, held: [], rate: 1, dir: "up", oct: 1, gate: 0.8, idx: 0, up: true };
const arp = { on: false, held: [], rate: 1, dir: "up", oct: 1, gate: 0.8, idx: 0 };
// rate 4=1/4 · 2=1/8 · 1=1/16 · 0.5=1/32 dir up|down|updown|random oct 1..3 held [{note,vel}] cap 8
function arpLatch(note, vel) { // toggle a note in/out of the held chord (latch)
const i = arp.held.findIndex(h => h.note === note);
@ -2954,7 +2967,7 @@
else if (arp.held.length < 8) arp.held.push({ note, vel }); // cap 8
arpPanelRefresh();
}
function arpSetOn(v) { arp.on = !!v; if (!arp.on) { arp.held = []; arp.idx = 0; arp.up = true; } } // off → drop the latch
function arpSetOn(v) { arp.on = !!v; if (!arp.on) { arp.held = []; arp.idx = 0; } } // off → drop the latch
function arpPool() { // held, ascending, expanded across oct octaves
const base = arp.held.slice().sort((a, b) => a.note - b.note), pool = [];
for (let o = 0; o < arp.oct; o++) for (const h of base) pool.push({ note: h.note + 12 * o, vel: h.vel });
@ -2972,8 +2985,11 @@
const pool = arpPool(); if (!pool.length) return;
const pick = arpNext(pool); if (!pick) return;
const durMs = Math.max(30, sub * arp.rate * arp.gate); // gate = fraction of the step length
Synth.pluck(pick.note, pick.vel);
midiPluck(2, pick.note, pick.vel, durMs); // channel 2 — the mind console's channel, exactly like mindStrike
let vel = pick.vel;
if (groove.velo) // breathe like the lanes — shared Velocity Deviation
vel = clamp(vel + (Math.random() * 2 - 1) * groove.velo * groove.amount * 0.5, 0.05, 1);
Synth.pluck(pick.note, vel);
midiPluck(2, pick.note, vel, durMs); // channel 2 — the mind console's channel, exactly like mindStrike
}
function arpTick() { // called at seqTick's tail — one 16th has passed
if (!arp.on || !arp.held.length) return;
@ -3283,6 +3299,7 @@
arr: { grid: JSON.parse(JSON.stringify(arr.grid)), banks: JSON.parse(JSON.stringify(arr.banks)),
follow: JSON.parse(JSON.stringify(arr.follow)) },
bpm: godtime.bpm,
locked: godtime.locked, // the tempo-lock travels too, so ⏪ can restore a mood's lock (see loadPatch)
groove: Object.assign({}, groove), // the feel travels with the patch
scale: Object.assign({}, gScale), // the key travels too
kit: (Synth.drumKit ? Synth.drumKit() : undefined), // the GODRUM kit travels too (undefined pre-merge)
@ -3311,6 +3328,7 @@
if (arr.open) openArranger(); // rebuild the panel on the new song
}
if (typeof p.bpm === "number") godtime.bpm = clamp(p.bpm, 40, 180);
if (typeof p.locked === "boolean") godtime.locked = p.locked; // restore the lock (old patches omit it → left as-is)
if (beat.open) openBeat(); // rebuild the beat panel on the new song
layoutDirty = true;
}
@ -4517,8 +4535,127 @@
}, 1000);
}
// ---- 🗣 GODSPEAK — the voice reaches the client --------------------------
// The worker (Apple-Silicon-only) sends each transcript to the hub, which relays
// it as {godspeak:{text|rec}}. Here the word becomes action: a tight, forgiving
// grammar over the instrument. First match wins; a command that fires narrates
// itself into the ticker; unmatched words simply arrive in the world (🗣 "…") —
// never an error. Nothing here reaches the network; the voice only ever acts locally.
let godspeakChip = null;
function godspeakChipEnsure() {
if (godspeakChip) return godspeakChip;
godspeakChip = document.createElement("div");
godspeakChip.id = "godspeakrec"; godspeakChip.textContent = "🗣";
godspeakChip.title = "GODSPEAK — the instrument is listening";
document.body.appendChild(godspeakChip);
return godspeakChip;
}
function godspeakSay(text) { eventTicker.unshift({ text: "🗣 " + text, tAdded: now(), src: "sys" }); }
function handleGodspeak(g) {
if (!g) return;
if ("rec" in g) godspeakChipEnsure().classList.toggle("on", !!(+g.rec)); // pulse while the mic is held
if (typeof g.text === "string" && g.text.trim()) godspeakCommand(g.text);
}
// spoken → number, tuned for the tempo range. Digits win; else number-words, with
// "one twenty" → 120 (a leading single before a tens word reads as hundreds).
function spokenNumber(t) {
const dm = t.match(/\b(\d{1,3})\b/); if (dm) return parseInt(dm[1], 10);
const W = { zero:0, one:1, two:2, three:3, four:4, five:5, six:6, seven:7, eight:8, nine:9,
ten:10, eleven:11, twelve:12, thirteen:13, fourteen:14, fifteen:15, sixteen:16, seventeen:17,
eighteen:18, nineteen:19, twenty:20, thirty:30, forty:40, fourty:40, fifty:50, sixty:60,
seventy:70, eighty:80, ninety:90, hundred:100 };
let cur = 0, seen = false;
for (const tok of t.split(/[^a-z]+/)) {
if (tok === "a") { cur += 1; seen = true; continue; }
if (tok === "and") continue;
const v = W[tok]; if (v == null) continue; seen = true;
if (v === 100) cur = (cur || 1) * 100;
else if (v >= 20 && cur >= 1 && cur <= 9) cur = cur * 100 + v; // "one twenty" → 120
else cur += v;
}
return seen ? cur : null;
}
function godspeakBeat(on) { // mirror the beat panel's master ▶ — flip all five drum lanes
ungodlyMark("beat play");
if (on && !DRUM_VOICES.some(v => seqs["drum." + v])) seedBeat(); // a virgin session still makes music
for (const v of DRUM_VOICES) drumSeqFor("drum." + v).on = on;
if (beatRefresh) beatRefresh();
}
function matchMood(t) { // real MOODS keys only — never invent a name
const strong = { "the full storm": ["storm"], "ambient drift": ["ambient", "drift"], "first light": ["first light"] };
for (const name in MOODS) {
if (t.indexOf(name) >= 0) return name;
for (const s of (strong[name] || [])) if (t.indexOf(s) >= 0) return name;
}
if (t.indexOf("mood") >= 0) for (const name in MOODS)
for (const w of name.split(" ")) if (w.length >= 4 && t.indexOf(w) >= 0) return name;
return null;
}
function matchSkin(t) { // gated on the word "skin" — never hijack ordinary speech
if (t.indexOf("skin") < 0) return null;
const nrm = (x) => String(x).toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
for (const k in SKINS) { const s = SKINS[k];
for (const c of [k, nrm(s.label || ""), nrm(s.word || "")])
if (c && c.length >= 3 && t.indexOf(c) >= 0) return k; }
return null;
}
function godspeakCommand(raw) {
const t = String(raw).toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
if (!t) return;
const has = (w) => t.indexOf(w) >= 0;
if (has("take it back") || (has("undo") && !has("redo"))) { ungodlyUndo(); return; } // the voice of god, un-doing
if (has("tempo") || has("bpm")) {
const n = spokenNumber(t);
if (n != null) {
if (godtime.locked) godspeakSay("godtime is locked — unlock the dial first");
else { ungodlyMark("tempo"); godtime.bpm = clamp(Math.round(n), 40, 180); godspeakSay("tempo → " + godtime.bpm); }
return;
}
}
if (has("squash")) {
const n = spokenNumber(t);
if (n != null) { ensureDest("squash"); ungodlyMark("squash");
const val = clamp(n / 100, 0, 1); setParamLocal("squash", "offset", val);
godspeakSay("squash → " + Math.round(val * 100) + "%"); return; }
}
if (has("capture") || has("sample")) {
const n = spokenNumber(t), secs = (n && n >= 1 && n <= 10) ? n : 3;
const ok = Synth.amplerArm ? Synth.amplerArm(secs) : false;
godspeakSay(ok ? ("AMPLER — capturing " + secs + "s of the world") : "AMPLER needs the world sounding — press ♪ first");
return;
}
if (has("chop")) {
const n = (/\beight\b/.test(t) || /\b8\b/.test(t)) ? 8 : 16;
const ok = Synth.amplerChop ? Synth.amplerChop(n) : false;
if (ok && ampler.open) amplerRender();
godspeakSay(ok ? ("chopped into " + n) : "nothing to chop — capture first"); return;
}
if (has("open") || has("close") || has("show") || has("hide")) {
const closing = has("close") || has("hide");
const flip = (name, isOpen, open, close) => {
if (closing) { if (isOpen) close(); } else { if (!isOpen) open(); }
godspeakSay((closing ? "closed " : "opened ") + name);
};
if (has("beat")) { flip("beat", beat.open, openBeat, closeBeat); return; }
if (has("track")) { flip("tracks", arr.open, openArranger, closeArranger); return; }
if (has("omni")) { flip("OMNI", OMNI.isOpen(), () => OMNI.open(), () => OMNI.close()); return; }
if (has("ampler")) { flip("AMPLER", ampler.open, openAmpler, closeAmpler); return; }
if (has("key")) { flip("keys", keysEl.classList.contains("open"), openKeys, closeKeys); return; }
if (has("godspeed") || has("arp")) { flip("GODSPEED", godspeedOpen(), openGodspeed, closeGodspeed); return; }
}
if (has("beat") || has("drums")) {
if (has("stop") || has("pause")) { godspeakBeat(false); godspeakSay("beat stopped"); return; }
if (has("play") || has("start") || has("go")) { godspeakBeat(true); godspeakSay("beat playing"); return; }
}
{ const m = matchMood(t); if (m) { applyMood(m); godspeakSay("mood → " + m); return; } }
{ const k = matchSkin(t); if (k) { setSkin(k); godspeakSay("skin → " + (SKINS[k].label || k)); return; } }
if (/\bzen\b/.test(t)) { setZen(!zenMode); godspeakSay(zenMode ? "zen" : "left zen"); return; }
godspeakSay('"' + String(raw).trim() + '"'); // unmatched — the word arrives in the world
}
function handleMessage(msg) {
if (msg.commune) { handleCommune(msg.commune); return; } // a roommate speaking
if (msg.godspeak) { handleGodspeak(msg.godspeak); return; } // 🗣 the voice reaches the client
if (msg.hello) {
helloReceived = true;
if (Array.isArray(msg.routes)) {

View File

@ -338,6 +338,39 @@
#ampler .x { cursor: pointer; color: #ff8b8b; font-weight: 700; padding: 0 3px; }
#ampler .hint { color: rgba(130,150,180,0.6); font-size: 10px; line-height: 1.5; }
/* ⚡ GODSPEED — the arpeggiator (compact, left side) */
#godspeed {
position: fixed; left: 12px; top: 64px; width: 244px; z-index: 13;
background: rgba(10,15,26,0.95); border: 1px solid rgba(120,150,200,0.35);
border-radius: 12px; padding: 8px 10px; display: none; color: #c7d4ea;
font-size: 11px; -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
#godspeed.open { display: flex; flex-direction: column; gap: 6px; }
#godspeed .atop { display: flex; align-items: center; gap: 8px; }
#godspeed .btitle { color: #cfe0ff; font-size: 12px; font-weight: 600; flex: 1; }
#godspeed .aclose { cursor: pointer; color: #8fa4c8; font-size: 16px; padding: 0 4px; }
#godspeed .row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
#godspeed .grow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
#godspeed .gctl { display: inline-flex; align-items: center; gap: 4px; }
#godspeed .glbl { color: rgba(160,180,215,0.8); font-size: 10px; }
#godspeed .midibtn { background: rgba(40,52,74,0.85); color: #b9c8e2; border: 1px solid rgba(120,150,200,0.25);
border-radius: 7px; padding: 3px 9px; cursor: pointer; font-size: 11px; }
#godspeed .midibtn.on { background: rgba(120,220,150,0.22); color: #bff0c9; border-color: rgba(130,230,160,0.55); }
#godspeed .bmini { padding: 2px 7px; }
#godspeed .asel { background: rgba(30,40,60,0.85); color: #dbe6f5; border: 1px solid rgba(120,150,200,0.3);
border-radius: 6px; padding: 2px 5px; font-size: 10px; }
#godspeed input[type=range] { width: 88px; accent-color: #7fa0e6; height: 3px; }
#godspeed .held { color: #bcd; font-size: 11px; letter-spacing: 0.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#godspeed .hint { color: rgba(130,150,180,0.6); font-size: 10px; line-height: 1.5; }
/* 🗣 GODSPEAK — a small "listening" chip near the ♪ button, pulses while the mic is held */
#godspeakrec { position: fixed; top: 12px; right: 92px; z-index: 40; width: 30px; height: 30px;
border-radius: 8px; display: none; align-items: center; justify-content: center; font-size: 15px;
background: rgba(230,90,90,0.18); color: #ffd2d2; border: 1px solid rgba(240,110,110,0.5);
-webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
#godspeakrec.on { display: flex; animation: gspk-pulse 1.1s ease-in-out infinite; }
@keyframes gspk-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
/* right-click context menu — every setting, on everything */
#ctxmenu {
position: fixed; z-index: 30; display: none; width: 240px; max-height: 72vh;
@ -1121,6 +1154,10 @@
<li><b>Keys <kbd>1</kbd><kbd>5</kbd> &amp; ⏺ rec — finger-drum the kit.</b> With the beat open the number row <i>is</i> the kit — <kbd>1</kbd> kick, <kbd>2</kbd> snare, <kbd>3</kbd> clap, <kbd>4</kbd> hat, <kbd>5</kbd> open hat — played by hand, live, straight into the world's FX. Hold <b></b> for the ghost note, softer. Arm <b>⏺ rec</b> and every tap writes itself into the grid, <b>quantized to the nearest sixteenth</b> — tap a hi-hat pattern into a quiet pocket and it's a lane — so you play the beat in rather than clicking it out.</li>
<li><b>🌾 AMPLER — there is ample, for god is abundant.</b> The SP-404 move, made of the planet. Right-click the sky → <b>🌾 AMPLER</b>: <b>⏺ capture</b> a stretch of the sounding world (1.5, 3 or 6 seconds of the live master — and since a fed tab <i>is</i> the world, that YouTube loop counts), then <b>✂ chop</b> it into 8 or 16 slices. Tap a slice to audition it, <b></b> to play it backwards, then <b>seat a slice onto a drum voice</b> — and that lane stops synthesising and starts <i>playing the world</i>. The slice rides the voice's own <i>level</i> and <i>pan</i> and, on the kick, its <b>duck</b>; a slice on the open hat still chokes under a closed one — the machine's rules don't care whether a hit is oscillator or sample. Sample anything playing on the machine, chop it, and it's a kit. Session-only for now: the slices don't travel with your patch (yet).</li>
<li><b>The beat leaves the building.</b> The drum lanes ride <b>MIDI out on channel 10</b> — General MIDI percussion, <i>kick 36, snare 38, clap 39, closed hat 42, open hat 46</i> — so with an out port chosen in ⚙ they play your hardware and DAW exactly as the note lanes do. And they land in <b>export .mid</b>: draw a beat, hit export, and the Standard MIDI File carries a channel-10 drum track (looped across the arrangement's bars) alongside your lead and bass — drag it into Ableton and the planet's rhythm is already programmed.</li>
<li><b>⚡ GODSPEED — hold a chord, the machine runs.</b> Press <kbd>G</kbd>. Enter a chakra (<kbd>C</kbd>), turn GODSPEED <b>on</b>, and <b>strike keys to latch a chord</b> — each note stays held until you strike it again. Then the machine runs with it, one note at a time, <i>on the godtime clock</i> — so it <b>swings with the groove</b>, laying its off-beats back exactly as the drums do, because it plays from the same swung tick. Set the <b>rate</b> (1/4 → 1/32), the <b>direction</b> (up, down, up/down, random), the <b>octaves</b> (×13) and the <b>gate</b> (how long each note rings). A <b>MIDI keyboard latches too</b> — its notes route through the same hands — and the arp plays <i>outward</i> on channel 2, the mind console's channel, so it drives your rig as it drives the synth.</li>
<li><b>🫀 GODSQUASH — the master squeeze the world can pump.</b> In <b>OMNI</b>'s fx rack sits one new knob: <b>squash</b>, a compressor across the whole master bus. Turn it up and the <b>threshold dives while the ratio climbs together</b>, a matched gain riding underneath so the <i>loudness stays level while the peaks kneel</i> — the mix gets denser, not louder. And because it's a destination like any other, <b>route a feed onto it</b> — the quakes, your heartbeat, the market's velocity — and the planet <b>pumps the mix</b>, breathing the whole instrument in time with the world.</li>
<li><b>🗣 GODSPEAK — the voice of god.</b> On your own machine (<i>Apple-Silicon only</i>), run <code>run.py --godspeak</code>, then <b>hold Right ⌥ and speak</b>. Your words are heard <b>locally</b> — your own Whisper, nothing leaving the machine — and become commands: <i>"tempo ninety", "storm mood", "capture six seconds", "chop sixteen", "open the beat", "squash sixty percent", "zen"</i>, and — the voice of god undoing its own works — <i>"take it back"</i>. A 🗣 chip glows by the ♪ while it listens. Anything it doesn't recognise simply <b>arrives in the world</b>, printed to the ticker — the word is never an error, only another feed.</li>
<li><b>⏪ UNGODLY — take it back.</b> <kbd>⌘Z</kbd> undoes; <kbd>⌘⇧Z</kbd> redoes. The instrument remembers your last <b>thirty-two acts</b> — every cable patched, mood applied, groove and scale changed, kit knob turned, beat step drawn, level and mute — and steps back through them, un-doing what god (or you) did. A held drag counts as one act, not fifty. It is <b>memory-only</b>: a reload forgives everything and starts the history clean.</li>
<li><b>📽 The stage — send the visual to any screen.</b> Right-click the sky → <b>the stage</b> and a clean black mirror window opens, fed live from the canvas. Drag it to a projector, an AirPlay display, a second monitor — macOS treats it like any window — and <b>double-click for fullscreen</b>. The stage has a <b>camera</b>: the whole view by default (whatever you're in — a skin, the wheel, mind mode's POV), or right-click any node → <b>project this node</b> and the camera glides after that one sphere through its orbits, or <b>project its station</b> to frame a whole wheel and its satellites. Change cameras live from the menus while the projector runs; the audience sees the camera breathe from subject to subject.</li>
<li><b>🕸 The commune — play together, properly.</b> Right-click the sky → <b>the commune</b>. This is for <b>signed-in players</b>: your session rides the connection, so the hub knows exactly who everyone is — no impostors, no setup, no Bluetooth. One of you <b>hosts</b>: name the room, then <b>✉ invite</b> the others by their username (they must be on the site — invitations reach the present). The invited see the invitation arrive live and <b>join or decline</b>; anyone may <b>leave</b> whenever they wish, and if the host leaves, the room closes with them. Then the sections: members <b>claim voices</b> (right-click any voice, or the panel's picker), or the <b>host allocates</b> them — this bass to her, those pads to him, <b>the godtime itself</b> to whoever conducts — and the hub enforces it: only the section's holder can speak for it. From that moment your levels, mutes, grooves and arranger lanes for what you hold follow your hands on every screen, same living room or opposite sides of the earth. The world — the Source — stays identical for everyone, because it always was. Three people, one planet, one instrument.</li></ul>

View File

@ -15,6 +15,12 @@ stay clean. On a Mac, into the hub's venv:
Model: mlx-community/whisper-large-v3-turbo (~1.6 GB, downloaded on first run).
macOS one-time permissions (System Settings Privacy & Security) for the
terminal/app that launches this worker same as wersperr:
- Microphone to hear you (sounddevice input)
- Input Monitoring for pynput to see the Right hotkey held globally
(older macOS may list this under Accessibility instead)
python3 workers/godspeak_worker.py # hold Right ⌥ to speak
python3 workers/godspeak_worker.py --check # self-test: model loads + silence transcribes, then exit