diff --git a/GODSTRUMENT_MANUAL_SOURCE.md b/GODSTRUMENT_MANUAL_SOURCE.md index 489334c..fd50514 100644 --- a/GODSTRUMENT_MANUAL_SOURCE.md +++ b/GODSTRUMENT_MANUAL_SOURCE.md @@ -586,6 +586,8 @@ The matrix pours out clean control values whether or not anyone is listening. Ch **The built-in voice, up close.** Press `โ™ช` and the browser synth wakes. Its plucked notes ring with a **DX7-style FM tine** โ€” brighter the harder they're struck; its pads bloom into a **detuned supersaw**; a **Juno chorus** warms the whole thing. Prefer strings? Right-click the sky โ†’ **๐ŸŽธ string voice** and every pluck becomes a real **Karplus-Strong** plucked string. And route anything to `crush` for a **12-bit-DAC bitcrusher** โ€” the planet degrading its own converter into lo-fi grit. (Every one of these was tuned by spectral analysis before it shipped โ€” rendered offline and measured, so it's in-tune and clean, not just plausible.) +**๐ŸŽ› 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. + **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. @@ -1013,3 +1015,7 @@ Every feed the world sends is one of eight temperaments; the shape dictates how - **๐ŸŽน Supersaw pad** โ€” each of the pad's chord tones is a detuned 9-saw cluster (JP-8000 style) for a lush, wide wall. - **๐ŸŽธ String voice** โ€” right-click the sky โ†’ *string voice* swaps the FM tine for a real Karplus-Strong plucked string (an AudioWorklet: noise burst โ†’ damped delay loop, sample-accurate). Spectrally verified stable and in-tune. - **๐Ÿ”Š 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). +- **โฌ— 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. diff --git a/viz/index.html b/viz/index.html index 240a88c..0bcba53 100644 --- a/viz/index.html +++ b/viz/index.html @@ -1018,6 +1018,8 @@

The built-in voice, up close. Press โ™ช and the browser synth wakes. Its plucked notes ring with a DX7-style FM tine โ€” brighter the harder they're struck; its pads bloom into a detuned supersaw; a Juno chorus warms the whole thing. Prefer strings? Right-click the sky โ†’ ๐ŸŽธ string voice and every pluck becomes a real Karplus-Strong plucked string. And route anything to crush for a 12-bit-DAC bitcrusher โ€” the planet degrading its own converter into lo-fi grit. (Every one of these was tuned by spectral analysis before it shipped โ€” rendered offline and measured, so it's in-tune and clean, not just plausible.)

+

๐ŸŽ› 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.

+

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.

@@ -5536,6 +5538,10 @@ () => earthEchoSet(!earthEchoOn)); ctxItem("๐ŸŽธ string voice" + (Synth.stringVoice() ? " (on โ€” plucks ring like a string)" : " โ€” swap the FM tine for a Karplus string"), () => Synth.stringVoice(!Synth.stringVoice())); + ctxItem("๐ŸŽ› GODSONIQ" + (Synth.sampling() ? " (on โ€” plucks replay your capture ยท tap to re-sample)" : " โ€” resample the world onto the keys"), + () => { 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("โŒจ keys โ€” remap the keyboard", () => openKeys()); if (commune.room) { const gOwner = commune.alloc["@godtime"]; @@ -6942,8 +6948,9 @@ // drivable) and a sample-accurate Karplus-Strong string. Both need a worklet // (native feedback + real sample-rate reduction are unstable/impossible with // stock nodes โ€” the spectral harness proved it). Loaded async from a Blob URL. - const WORKLET_SRC = "class Bitcrush extends AudioWorkletProcessor{static get parameterDescriptors(){return[{name:'bits',defaultValue:16,minValue:1,maxValue:16,automationRate:'k-rate'},{name:'reduction',defaultValue:1,minValue:1,maxValue:64,automationRate:'k-rate'}];}constructor(){super();this.phase=0;this.hold=null;}process(inputs,outputs,p){const inp=inputs[0],out=outputs[0];if(!inp||!inp.length)return true;const b=p.bits[0],red=Math.max(1,Math.round(p.reduction[0])),lv=Math.pow(2,b),nC=out.length,nS=out[0].length;if(!this.hold)this.hold=new Float32Array(8);for(let i=0;i{const vo=this.v[this.n];this.n=(this.n+1)%8;const L=Math.max(2,Math.min(4096,Math.round(sampleRate/e.data.freq)));vo.len=L;vo.idx=0;vo.active=true;vo.amp=e.data.vel;vo.last=0;for(let i=0;i=vo.len)vo.idx=0;}}for(let c=1;c { if (!e.data.done) return; + const f = e.data.buf, ab = ctx.createBuffer(1, f.length, ctx.sampleRate); ab.copyToChannel(f, 0); + sampleBuf = ab; sampleMode = true; ksMode = false; // captured โ€” plucks now replay it, pitched + }; }).catch(() => { workletTried = false; }); } + function godsoniq(secs) { // arm the resampler for `secs` of the live world + if (!ctx) start(); + if (!recNode) return false; // worklet not ready + if (!on) start(); // world must be sounding to sample it + recNode.port.postMessage({ rec: true, len: Math.floor((secs || 3) * ctx.sampleRate) }); + return true; + } function start() { if (!AC) return; if (!ctx) { ctx = new AC(); N = build(); loadWorklets(); } @@ -6976,6 +6999,16 @@ if (!on) N.out.gain.setTargetAtTime(0.85, t, 0.1); // master up; data voices stay silent const v = vel == null ? 0.8 : vel; const freq = mtof(midi); + if (sampleMode && sampleBuf) { // GODSONIQ โ€” replay the captured world, pitched across the keys + const src = ctx.createBufferSource(); src.buffer = sampleBuf; + src.playbackRate.value = Math.pow(2, (midi - 60) / 12); // note-spread from middle C (ASR-style) + const g = ctx.createGain(); g.gain.value = 0; + src.connect(g); g.connect(N.preSat); // through the full FX chain โ€” route `crush` for the ASR grit + g.gain.setValueAtTime(0, t); g.gain.linearRampToValueAtTime(0.5 * v, t + 0.005); + g.gain.setTargetAtTime(0, t + 0.05, 0.6); + src.start(t); src.stop(t + 3.2); + return; + } if (ksMode && ksNode) { ksNode.port.postMessage({ freq, vel: v }); return; } // Karplus string voice // FM pluck โ€” a sine carrier phase-modulated by a sine operator, index enveloped @@ -7019,7 +7052,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; return ksMode; } }; + 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; } }; })(); // ---------------------------------------------------------------------------