adding modulators

This commit is contained in:
Jade (Rose) Rowland 2024-03-04 23:17:58 -05:00
parent 1e3a23e017
commit bfcb2070d1
3 changed files with 136 additions and 15 deletions

View File

@ -186,3 +186,61 @@ export function getVibratoOscillator(param, value, t) {
return vibratoOscillator; 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);
}
}
}

View File

@ -1,6 +1,6 @@
import { clamp, midiToFreq, noteToMidi } from './util.mjs'; import { clamp, midiToFreq, noteToMidi } from './util.mjs';
import { registerSound, getAudioContext, getWorklet } from './superdough.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'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
const mod = (freq, range = 1, type = 'sine') => { const mod = (freq, range = 1, type = 'sine') => {
@ -21,9 +21,6 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => {
return mod(modfreq, modgain, wave); return mod(modfreq, modgain, wave);
}; };
const waveforms = ['triangle', 'square', 'sawtooth', 'sine'];
const noises = ['pink', 'white', 'brown', 'crackle'];
const getFrequencyFromValue = (value) => { const getFrequencyFromValue = (value) => {
let { note, freq } = value; let { note, freq } = value;
note = note || 36; note = note || 36;
@ -38,6 +35,62 @@ const getFrequencyFromValue = (value) => {
return Number(freq); 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() { export function registerSynthSounds() {
[...waveforms].forEach((s) => { [...waveforms].forEach((s) => {
registerSound( registerSound(
@ -102,15 +155,18 @@ export function registerSynthSounds() {
frequency, frequency,
begin, begin,
end, end,
detune: detune * 0.1, freqspread: detune * 0.1,
voices: clamp(unison, 0, 100), voices: clamp(unison, 0, 100),
spread: clamp(spread, 0, 1), panspread: clamp(spread, 0, 1),
}, },
{ {
outputChannelCount: [2], 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); const envGain = gainNode(1);
node = node.connect(envGain); node = node.connect(envGain);

View File

@ -169,16 +169,21 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
}, },
{ {
name: 'spread', name: 'panspread',
defaultValue: 0.4, defaultValue: 0.4,
min: 0, min: 0,
max: 1, max: 1,
}, },
{ {
name: 'detune', name: 'freqspread',
defaultValue: 0.2, defaultValue: 0.2,
min: 0, min: 0,
}, },
{
name: 'detune',
defaultValue: 0,
min: 0,
},
{ {
name: 'voices', name: 'voices',
@ -194,23 +199,25 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
if (currentTime >= params.end[0]) { if (currentTime >= params.end[0]) {
return false; 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 output = outputs[0];
const voices = params.voices[0]; const voices = params.voices[0];
const detune = params.detune[0]; const freqspread = params.freqspread[0];
let spread = params.spread[0]; let panspread = params.panspread[0];
spread = spread * 0.5 + 0.5; panspread = panspread * 0.5 + 0.5;
const gainAdjustment = 1; const gainAdjustment = 1;
for (let n = 0; n < voices; n++) { for (let n = 0; n < voices; n++) {
let adj = 0; let adj = 0;
const isOdd = n % 2 === 1; const isOdd = n % 2 === 1;
if (n > 0) { 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 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; const dt = freq / sampleRate;
for (let i = 0; i < output[0].length; i++) { for (let i = 0; i < output[0].length; i++) {