diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 1f49cf06..bbed050e 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -891,17 +891,37 @@ export const { delaytime, delayt, dt } = registerControl('delaytime', 'delayt', */ export const { lock } = registerControl('lock'); /** - * Set detune of oscillators. Works only with some synths, see tidal doc + * Set detune for stacked voices of supported oscillators * * @name detune - * @param {number | Pattern} amount between 0 and 1 + * @param {number | Pattern} amount * @synonyms det - * @superdirtOnly * @example - * n("0 3 7").s('superzow').octave(3).detune("<0 .25 .5 1 2>").osc() + * note("d f a a# a d3").fast(2).s("supersaw").detune("<.4 1 3 200>") * */ export const { detune, det } = registerControl('detune', 'det'); +/** + * Set number of stacked voices for supported oscillators + * + * @name unison + * @param {number | Pattern} numvoices + * @example + * note("d f a a# a d3").fast(2).s("supersaw").unison("<1 2 7>") + * + */ +export const { unison } = registerControl('unison'); + +/** + * Set the stereo pan spread for supported oscillators + * + * @name spread + * @param {number | Pattern} spread between 0 and 1 + * @example + * note("d f a a# a d3").fast(2).s("supersaw").spread("<0 .3 1>") + * + */ +export const { spread } = registerControl('spread'); /** * Set dryness of reverb. See `room` and `size` for more information about reverb. * diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index a0865103..05a2f572 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,4 +1,4 @@ -import { midiToFreq, noteToMidi } from './util.mjs'; +import { clamp, midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext, getWorklet } from './superdough.mjs'; import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; @@ -24,6 +24,20 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { const waveforms = ['triangle', 'square', 'sawtooth', 'sine']; const noises = ['pink', 'white', 'brown', 'crackle']; +const getFrequencyFromValue = (value) => { + let { note, freq } = value; + note = note || 36; + if (typeof note === 'string') { + note = noteToMidi(note); // e.g. c3 => 48 + } + // get frequency + if (!freq && typeof note === 'number') { + freq = midiToFreq(note); // + 48); + } + + return Number(freq); +}; + export function registerSynthSounds() { [...waveforms].forEach((s) => { registerSound( @@ -35,8 +49,7 @@ export function registerSynthSounds() { [0.001, 0.05, 0.6, 0.01], ); - let sound; - sound = getOscillator(s, t, value); + let sound = getOscillator(s, t, value); let { node: o, stop, triggerRelease } = sound; // turn down @@ -69,18 +82,9 @@ export function registerSynthSounds() { 'supersaw', (begin, value, onended) => { const ac = getAudioContext(); - let { note, freq, duration, n = 2 } = value; - note = note || 36; - if (typeof note === 'string') { - note = noteToMidi(note); // e.g. c3 => 48 - } - // get frequency - if (!freq && typeof note === 'number') { - freq = midiToFreq(note); // + 48); - } - - // set frequency - freq = Number(freq); + let { duration, n, unison = 6, spread = 0.3, detune } = value; + detune = detune ?? n ?? 2; + const frequency = getFrequencyFromValue(value); const [attack, decay, sustain, release] = getADSRValues( [value.attack, value.decay, value.sustain, value.release], @@ -95,12 +99,12 @@ export function registerSynthSounds() { ac, 'supersaw-oscillator', { - frequency: freq, + frequency, begin, end, - detune: Math.min(200, Math.max(0, n * 0.1)), - // voices: n, - // spread: 0, + detune: detune * 0.1, + voices: clamp(unison, 0, 100), + spread: clamp(spread, 0, 1), }, { outputChannelCount: [2], diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index add1f607..b98f7768 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -184,7 +184,6 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { name: 'voices', defaultValue: 6, min: 1, - max: 10, }, ]; } @@ -202,7 +201,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { const detune = params.detune[0]; let spread = params.spread[0]; spread = spread * 0.5 + 0.5; - const gainAdjustment = Math.max(0.75, 1 - (voices - 1) * 0.05); + const gainAdjustment = 1; for (let n = 0; n < voices; n++) { let adj = 0; @@ -210,7 +209,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { if (n > 0) { adj = isOdd ? n * detune : -((n - 1) * detune); } - const freq = 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 dt = freq / sampleRate;