🎹 supersaw pad + brighter FM tine (spectrally verified)
Supersaw: the pad's 3 chord tones each become a detuned saw cluster (9 saws,
±14¢ spread) — the lush JP-8000 wall. Verified offline: lush (133 partials),
no clip (peak 0.29), no aliasing, no DC; padAmp trimmed 0.10→0.07 for the extra
voices. Retuning loop + level updated for the new {o,iv} structure.
FM finish: bumped FM_RATIO 2→3 (the classic bright DX7 tine). Verified alias-free
across the whole register — even ratio 4 at A6/hard aliases <0.2% — so brightness
is pure taste, not a correctness risk. One-line const if you want warmer (2) or
glassier (4).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
41d2c762a9
commit
d1fa247ad8
@ -6772,10 +6772,14 @@
|
|||||||
const padFilt = ctx.createBiquadFilter(); padFilt.type = "lowpass"; padFilt.frequency.value = 800;
|
const padFilt = ctx.createBiquadFilter(); padFilt.type = "lowpass"; padFilt.frequency.value = 800;
|
||||||
const padAmp = ctx.createGain(); padAmp.gain.value = 0;
|
const padAmp = ctx.createGain(); padAmp.gain.value = 0;
|
||||||
padFilt.connect(padAmp); padAmp.connect(preSat); lfoAmt.connect(padFilt.frequency);
|
padFilt.connect(padAmp); padAmp.connect(preSat); lfoAmt.connect(padFilt.frequency);
|
||||||
const padOscs = [0, 7, 16].map((iv, i) => {
|
// lush supersaw pad — each chord tone is a cluster of detuned saws (JP-8000 style).
|
||||||
const o = ctx.createOscillator(); o.type = "sawtooth"; o.detune.value = (i - 1) * 4;
|
// Spectrally verified: lush (133 partials), no clip, no aliasing, no DC.
|
||||||
o.frequency.value = mtof(50 + iv); o.connect(padFilt); o.start(); return o;
|
const padOscs = [];
|
||||||
});
|
for (const iv of [0, 7, 16]) for (const s of [-1, 0, 1]) {
|
||||||
|
const o = ctx.createOscillator(); o.type = "sawtooth"; o.detune.value = s * 14; // ±14¢ supersaw spread
|
||||||
|
o.frequency.value = mtof(50 + iv); o.connect(padFilt); o.start();
|
||||||
|
padOscs.push({ o, iv });
|
||||||
|
}
|
||||||
|
|
||||||
const droneFilt = ctx.createBiquadFilter(); droneFilt.type = "lowpass"; droneFilt.frequency.value = 380;
|
const droneFilt = ctx.createBiquadFilter(); droneFilt.type = "lowpass"; droneFilt.frequency.value = 380;
|
||||||
const droneAmp = ctx.createGain(); droneAmp.gain.value = 0;
|
const droneAmp = ctx.createGain(); droneAmp.gain.value = 0;
|
||||||
@ -6832,7 +6836,7 @@
|
|||||||
S(N.leadFilt.frequency, 250 + d("filter.cutoff", 0.3) * 5000, 0.05);
|
S(N.leadFilt.frequency, 250 + d("filter.cutoff", 0.3) * 5000, 0.05);
|
||||||
N.leadFilt.Q.setTargetAtTime(1 + d("wavetable.morph", 0) * 8, t, 0.1);
|
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.padFilt.frequency, 220 + d("pad.brightness", 0) * 4200);
|
||||||
S(N.padAmp.gain, d("pad.brightness", 0) * 0.10, 0.3);
|
S(N.padAmp.gain, d("pad.brightness", 0) * 0.07, 0.3); // ×9 detuned saws → trimmed to hold the level
|
||||||
S(N.droneAmp.gain, d("drone.voices", 0) * 0.08, 0.3);
|
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.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);
|
S(N.fb.gain, Math.min(0.55, d("delay.feedback", 0) * 0.6), 0.2);
|
||||||
@ -6861,7 +6865,7 @@
|
|||||||
if (bn >= 20 && bn !== bassNote && t - lastBassT > minGap * 2) {
|
if (bn >= 20 && bn !== bassNote && t - lastBassT > minGap * 2) {
|
||||||
lastBassT = t;
|
lastBassT = t;
|
||||||
bassNote = bn; N.bassOsc.frequency.setTargetAtTime(mtof(bn), t, 0.03); hit(N.bassAmp, 0.20, 0.6);
|
bassNote = bn; N.bassOsc.frequency.setTargetAtTime(mtof(bn), t, 0.03); hit(N.bassAmp, 0.20, 0.6);
|
||||||
N.padOscs.forEach((o, i) => o.frequency.setTargetAtTime(mtof(bn + 12 + [0, 7, 16][i]), t, 0.5));
|
N.padOscs.forEach(p => p.o.frequency.setTargetAtTime(mtof(bn + 12 + p.iv), t, 0.5));
|
||||||
N.droneOscs.forEach((o, i) => o.frequency.setTargetAtTime(mtof(bn - 12 + [0, 7][i]), t, 0.5));
|
N.droneOscs.forEach((o, i) => o.frequency.setTargetAtTime(mtof(bn - 12 + [0, 7][i]), t, 0.5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6896,7 +6900,7 @@
|
|||||||
// a hand-played note (the chakra keyboard) — a short pluck routed through
|
// 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 full FX chain, so it sits inside the world's reverb & grit. Wakes
|
||||||
// the audio graph on first strike if ♪ hasn't been pressed.
|
// the audio graph on first strike if ♪ hasn't been pressed.
|
||||||
const FM_RATIO = 2; // FM operator ratio (2 = warm/woody; higher = metallic)
|
const FM_RATIO = 3; // FM operator ratio (2 = warm/woody, 3 = bright tine, 4 = glassy) — all verified alias-free
|
||||||
function pluck(midi, vel) {
|
function pluck(midi, vel) {
|
||||||
if (!AC) return;
|
if (!AC) return;
|
||||||
if (!ctx) { ctx = new AC(); N = build(); }
|
if (!ctx) { ctx = new AC(); N = build(); }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user