diff --git a/GODSTRUMENT_MANUAL_SOURCE.md b/GODSTRUMENT_MANUAL_SOURCE.md index fd50514..63b9e43 100644 --- a/GODSTRUMENT_MANUAL_SOURCE.md +++ b/GODSTRUMENT_MANUAL_SOURCE.md @@ -588,6 +588,8 @@ The matrix pours out clean control values whether or not anyone is listening. Ch **π GODSONIQ β resample the world onto the keys.** A cheeky nod to the Ensoniq ASR-10: right-click the sky β **π GODSONIQ** and it records ~3 seconds of the *live* master mix β whatever the world is playing right now, this exact never-repeating take β into a buffer. From then on every pluck **replays that capture, pitched across the keyboard** (middle C plays it at speed, an octave up plays it twice as fast). It runs through the full FX chain, so route `crush` at it and you get the classic 12-bit sampler grit. Right-click β π again to grab a fresh slice; **πΈ string voice** (or toggling it off) drops you back to the FM tine. The browser-native bounce: freeze a moment of the planet, then play the planet like an instrument. +**π Feed a tab into the world.** Right-click the sky β **π feed a tab** and the browser's share-picker opens β choose another tab (a YouTube video, a live radio stream, a Bandcamp page), tick **βShare tab audio,β** and that sound pours straight into Godstrument's FX chain. It's *in the world* now: route `crush` to grit it, drench it in the world's reverb, or hit **π GODSONIQ** to resample it and play it pitched across the keys. Click π again (or the browser's *Stop sharing*) to cut it. **The fine print:** this is a **Chrome / Edge** trick (Safari and Firefox can't capture tab audio at all); on a Mac it grabs one *tab's* sound, not the whole system (for that you'd route audio through a virtual device like BlackHole); and DRM'd services (Netflix, Spotify, Apple Music) usually come through silent β ordinary YouTube is fine. + **Zero β the moods, the gentle front door.** On your very first visit the instrument greets you with **π first light** instead of the full flood: a bass line, soft hats, a small melody, modest space β a thing shaped like a song, with the world still playing underneath it. Right-click the sky β **π mood** to move between *first light*, *ambient drift* (no drums, all atmosphere, the planet as weather), and **the full storm** β the factory instrument with every one of the world's hands on it at once. Ease in, then open the door. **One β the built-in browser synth.** Click **βͺ** and a full voice rack wakes inside the page: **lead** (bitcoin's price and your market's average price both quantize to it β minor pentatonic and G dorian), **bass** (quake depth chooses its notes in a low minor), **pad** (brightened by Tokyo's warmth, daylight, harmony, the birth rate, longer lives), **drone** (thickened by aircraft aloft, barometric pressure, Saturn's slow wheel, the weight of the national debt), and **perc** (BTC volatility and market activity drive the hats). Over them sit the effects the world modulates: **reverb** (earthquakes open the void, room light sizes it), **delay** (your hand's Y feeds it, Mercury retrograde smears it), **saturation** (Delhi's air becomes grit, hard aspects add dissonance, wildfires burn it, hunger grinds beneath), and **glitch** (every Wikipedia edit on Earth, every light flash, every new wildfire fires a burst). Nothing to install; the planet plays a synth in your tab. @@ -1019,3 +1021,8 @@ Every feed the world sends is one of eight temperaments; the shape dictates how - **π Juno-106 chorus** β an always-on subtle BBD-style stereo chorus on the synth's master bus (two short delays warbled by slow LFOs, panned wide) that warms and widens the dry oscillators. - **π 12-bit bitcrush** β a world-drivable lo-fi stage on the synth's master bus (an AudioWorklet doing true bit-depth quantise + sample-rate reduction). Route anything to `crush` and the planet degrades its own DAC β clean by default (16-bit). - **π GODSONIQ (resampler)** β a cheeky nod to the Ensoniq ASR-10: right-click the sky β *GODSONIQ* records ~3s of the live master mix (a recorder AudioWorklet taps the full post-FX bus) into a buffer, then every pluck replays that capture pitched across the keys (playbackRate = 2^((midi-60)/12), middle C = unity). Plays through the full FX chain, so `crush` gives it the classic 12-bit sampler grit. πΈ string voice exits back to the FM tine. The browser-native bounce β capture the world's never-repeating take, then play it. +- **β¬ Parameter locks (p-locks)** β Pocket-Operator / Elektron style: a texture voice (filter, glitch, reverb, drive) gets a per-step `lock` lane that pins its value to a fixed level on that step (blocky, disjointed), overriding the world; drag a step below the lane to unlock it and hand that step back to the data. Applied in `computeDests` via `applyGroove`; travels with the patch. +- **π Juno-106 chorus** β an always-on subtle BBD-style stereo chorus on the synth's master bus (two short delays warbled by slow LFOs, panned wide) that warms and widens the dry oscillators. +- **π 12-bit bitcrush** β a world-drivable lo-fi stage on the synth's master bus (an AudioWorklet doing true bit-depth quantise + sample-rate reduction). Route anything to `crush` and the planet degrades its own DAC β clean by default (16-bit). +- **π GODSONIQ (resampler)** β a cheeky nod to the Ensoniq ASR-10: right-click the sky β *GODSONIQ* records ~3s of the live master mix (a recorder AudioWorklet taps the full post-FX bus) into a buffer, then every pluck replays that capture pitched across the keys (playbackRate = 2^((midi-60)/12), middle C = unity). Plays through the full FX chain, so `crush` gives it the classic 12-bit sampler grit. πΈ string voice exits back to the FM tine. The browser-native bounce β capture the world's never-repeating take, then play it. +- **π Tab feed (external audio in)** β right-click the sky β *feed a tab* calls `getDisplayMedia({video:true, audio:{...}})`; the user picks another browser tab and ticks 'Share tab audio', and its sound (a YouTube tab, a live stream) is wired via `createMediaStreamSource` into `preSat` β the full FX chain and master, so it's audible live, `crush`-able, and resampleable by GODSONIQ. Constraints disable echoCancellation/noiseSuppression/autoGainControl (voice DSP that mangles music) and set suppressLocalAudioPlayback (no double audio). Chromium-only (Chrome/Edge; Safari & Firefox don't deliver display audio); on macOS it captures a TAB not all-system (BlackHole/Loopback is the system-wide workaround); DRM'd services (Netflix/Spotify/Apple Music) usually capture silent, plain YouTube is fine. Video track is stopped immediately; a track 'ended' listener handles the browser's Stop-sharing. diff --git a/viz/index.html b/viz/index.html index 0bcba53..929c39c 100644 --- a/viz/index.html +++ b/viz/index.html @@ -1020,6 +1020,8 @@
π GODSONIQ β resample the world onto the keys. A cheeky nod to the Ensoniq ASR-10: right-click the sky β π GODSONIQ and it records ~3 seconds of the live master mix β whatever the world is playing right now, this exact never-repeating take β into a buffer. From then on every pluck replays that capture, pitched across the keyboard (middle C plays it at speed, an octave up plays it twice as fast). It runs through the full FX chain, so route crush at it and you get the classic 12-bit sampler grit. Right-click β π again to grab a fresh slice; πΈ string voice (or toggling it off) drops you back to the FM tine. The browser-native bounce: freeze a moment of the planet, then play the planet like an instrument.
+π Feed a tab into the world. Right-click the sky β π feed a tab and the browser's share-picker opens β choose another tab (a YouTube video, a live radio stream, a Bandcamp page), tick βShare tab audio,β and that sound pours straight into Godstrument's FX chain. It's in the world now: route crush to grit it, drench it in the world's reverb, or hit π GODSONIQ to resample it and play it pitched across the keys. Click π again (or the browser's Stop sharing) to cut it. The fine print: this is a Chrome / Edge trick (Safari and Firefox can't capture tab audio at all); on a Mac it grabs one tab's sound, not the whole system (for that you'd route audio through a virtual device like BlackHole); and DRM'd services (Netflix, Spotify, Apple Music) usually come through silent β ordinary YouTube is fine.
+Zero β the moods, the gentle front door. On your very first visit the instrument greets you with π first light instead of the full flood: a bass line, soft hats, a small melody, modest space β a thing shaped like a song, with the world still playing underneath it. Right-click the sky β π mood to move between first light, ambient drift (no drums, all atmosphere, the planet as weather), and the full storm β the factory instrument with every one of the world's hands on it at once. Ease in, then open the door.
One β the built-in browser synth. Click βͺ and a full voice rack wakes inside the page: lead (bitcoin's price and your market's average price both quantize to it β minor pentatonic and G dorian), bass (quake depth chooses its notes in a low minor), pad (brightened by Tokyo's warmth, daylight, harmony, the birth rate, longer lives), drone (thickened by aircraft aloft, barometric pressure, Saturn's slow wheel, the weight of the national debt), and perc (BTC volatility and market activity drive the hats). Over them sit the effects the world modulates: reverb (earthquakes open the void, room light sizes it), delay (your hand's Y feeds it, Mercury retrograde smears it), saturation (Delhi's air becomes grit, hard aspects add dissonance, wildfires burn it, hunger grinds beneath), and glitch (every Wikipedia edit on Earth, every light flash, every new wildfire fires a burst). Nothing to install; the planet plays a synth in your tab.
@@ -5542,6 +5544,14 @@ () => { const ok = Synth.godsoniq(3); 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("π " + (Synth.feeding() ? "tab feed: live β click to stop" : "feed a tab (YouTube, a streamβ¦) into the world"), + async () => { const r = await Synth.feedTab(); + const msg = r.stopped ? "π tab feed stopped" + : r.ok ? "π tab feed is live β it's in the world now: route crush for lo-fi, or π GODSONIQ to resample it pitched" + : r.reason === "unsupported" ? "π tab-audio capture needs Chrome or Edge (Safari & Firefox can't). On a Mac it grabs one TAB's sound, not all-system" + : r.reason === "no-audio" ? "π pick the tab AND tick βShare tab audioβ in the picker β no audio came through" + : "π tab feed cancelled"; + eventTicker.unshift({ text: msg, tAdded: now(), src: "sys" }); }); ctxItem("β¨ keys β remap the keyboard", () => openKeys()); if (commune.room) { const gOwner = commune.alloc["@godtime"]; @@ -6977,6 +6987,37 @@ recNode.port.postMessage({ rec: true, len: Math.floor((secs || 3) * ctx.sampleRate) }); return true; } + // π external tab feed β pull another tab's audio (a YouTube tab, a live stream) into the + // world via getDisplayMedia. Wired into preSat, so it flows the whole FX chain to the master: + // audible live AND resampleable by GODSONIQ (which taps N.out). Chrome/Edge only; on a Mac the + // picker grabs a TAB's audio (tick "Share tab audio"), not all-system (that needs a virtual device). + let extStream = null, extSrc = null, extGain = null; + function stopFeed() { + if (extStream) extStream.getTracks().forEach(t => t.stop()); + if (extSrc) { try { extSrc.disconnect(); } catch (e) {} } + if (extGain) { try { extGain.disconnect(); } catch (e) {} } + extStream = extSrc = extGain = null; + } + async function feedTab() { + const md = navigator.mediaDevices; + if (!md || !md.getDisplayMedia) return { ok: false, reason: "unsupported" }; + if (extStream) { stopFeed(); return { ok: true, stopped: true }; } // second click = stop + if (!ctx) { ctx = new AC(); N = build(); loadWorklets(); } + let stream; + try { // video:true is mandatory to get tab audio in Chromium; kill the voice-DSP that mangles music, and mute the tab's own speaker output so we don't hear it twice + stream = await md.getDisplayMedia({ video: true, audio: { echoCancellation: false, noiseSuppression: false, autoGainControl: false, suppressLocalAudioPlayback: true } }); + } catch (e) { return { ok: false, reason: "cancelled" }; } + const aud = stream.getAudioTracks(); + if (!aud.length) { stream.getTracks().forEach(t => t.stop()); return { ok: false, reason: "no-audio" }; } // user forgot "Share tab audio" + stream.getVideoTracks().forEach(t => t.stop()); // we only want the sound + extStream = stream; + extSrc = ctx.createMediaStreamSource(stream); + extGain = ctx.createGain(); extGain.gain.value = 0.9; + extSrc.connect(extGain); extGain.connect(N.preSat); // into the FX chain β master + if (!on) start(); + aud[0].addEventListener("ended", stopFeed); // browser's "Stop sharing" button + return { ok: true }; + } function start() { if (!AC) return; if (!ctx) { ctx = new AC(); N = build(); loadWorklets(); } @@ -7053,7 +7094,8 @@ return { update(dests) { if (dests) Object.assign(D, dests); }, pluck, 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; } }; + godsoniq(secs) { return godsoniq(secs); }, sampling() { return sampleMode && !!sampleBuf; }, + feedTab() { return feedTab(); }, feeding() { return !!extStream; } }; })(); // ---------------------------------------------------------------------------