diff options
Diffstat (limited to 'tools/mq_editor/mq_synth.js')
| -rw-r--r-- | tools/mq_editor/mq_synth.js | 47 |
1 files changed, 29 insertions, 18 deletions
diff --git a/tools/mq_editor/mq_synth.js b/tools/mq_editor/mq_synth.js index e5f7e1a..1029626 100644 --- a/tools/mq_editor/mq_synth.js +++ b/tools/mq_editor/mq_synth.js @@ -26,22 +26,22 @@ function buildHarmonics(harmonics) { // Synthesize audio from MQ partials // partials: array of {freqCurve (with a0-a3 for amp), harmonics?, resonator?} -// harmonics: {decay, freq_mult, jitter, spread_above, spread_below} +// harmonics: {decay, freq_mult, jitter, spread} // resonator: {enabled, r, gainComp} — two-pole resonator mode per partial // integratePhase: true = accumulate 2π*f/SR per sample (correct for varying freq) // false = 2π*f*t (simpler, only correct for constant freq) // options.k1: LP coefficient in (0,1] — omit to bypass // options.k2: HP coefficient in (0,1] — omit to bypass +// options.disableJitter: true = suppress per-sample frequency jitter function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, options = {}) { const numSamples = Math.floor(sampleRate * duration); const pcm = new Float32Array(numSamples); const defaultHarmonics = { - decay: 0.0, - freq_mult: 1.0, - jitter: 0.05, - spread_above: 0.02, - spread_below: 0.02 + decay: 0.0, + freq_mult: 1.0, + jitter: 0.05, + spread: 0.02 }; // Pre-build per-partial configs with fixed spread/jitter and phase accumulators @@ -62,35 +62,38 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt : (res.gainComp != null ? res.gainComp : 1.0); const gainNorm = Math.sqrt(Math.max(0, 1.0 - r * r)); - // Build harmonic list (jitter/spread not applied to resonator) + // Build harmonic list (spread not applied to resonator; jitter modulates center freq) const harm = partial.harmonics || defaultHarmonics; const harmonicList = buildHarmonics(harm); + const jitter = options.disableJitter ? 0.0 : (harm.jitter ?? 0.0); configs.push({ mode: 'resonator', fc, r, gainComp, gainNorm, harmonicList, + jitter, y1: new Float64Array(harmonicList.length), y2: new Float64Array(harmonicList.length), - noiseSeed: ((p * 1664525 + 1013904223) & 0xFFFFFFFF) >>> 0 + noiseSeed: ((p * 1664525 + 1013904223) & 0xFFFFFFFF) >>> 0, + jitterSeed: ((p * 6364136223 + 1442695040) & 0xFFFFFFFF) >>> 0 }); } else { // --- Sinusoidal (harmonic) mode --- const harm = partial.harmonics || defaultHarmonics; - const spread_above = harm.spread_above ?? 0.0; - const spread_below = harm.spread_below ?? 0.0; - const jitter = harm.jitter ?? 0.0; + const spread = harm.spread ?? 0.0; + const jitter = options.disableJitter ? 0.0 : (harm.jitter ?? 0.0); const harmonicList = buildHarmonics(harm); const replicaData = []; for (let h = 0; h < harmonicList.length; ++h) { - const hc = harmonicList[h]; - const spread = randFloat(p * 67890 + h * 999, -spread_below, spread_above); - const initPhase = randFloat(p * 67890 + h, 0.0, 1.0) * jitter * 2.0 * Math.PI; - replicaData.push({ ratio: hc.ratio, ampMult: hc.ampMult, spread, phase: initPhase }); + const hc = harmonicList[h]; + const spreadVal = randFloat(p * 67890 + h * 999, -spread, spread); + const initPhase = randFloat(p * 67890 + h, 0.0, 1.0) * 2.0 * Math.PI; + const jitterSeed = ((p * 12345 + h * 67890 + 999) & 0xFFFFFFFF) >>> 0; + replicaData.push({ ratio: hc.ratio, ampMult: hc.ampMult, spread: spreadVal, phase: initPhase, jitterSeed }); } - configs.push({ mode: 'sinusoid', fc, replicaData }); + configs.push({ mode: 'sinusoid', fc, replicaData, jitter }); } } @@ -114,9 +117,14 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt cfg.noiseSeed = (Math.imul(1664525, cfg.noiseSeed) + 1013904223) >>> 0; const noise = cfg.noiseSeed / 0x100000000 * 2.0 - 1.0; + // Per-sample frequency jitter on resonator center freq + cfg.jitterSeed = (Math.imul(1664525, cfg.jitterSeed) + 1013904223) >>> 0; + const jNoise = cfg.jitterSeed / 0x100000000 * 2.0 - 1.0; + const f0j = f0 * (1.0 + jNoise * cfg.jitter); + for (let h = 0; h < cfg.harmonicList.length; ++h) { const hc = cfg.harmonicList[h]; - const fh = f0 * hc.ratio; + const fh = f0j * hc.ratio; const omega = 2.0 * Math.PI * fh / sampleRate; const b1 = 2.0 * cfg.r * Math.cos(omega); @@ -140,7 +148,10 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt let phase; if (integratePhase) { - rep.phase += 2.0 * Math.PI * f / sampleRate; + // Per-sample frequency jitter: ±jitter fraction of instantaneous freq + rep.jitterSeed = (Math.imul(1664525, rep.jitterSeed) + 1013904223) >>> 0; + const jNoise = rep.jitterSeed / 0x100000000 * 2.0 - 1.0; + rep.phase += 2.0 * Math.PI * f / sampleRate * (1.0 + jNoise * cfg.jitter); phase = rep.phase; } else { phase = 2.0 * Math.PI * f * t + rep.phase; |
