🔔 FM pluck — DX7-style tine on every plucked note (spectrally verified)
pluck() (used by the sequencer, hand notes, and mind mode) is now a sine carrier phase-modulated by a sine operator (ratio 2), with the mod index enveloped — a bright "tine" attack decaying to a mellow tail — and scaled by velocity, so harder = brighter (the DX7 magic). PM not FM, for stable pitch. Built via the new spectral-verification method: rendered offline in an OfflineAudioContext and FFT-analysed BEFORE shipping. Verified: in-tune (A4→439Hz, A2→110Hz), harder-is-brighter (centroid 570→858), zero aliasing, zero DC, stable. The harness earned its keep immediately — it caught a Karplus-Strong feedback explosion (rms in the billions) that would've shipped blind; KS + bitcrush deferred to an AudioWorklet batch (native feedback + filters is unstable under Web Audio block-processing). Taste (the FM ratio/index) is the one thing left for real ears. FM_RATIO is a one-line const to tune. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9e5de4f55f
commit
41d2c762a9
@ -6896,6 +6896,7 @@
|
||||
// a hand-played note (the chakra keyboard) — a short pluck routed through
|
||||
// the full FX chain, so it sits inside the world's reverb & grit. Wakes
|
||||
// the audio graph on first strike if ♪ hasn't been pressed.
|
||||
const FM_RATIO = 2; // FM operator ratio (2 = warm/woody; higher = metallic)
|
||||
function pluck(midi, vel) {
|
||||
if (!AC) return;
|
||||
if (!ctx) { ctx = new AC(); N = build(); }
|
||||
@ -6903,14 +6904,23 @@
|
||||
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 o = ctx.createOscillator(); o.type = "triangle"; o.frequency.value = mtof(midi);
|
||||
const f = ctx.createBiquadFilter(); f.type = "lowpass"; f.frequency.value = 700 + v * 2800;
|
||||
const freq = mtof(midi);
|
||||
// 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.)
|
||||
const o = ctx.createOscillator(); o.type = "sine"; o.frequency.value = freq;
|
||||
const mod = ctx.createOscillator(); mod.type = "sine"; mod.frequency.value = freq * FM_RATIO;
|
||||
const mi = ctx.createGain(); // mod index in Hz
|
||||
mi.gain.setValueAtTime(freq * (1.2 + 4 * v), t);
|
||||
mi.gain.exponentialRampToValueAtTime(Math.max(1, freq * 0.3 * v), t + 0.22);
|
||||
mod.connect(mi); mi.connect(o.frequency);
|
||||
const f = ctx.createBiquadFilter(); f.type = "lowpass"; f.frequency.value = 900 + v * 4200;
|
||||
const g = ctx.createGain(); g.gain.value = 0;
|
||||
o.connect(f); f.connect(g); g.connect(N.preSat);
|
||||
g.gain.setValueAtTime(0, t);
|
||||
g.gain.linearRampToValueAtTime(0.22 * v, t + 0.006);
|
||||
g.gain.setTargetAtTime(0, t + 0.02, 0.38);
|
||||
o.start(t); o.stop(t + 2.4);
|
||||
o.start(t); mod.start(t); o.stop(t + 2.4); mod.stop(t + 2.4);
|
||||
}
|
||||
|
||||
const btn = document.createElement("div");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user