From bfcb2070d1bdb023258f1bf8e87080f1ebdf2233 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Mon, 4 Mar 2024 23:17:58 -0500 Subject: [PATCH] adding modulators --- packages/superdough/helpers.mjs | 58 ++++++++++++++++++++++++++ packages/superdough/synth.mjs | 70 ++++++++++++++++++++++++++++---- packages/superdough/worklets.mjs | 23 +++++++---- 3 files changed, 136 insertions(+), 15 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 39e9977a..01fc9bf2 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -186,3 +186,61 @@ export function getVibratoOscillator(param, value, t) { return vibratoOscillator; } } +const mod = (freq, range = 1, type = 'sine') => { + const ctx = getAudioContext(); + const osc = ctx.createOscillator(); + osc.type = type; + osc.frequency.value = freq; + osc.start(); + const g = new GainNode(ctx, { gain: range }); + osc.connect(g); // -range, range + return { node: g, stop: (t) => osc.stop(t) }; +}; +const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => { + const carrfreq = frequencyparam.value; + const modfreq = carrfreq * harmonicityRatio; + const modgain = modfreq * modulationIndex; + return mod(modfreq, modgain, wave); +}; +export function applyFM(param, value) { + const { + fmh: fmHarmonicity = 1, + fmi: fmModulationIndex, + fmenv: fmEnvelopeType = 'exp', + fmattack: fmAttack, + fmdecay: fmDecay, + fmsustain: fmSustain, + fmrelease: fmRelease, + fmvelocity: fmVelocity, + fmwave: fmWaveform = 'sine', + duration, + begin, + } = value; + + const ac = getAudioContext(); + if (fmModulationIndex) { + const envGain = ac.createGain(); + const { node: modulator } = fm(param, fmHarmonicity, fmModulationIndex, fmWaveform); + if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { + // no envelope by default + modulator.connect(param.frequency); + } else { + const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]); + const holdEnd = begin + duration; + getParamADSR( + envGain.gain, + attack, + decay, + sustain, + release, + 0, + 1, + begin, + holdEnd, + fmEnvelopeType === 'exp' ? 'exponential' : 'linear', + ); + modulator.connect(envGain); + envGain.connect(param); + } + } +} diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 05a2f572..558112ba 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,6 +1,6 @@ import { clamp, midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext, getWorklet } from './superdough.mjs'; -import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; +import { applyFM, gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; const mod = (freq, range = 1, type = 'sine') => { @@ -21,9 +21,6 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { return mod(modfreq, modgain, wave); }; -const waveforms = ['triangle', 'square', 'sawtooth', 'sine']; -const noises = ['pink', 'white', 'brown', 'crackle']; - const getFrequencyFromValue = (value) => { let { note, freq } = value; note = note || 36; @@ -38,6 +35,62 @@ const getFrequencyFromValue = (value) => { return Number(freq); }; +const waveforms = ['triangle', 'square', 'sawtooth', 'sine']; +const noises = ['pink', 'white', 'brown', 'crackle']; + +const applyModulators = (node, value) => { + let { + n: partials, + note, + freq, + noise = 0, + // fm + fmh: fmHarmonicity = 1, + fmi: fmModulationIndex, + fmenv: fmEnvelopeType = 'exp', + fmattack: fmAttack, + fmdecay: fmDecay, + fmsustain: fmSustain, + fmrelease: fmRelease, + fmvelocity: fmVelocity, + fmwave: fmWaveform = 'sine', + duration, + begin: t, + } = value; + + const ac = getAudioContext(); + if (fmModulationIndex) { + let envGain = ac.createGain(); + const { node: modulator, stop } = fm(node, fmHarmonicity, fmModulationIndex, fmWaveform); + if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { + // no envelope by default + modulator.connect(node.frequency); + } else { + const [attack, decay, sustain, release] = getADSRValues([fmAttack, fmDecay, fmSustain, fmRelease]); + const holdEnd = t + duration; + getParamADSR( + envGain.gain, + attack, + decay, + sustain, + release, + 0, + 1, + t, + holdEnd, + fmEnvelopeType === 'exp' ? 'exponential' : 'linear', + ); + modulator.connect(envGain); + envGain.connect(node.frequency); + } + } + + getVibratoOscillator(node.detune, value, t); + getPitchEnvelope(node.detune, value, t, t + duration); + + return node; +}; + export function registerSynthSounds() { [...waveforms].forEach((s) => { registerSound( @@ -102,15 +155,18 @@ export function registerSynthSounds() { frequency, begin, end, - detune: detune * 0.1, + freqspread: detune * 0.1, voices: clamp(unison, 0, 100), - spread: clamp(spread, 0, 1), + panspread: clamp(spread, 0, 1), }, { outputChannelCount: [2], }, ); - + // console.log(node.parameters.get('frequency')); + getPitchEnvelope(node.parameters.get('detune'), value, begin, holdend); + getVibratoOscillator(node.parameters.get('detune'), value, begin); + // applyFM(node.parameters.get('frequency')); const envGain = gainNode(1); node = node.connect(envGain); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index b98f7768..ab8b9c07 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -169,16 +169,21 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { }, { - name: 'spread', + name: 'panspread', defaultValue: 0.4, min: 0, max: 1, }, { - name: 'detune', + name: 'freqspread', defaultValue: 0.2, min: 0, }, + { + name: 'detune', + defaultValue: 0, + min: 0, + }, { name: 'voices', @@ -194,23 +199,25 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { if (currentTime >= params.end[0]) { return false; } - const frequency = params.frequency[0]; + let frequency = params.frequency[0]; + //apply detune in cents + frequency = frequency * Math.pow(2, params.detune[0] / 1200); const output = outputs[0]; const voices = params.voices[0]; - const detune = params.detune[0]; - let spread = params.spread[0]; - spread = spread * 0.5 + 0.5; + const freqspread = params.freqspread[0]; + let panspread = params.panspread[0]; + panspread = panspread * 0.5 + 0.5; const gainAdjustment = 1; for (let n = 0; n < voices; n++) { let adj = 0; const isOdd = n % 2 === 1; if (n > 0) { - adj = isOdd ? n * detune : -((n - 1) * detune); + adj = isOdd ? n * freqspread : -((n - 1) * freqspread); } const freq = Math.min(16744, Math.max(1, frequency + adj * 0.01 * frequency)); - const balance = isOdd ? 1 - spread : spread; + const balance = isOdd ? 1 - panspread : panspread; const dt = freq / sampleRate; for (let i = 0; i < output[0].length; i++) {