diff --git a/GODSTRUMENT_MANUAL_SOURCE.md b/GODSTRUMENT_MANUAL_SOURCE.md index ac64963..489334c 100644 --- a/GODSTRUMENT_MANUAL_SOURCE.md +++ b/GODSTRUMENT_MANUAL_SOURCE.md @@ -584,6 +584,8 @@ Because the orrery is rendered client-side, in each listener's own browser, **ev The matrix pours out clean control values whether or not anyone is listening. Choosing *how* to hear it is choosing which instrument the planet plays. +**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.) + **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. @@ -1005,3 +1007,9 @@ 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. - **โฌ— 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. +- **โฌ— 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. +- **๐Ÿ”” FM tine** โ€” every plucked note (sequencer, hands, mind mode) is a sine carrier phase-modulated by a sine operator (DX7-style), index enveloped for a bright attack โ†’ mellow tail and scaled by velocity (harder = brighter). Spectrally verified alias-free across the register. +- **๐ŸŽน 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). diff --git a/viz/index.html b/viz/index.html index eafee92..3325a12 100644 --- a/viz/index.html +++ b/viz/index.html @@ -1016,6 +1016,8 @@

The matrix pours out clean control values whether or not anyone is listening. Choosing how to hear it is choosing which instrument the planet plays.

+

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.)

+

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.

@@ -5532,6 +5534,8 @@ () => openCV()); ctxItem((earthEchoOn ? "๐ŸŒ€ earth echo: on" : "๐ŸŒ€ earth echo โ€” the planet on tape"), () => 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("โŒจ keys โ€” remap the keyboard", () => openKeys()); if (commune.room) { const gOwner = commune.alloc["@godtime"]; @@ -6869,6 +6873,11 @@ N.leadFilt.Q.setTargetAtTime(1 + d("wavetable.morph", 0) * 8, t, 0.1); S(N.padFilt.frequency, 220 + d("pad.brightness", 0) * 4200); S(N.padAmp.gain, d("pad.brightness", 0) * 0.07, 0.3); // ร—9 detuned saws โ†’ trimmed to hold the level + if (crusher) { // world-driven 12-bit-DAC crunch (default clean) + const cb = clamp(d("crush", 0), 0, 1); + crusher.parameters.get("bits").value = 16 - cb * 12; // 16 clean โ†’ 4 crunchy + crusher.parameters.get("reduction").value = 1 + cb * 8; // 1 โ†’ 9ร— downsample + } S(N.droneAmp.gain, d("drone.voices", 0) * 0.08, 0.3); S(N.revSend.gain, (d("reverb.size", 0) * 0.5 + d("master.space", 0) * 0.5) * 0.30, 0.3); S(N.fb.gain, Math.min(0.55, d("delay.feedback", 0) * 0.6), 0.2); @@ -6919,9 +6928,25 @@ params(); } + // ---- AudioWorklet voices: a 12-bit-DAC bitcrusher on the master bus (world- + // 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 { + crusher = new AudioWorkletNode(ctx, "bitcrush"); + N.glitch.disconnect(); N.glitch.connect(crusher); crusher.connect(N.out); // splice onto the master bus + ksNode = new AudioWorkletNode(ctx, "karplus"); ksNode.connect(N.preSat); + }).catch(() => { workletTried = false; }); + } function start() { if (!AC) return; - if (!ctx) { ctx = new AC(); N = build(); } + if (!ctx) { ctx = new AC(); N = build(); loadWorklets(); } ctx.resume(); on = true; nextAt = ctx.currentTime; N.out.gain.setTargetAtTime(0.85, ctx.currentTime, 0.5); @@ -6935,12 +6960,14 @@ const FM_RATIO = 3; // FM operator ratio (2 = warm/woody, 3 = bright tine, 4 = glassy) โ€” all verified alias-free function pluck(midi, vel) { if (!AC) return; - if (!ctx) { ctx = new AC(); N = build(); } + if (!ctx) { ctx = new AC(); N = build(); loadWorklets(); } ctx.resume(); const t = ctx.currentTime; 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 (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 // (bright "tine" attack โ†’ mellow tail) and scaled by velocity. Spectrally verified: // in-tune, harder=brighter, no aliasing, no DC. (DX7 ยง, PM not FM โ€” stable pitch.) @@ -6981,7 +7008,8 @@ }); return { update(dests) { if (dests) Object.assign(D, dests); }, pluck, - toggle() { on ? stop() : start(); paint(); return on; }, playing() { return on; } }; + toggle() { on ? stop() : start(); paint(); return on; }, playing() { return on; }, + stringVoice(v) { if (v === undefined) return ksMode; if (!ctx) start(); ksMode = !!v; return ksMode; } }; })(); // ---------------------------------------------------------------------------