From c2481e460b5c455662999fd61a8d2b9ec1ffb22c Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sat, 30 Sep 2023 14:07:33 +0200 Subject: [PATCH 01/13] Add pink, white and brown oscillators --- packages/superdough/synth.mjs | 286 ++++++++++++++++++++-------------- 1 file changed, 169 insertions(+), 117 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 633d0113d..3ab3720ff 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -20,100 +20,102 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { return mod(modfreq, modgain, wave); }; + export function registerSynthSounds() { - ['sine', 'square', 'triangle', 'sawtooth'].forEach((wave) => { - registerSound( - wave, - (t, value, onended) => { - // destructure adsr here, because the default should be different for synths and samples - let { - attack = 0.001, - decay = 0.05, - sustain = 0.6, - release = 0.01, - fmh: fmHarmonicity = 1, - fmi: fmModulationIndex, - fmenv: fmEnvelopeType = 'lin', - fmattack: fmAttack, - fmdecay: fmDecay, - fmsustain: fmSustain, - fmrelease: fmRelease, - fmvelocity: fmVelocity, - fmwave: fmWaveform = 'sine', - vib = 0, - vibmod = 0.5, - } = value; - let { n, note, freq } = value; - // with synths, n and note are the same thing - 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); - } - // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) - // make oscillator - const { node: o, stop } = getOscillator({ - t, - s: wave, - freq, - vib, - vibmod, - partials: n, - }); - - // FM + FM envelope - let stopFm, fmEnvelope; - if (fmModulationIndex) { - const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform); - if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { - // no envelope by default - modulator.connect(o.frequency); - } else { - fmAttack = fmAttack ?? 0.001; - fmDecay = fmDecay ?? 0.001; - fmSustain = fmSustain ?? 1; - fmRelease = fmRelease ?? 0.001; - fmVelocity = fmVelocity ?? 1; - fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); - if (fmEnvelopeType === 'exp') { - fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); - fmEnvelope.node.maxValue = fmModulationIndex * 2; - fmEnvelope.node.minValue = 0.00001; + ['sine', 'square', 'triangle', + 'sawtooth', 'pink', 'white', + 'brown'].forEach((wave) => { + registerSound( + wave, + (t, value, onended) => { + // destructure adsr here, because the default should be different for synths and samples + let { + attack = 0.001, + decay = 0.05, + sustain = 0.6, + release = 0.01, + fmh: fmHarmonicity = 1, + fmi: fmModulationIndex, + fmenv: fmEnvelopeType = 'lin', + fmattack: fmAttack, + fmdecay: fmDecay, + fmsustain: fmSustain, + fmrelease: fmRelease, + fmvelocity: fmVelocity, + fmwave: fmWaveform = 'sine', + vib = 0, + vibmod = 0.5, + } = value; + let { n, note, freq } = value; + // with synths, n and note are the same thing + 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); + } + // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) + // make oscillator + const { node: o, stop } = getOscillator({ + t, + s: wave, + freq, + vib, + vibmod, + partials: n, + }); + // FM + FM envelope + let stopFm, fmEnvelope; + if (fmModulationIndex) { + const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform); + if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { + // no envelope by default + modulator.connect(o.frequency); + } else { + fmAttack = fmAttack ?? 0.001; + fmDecay = fmDecay ?? 0.001; + fmSustain = fmSustain ?? 1; + fmRelease = fmRelease ?? 0.001; + fmVelocity = fmVelocity ?? 1; + fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); + if (fmEnvelopeType === 'exp') { + fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); + fmEnvelope.node.maxValue = fmModulationIndex * 2; + fmEnvelope.node.minValue = 0.00001; + } + modulator.connect(fmEnvelope.node); + fmEnvelope.node.connect(o.frequency); } - modulator.connect(fmEnvelope.node); - fmEnvelope.node.connect(o.frequency); + stopFm = stop; } - stopFm = stop; - } - - // turn down - const g = gainNode(0.3); - - // gain envelope - const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); - - o.onended = () => { - o.disconnect(); - g.disconnect(); - onended(); - }; - return { - node: o.connect(g).connect(envelope), - stop: (releaseTime) => { - releaseEnvelope(releaseTime); - fmEnvelope?.stop(releaseTime); - let end = releaseTime + release; - stop(end); - stopFm?.(end); - }, - }; - }, - { type: 'synth', prebake: true }, - ); - }); + + // turn down + const g = gainNode(0.3); + + // gain envelope + const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); + + o.onended = () => { + o.disconnect(); + g.disconnect(); + onended(); + }; + return { + node: o.connect(g).connect(envelope), + stop: (releaseTime) => { + releaseEnvelope(releaseTime); + fmEnvelope?.stop(releaseTime); + let end = releaseTime + release; + stop(end); + stopFm?.(end); + }, + }; + }, + { type: 'synth', prebake: true }, + ); + }); } export function waveformN(partials, type) { @@ -146,36 +148,86 @@ export function waveformN(partials, type) { return osc; } -export function getOscillator({ s, freq, t, vib, vibmod, partials }) { - // Make oscillator with partial count - let o; - if (!partials || s === 'sine') { - o = getAudioContext().createOscillator(); - o.type = s || 'triangle'; - } else { - o = waveformN(partials, s); - } - o.frequency.value = Number(freq); - o.start(t); +export function getNoiseOscillator({ t, ac, type = 'white' }) { + const bufferSize = 2 * ac.sampleRate; + const noiseBuffer = ac.createBuffer(1, bufferSize, ac.sampleRate); + const output = noiseBuffer.getChannelData(0); + let lastOut = 0; + let b0, b1, b2, b3, b4, b5, b6; + b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0; - // Additional oscillator for vibrato effect - let vibrato_oscillator; - if (vib > 0) { - vibrato_oscillator = getAudioContext().createOscillator(); - vibrato_oscillator.frequency.value = vib; - const gain = getAudioContext().createGain(); - // Vibmod is the amount of vibrato, in semitones - gain.gain.value = vibmod * 100; - vibrato_oscillator.connect(gain); - gain.connect(o.detune); - vibrato_oscillator.start(t); + for (let i = 0; i < bufferSize; i++) { + if (type === 'white') { + output[i] = Math.random() * 2 - 1; + } else if (type === 'brown') { + let white = Math.random() * 2 - 1; + output[i] = (lastOut + (0.02 * white)) / 1.02; + lastOut = output[i]; + } else if (type === 'pink') { + let white = Math.random() * 2 - 1; + b0 = 0.99886 * b0 + white * 0.0555179; + b1 = 0.99332 * b1 + white * 0.0750759; + b2 = 0.96900 * b2 + white * 0.1538520; + b3 = 0.86650 * b3 + white * 0.3104856; + b4 = 0.55000 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.0168980; + output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; + output[i] *= 0.11; + b6 = white * 0.115926; + } } + const o = ac.createBufferSource(); + o.buffer = noiseBuffer; + o.loop = true; + o.start(t); + return { node: o, - stop: (time) => { - vibrato_oscillator?.stop(time); - o.stop(time); - }, + stop: (time) => o.stop(time) }; } + +export function getOscillator({ s, freq, t, vib, vibmod, partials }) { + // Make oscillator with partial count + let o; + + if (['pink', 'white', 'brown'].includes(s)) { + let noiseOscillator = getNoiseOscillator({ t: t, ac: getAudioContext(), type: s }) + return { + node: noiseOscillator.node, + stop: noiseOscillator.stop + } + } else { + if (!partials || s === 'sine') { + o = getAudioContext().createOscillator(); + o.type = s || 'triangle'; + } else { + o = waveformN(partials, s); + } + o.frequency.value = Number(freq); + o.start(t); + + // Additional oscillator for vibrato effect + let vibrato_oscillator; + if (vib > 0) { + vibrato_oscillator = getAudioContext().createOscillator(); + vibrato_oscillator.frequency.value = vib; + const gain = getAudioContext().createGain(); + // Vibmod is the amount of vibrato, in semitones + gain.gain.value = vibmod * 100; + vibrato_oscillator.connect(gain); + gain.connect(o.detune); + vibrato_oscillator.start(t); + } + + return { + node: o, + stop: (time) => { + vibrato_oscillator?.stop(time); + o.stop(time); + }, + }; + } +} + From 389c7be264c22fd7ce7fb2374dad9936ad4ffc15 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sat, 30 Sep 2023 14:39:44 +0200 Subject: [PATCH 02/13] Add noise parameter for base oscillators --- packages/superdough/synth.mjs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 3ab3720ff..aa7b63ee3 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -45,6 +45,7 @@ export function registerSynthSounds() { fmwave: fmWaveform = 'sine', vib = 0, vibmod = 0.5, + noise = 0, } = value; let { n, note, freq } = value; // with synths, n and note are the same thing @@ -65,6 +66,7 @@ export function registerSynthSounds() { vib, vibmod, partials: n, + noise: noise, }); // FM + FM envelope let stopFm, fmEnvelope; @@ -188,8 +190,9 @@ export function getNoiseOscillator({ t, ac, type = 'white' }) { }; } -export function getOscillator({ s, freq, t, vib, vibmod, partials }) { +export function getOscillator({ s, freq, t, vib, vibmod, partials, noise }) { // Make oscillator with partial count + let ac = getAudioContext(); let o; if (['pink', 'white', 'brown'].includes(s)) { @@ -221,6 +224,33 @@ export function getOscillator({ s, freq, t, vib, vibmod, partials }) { vibrato_oscillator.start(t); } + if (noise > 0) { + // Two gain nodes to set the oscillators to their respective levels + let o_gain = ac.createGain(); + let n_gain = ac.createGain(); + o_gain.gain.setValueAtTime(1 - noise, ac.currentTime); + n_gain.gain.setValueAtTime(noise, ac.currentTime); + + // Instanciating a mixer to blend sources together + let mix_gain = ac.createGain(); + + // Connecting the main oscillator to the gain node + o.connect(o_gain).connect(mix_gain); + + // Instanciating a noise oscillator and connecting + const noiseOscillator = getNoiseOscillator({ t: t, ac: ac, type: 'pink' }); + noiseOscillator.node.connect(n_gain).connect(mix_gain); + + return { + node: mix_gain, + stop: (time) => { + vibrato_oscillator?.stop(time); + o.stop(time); + noiseOscillator.stop(time); + } + } + } + return { node: o, stop: (time) => { From bb7b8c2fabe72b520e9e312e2d2d02c8cb2e17c9 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sat, 30 Sep 2023 14:59:43 +0200 Subject: [PATCH 03/13] Fix noise parameter and FM parameters compatibility --- packages/superdough/synth.mjs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index aa7b63ee3..cd523de8a 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -59,7 +59,7 @@ export function registerSynthSounds() { } // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) // make oscillator - const { node: o, stop } = getOscillator({ + const { node: o, stop, dry_node = null } = getOscillator({ t, s: wave, freq, @@ -71,10 +71,10 @@ export function registerSynthSounds() { // FM + FM envelope let stopFm, fmEnvelope; if (fmModulationIndex) { - const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform); + const { node: modulator, stop } = fm(dry_node !== null ? dry_node : o, fmHarmonicity, fmModulationIndex, fmWaveform); if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { // no envelope by default - modulator.connect(o.frequency); + modulator.connect(dry_node !== null ? dry_node.frequency : o.frequency); } else { fmAttack = fmAttack ?? 0.001; fmDecay = fmDecay ?? 0.001; @@ -88,7 +88,7 @@ export function registerSynthSounds() { fmEnvelope.node.minValue = 0.00001; } modulator.connect(fmEnvelope.node); - fmEnvelope.node.connect(o.frequency); + fmEnvelope.node.connect(dry_node !== null ? dry_node.frequency : o.frequency); } stopFm = stop; } @@ -243,6 +243,7 @@ export function getOscillator({ s, freq, t, vib, vibmod, partials, noise }) { return { node: mix_gain, + dry_node: o, stop: (time) => { vibrato_oscillator?.stop(time); o.stop(time); From e3333e716fc6b30da5a792eced45a5487dd59a35 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sat, 30 Sep 2023 15:08:09 +0200 Subject: [PATCH 04/13] Cap noise amount to 1 --- packages/superdough/synth.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index cd523de8a..e5f84bcf7 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -226,6 +226,7 @@ export function getOscillator({ s, freq, t, vib, vibmod, partials, noise }) { if (noise > 0) { // Two gain nodes to set the oscillators to their respective levels + noise = noise > 1 ? 1 : noise; let o_gain = ac.createGain(); let n_gain = ac.createGain(); o_gain.gain.setValueAtTime(1 - noise, ac.currentTime); From b2acff40c4354b5390334fcd9ec83b5aeacd2502 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Sat, 30 Sep 2023 15:17:22 +0200 Subject: [PATCH 05/13] Add documentation about noise sources --- website/src/pages/learn/synths.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index 9f21204fa..661ec860d 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -23,6 +23,23 @@ The basic waveforms are `sine`, `sawtooth`, `square` and `triangle`, which can b If you don't set a `sound` but a `note` the default value for `sound` is `triangle`! +You can also use noise as a source by setting the waveform to: `white`, `pink` or `brown`. These are different +flavours of noise, here written from hard to soft. + +>") +.sound("/2") +.scope()`} +/> + +Some amount of pink noise can also be added to any oscillator by using the `noise` paremeter: + +").scope()`} +/> + ### Additive Synthesis To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform: From 1e352fdf8001696b243cdb7f7fc777e34b151eb5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 3 Oct 2023 08:51:35 +0200 Subject: [PATCH 06/13] codeformat --- packages/superdough/synth.mjs | 215 +++++++++++++++-------------- website/src/pages/learn/synths.mdx | 5 +- 2 files changed, 111 insertions(+), 109 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index e5f84bcf7..51d569937 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -20,104 +20,110 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { return mod(modfreq, modgain, wave); }; - export function registerSynthSounds() { - ['sine', 'square', 'triangle', - 'sawtooth', 'pink', 'white', - 'brown'].forEach((wave) => { - registerSound( - wave, - (t, value, onended) => { - // destructure adsr here, because the default should be different for synths and samples - let { - attack = 0.001, - decay = 0.05, - sustain = 0.6, - release = 0.01, - fmh: fmHarmonicity = 1, - fmi: fmModulationIndex, - fmenv: fmEnvelopeType = 'lin', - fmattack: fmAttack, - fmdecay: fmDecay, - fmsustain: fmSustain, - fmrelease: fmRelease, - fmvelocity: fmVelocity, - fmwave: fmWaveform = 'sine', - vib = 0, - vibmod = 0.5, - noise = 0, - } = value; - let { n, note, freq } = value; - // with synths, n and note are the same thing - 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); - } - // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) - // make oscillator - const { node: o, stop, dry_node = null } = getOscillator({ - t, - s: wave, - freq, - vib, - vibmod, - partials: n, - noise: noise, - }); - // FM + FM envelope - let stopFm, fmEnvelope; - if (fmModulationIndex) { - const { node: modulator, stop } = fm(dry_node !== null ? dry_node : o, fmHarmonicity, fmModulationIndex, fmWaveform); - if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { - // no envelope by default - modulator.connect(dry_node !== null ? dry_node.frequency : o.frequency); - } else { - fmAttack = fmAttack ?? 0.001; - fmDecay = fmDecay ?? 0.001; - fmSustain = fmSustain ?? 1; - fmRelease = fmRelease ?? 0.001; - fmVelocity = fmVelocity ?? 1; - fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); - if (fmEnvelopeType === 'exp') { - fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); - fmEnvelope.node.maxValue = fmModulationIndex * 2; - fmEnvelope.node.minValue = 0.00001; - } - modulator.connect(fmEnvelope.node); - fmEnvelope.node.connect(dry_node !== null ? dry_node.frequency : o.frequency); + ['sine', 'square', 'triangle', 'sawtooth', 'pink', 'white', 'brown'].forEach((wave) => { + registerSound( + wave, + (t, value, onended) => { + // destructure adsr here, because the default should be different for synths and samples + let { + attack = 0.001, + decay = 0.05, + sustain = 0.6, + release = 0.01, + fmh: fmHarmonicity = 1, + fmi: fmModulationIndex, + fmenv: fmEnvelopeType = 'lin', + fmattack: fmAttack, + fmdecay: fmDecay, + fmsustain: fmSustain, + fmrelease: fmRelease, + fmvelocity: fmVelocity, + fmwave: fmWaveform = 'sine', + vib = 0, + vibmod = 0.5, + noise = 0, + } = value; + let { n, note, freq } = value; + // with synths, n and note are the same thing + 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); + } + // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) + // make oscillator + const { + node: o, + stop, + dry_node = null, + } = getOscillator({ + t, + s: wave, + freq, + vib, + vibmod, + partials: n, + noise: noise, + }); + // FM + FM envelope + let stopFm, fmEnvelope; + if (fmModulationIndex) { + const { node: modulator, stop } = fm( + dry_node !== null ? dry_node : o, + fmHarmonicity, + fmModulationIndex, + fmWaveform, + ); + if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { + // no envelope by default + modulator.connect(dry_node !== null ? dry_node.frequency : o.frequency); + } else { + fmAttack = fmAttack ?? 0.001; + fmDecay = fmDecay ?? 0.001; + fmSustain = fmSustain ?? 1; + fmRelease = fmRelease ?? 0.001; + fmVelocity = fmVelocity ?? 1; + fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); + if (fmEnvelopeType === 'exp') { + fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); + fmEnvelope.node.maxValue = fmModulationIndex * 2; + fmEnvelope.node.minValue = 0.00001; } - stopFm = stop; + modulator.connect(fmEnvelope.node); + fmEnvelope.node.connect(dry_node !== null ? dry_node.frequency : o.frequency); } + stopFm = stop; + } - // turn down - const g = gainNode(0.3); + // turn down + const g = gainNode(0.3); - // gain envelope - const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); + // gain envelope + const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); - o.onended = () => { - o.disconnect(); - g.disconnect(); - onended(); - }; - return { - node: o.connect(g).connect(envelope), - stop: (releaseTime) => { - releaseEnvelope(releaseTime); - fmEnvelope?.stop(releaseTime); - let end = releaseTime + release; - stop(end); - stopFm?.(end); - }, - }; - }, - { type: 'synth', prebake: true }, - ); - }); + o.onended = () => { + o.disconnect(); + g.disconnect(); + onended(); + }; + return { + node: o.connect(g).connect(envelope), + stop: (releaseTime) => { + releaseEnvelope(releaseTime); + fmEnvelope?.stop(releaseTime); + let end = releaseTime + release; + stop(end); + stopFm?.(end); + }, + }; + }, + { type: 'synth', prebake: true }, + ); + }); } export function waveformN(partials, type) { @@ -163,16 +169,16 @@ export function getNoiseOscillator({ t, ac, type = 'white' }) { output[i] = Math.random() * 2 - 1; } else if (type === 'brown') { let white = Math.random() * 2 - 1; - output[i] = (lastOut + (0.02 * white)) / 1.02; + output[i] = (lastOut + 0.02 * white) / 1.02; lastOut = output[i]; } else if (type === 'pink') { let white = Math.random() * 2 - 1; b0 = 0.99886 * b0 + white * 0.0555179; b1 = 0.99332 * b1 + white * 0.0750759; - b2 = 0.96900 * b2 + white * 0.1538520; - b3 = 0.86650 * b3 + white * 0.3104856; - b4 = 0.55000 * b4 + white * 0.5329522; - b5 = -0.7616 * b5 - white * 0.0168980; + b2 = 0.969 * b2 + white * 0.153852; + b3 = 0.8665 * b3 + white * 0.3104856; + b4 = 0.55 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.016898; output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; output[i] *= 0.11; b6 = white * 0.115926; @@ -186,7 +192,7 @@ export function getNoiseOscillator({ t, ac, type = 'white' }) { return { node: o, - stop: (time) => o.stop(time) + stop: (time) => o.stop(time), }; } @@ -196,11 +202,11 @@ export function getOscillator({ s, freq, t, vib, vibmod, partials, noise }) { let o; if (['pink', 'white', 'brown'].includes(s)) { - let noiseOscillator = getNoiseOscillator({ t: t, ac: getAudioContext(), type: s }) + let noiseOscillator = getNoiseOscillator({ t: t, ac: getAudioContext(), type: s }); return { node: noiseOscillator.node, - stop: noiseOscillator.stop - } + stop: noiseOscillator.stop, + }; } else { if (!partials || s === 'sine') { o = getAudioContext().createOscillator(); @@ -238,7 +244,7 @@ export function getOscillator({ s, freq, t, vib, vibmod, partials, noise }) { // Connecting the main oscillator to the gain node o.connect(o_gain).connect(mix_gain); - // Instanciating a noise oscillator and connecting + // Instanciating a noise oscillator and connecting const noiseOscillator = getNoiseOscillator({ t: t, ac: ac, type: 'pink' }); noiseOscillator.node.connect(n_gain).connect(mix_gain); @@ -249,8 +255,8 @@ export function getOscillator({ s, freq, t, vib, vibmod, partials, noise }) { vibrato_oscillator?.stop(time); o.stop(time); noiseOscillator.stop(time); - } - } + }, + }; } return { @@ -262,4 +268,3 @@ export function getOscillator({ s, freq, t, vib, vibmod, partials, noise }) { }; } } - diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index 661ec860d..83de3ca57 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -35,10 +35,7 @@ flavours of noise, here written from hard to soft. Some amount of pink noise can also be added to any oscillator by using the `noise` paremeter: -").scope()`} -/> +").scope()`} /> ### Additive Synthesis From a0884e2a038c654a722647237f5e760810ddcf63 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 3 Oct 2023 09:09:49 +0200 Subject: [PATCH 07/13] add noise heading + hihat example --- website/src/pages/learn/synths.mdx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index 83de3ca57..432276ef4 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -23,14 +23,19 @@ The basic waveforms are `sine`, `sawtooth`, `square` and `triangle`, which can b If you don't set a `sound` but a `note` the default value for `sound` is `triangle`! +## Noise + You can also use noise as a source by setting the waveform to: `white`, `pink` or `brown`. These are different flavours of noise, here written from hard to soft. +/2").scope()`} /> + +Here's a more musical example of how to use noise for hihats: + >") -.sound("/2") -.scope()`} + tune={`sound("bd*2,*8") +.decay(.04).sustain(0).scope()`} /> Some amount of pink noise can also be added to any oscillator by using the `noise` paremeter: From 484bb6b11f7c79220cc9bd0c8f5056a88232d90a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 3 Oct 2023 10:03:09 +0200 Subject: [PATCH 08/13] refactor synth - separate waveform / noise oscillators - pull noise out of getOscillator - put fm into getOscillator - simplify overall value plumbing --- packages/superdough/synth.mjs | 264 ++++++++++++++++------------------ 1 file changed, 127 insertions(+), 137 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 51d569937..f12be9c84 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -20,85 +20,26 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { return mod(modfreq, modgain, wave); }; +const waveforms = ['sine', 'square', 'triangle', 'sawtooth']; +const noises = ['pink', 'white', 'brown']; + export function registerSynthSounds() { - ['sine', 'square', 'triangle', 'sawtooth', 'pink', 'white', 'brown'].forEach((wave) => { + [...waveforms, ...noises].forEach((s) => { registerSound( - wave, + s, (t, value, onended) => { // destructure adsr here, because the default should be different for synths and samples - let { - attack = 0.001, - decay = 0.05, - sustain = 0.6, - release = 0.01, - fmh: fmHarmonicity = 1, - fmi: fmModulationIndex, - fmenv: fmEnvelopeType = 'lin', - fmattack: fmAttack, - fmdecay: fmDecay, - fmsustain: fmSustain, - fmrelease: fmRelease, - fmvelocity: fmVelocity, - fmwave: fmWaveform = 'sine', - vib = 0, - vibmod = 0.5, - noise = 0, - } = value; - let { n, note, freq } = value; - // with synths, n and note are the same thing - 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); - } - // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) - // make oscillator - const { - node: o, - stop, - dry_node = null, - } = getOscillator({ - t, - s: wave, - freq, - vib, - vibmod, - partials: n, - noise: noise, - }); - // FM + FM envelope - let stopFm, fmEnvelope; - if (fmModulationIndex) { - const { node: modulator, stop } = fm( - dry_node !== null ? dry_node : o, - fmHarmonicity, - fmModulationIndex, - fmWaveform, - ); - if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { - // no envelope by default - modulator.connect(dry_node !== null ? dry_node.frequency : o.frequency); - } else { - fmAttack = fmAttack ?? 0.001; - fmDecay = fmDecay ?? 0.001; - fmSustain = fmSustain ?? 1; - fmRelease = fmRelease ?? 0.001; - fmVelocity = fmVelocity ?? 1; - fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); - if (fmEnvelopeType === 'exp') { - fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); - fmEnvelope.node.maxValue = fmModulationIndex * 2; - fmEnvelope.node.minValue = 0.00001; - } - modulator.connect(fmEnvelope.node); - fmEnvelope.node.connect(dry_node !== null ? dry_node.frequency : o.frequency); - } - stopFm = stop; + let { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = value; + + let sound; + if (waveforms.includes(s)) { + sound = getOscillator(s, t, value); + } else { + sound = getNoiseOscillator(t, s); } + let { node: o, stop, triggerRelease } = sound; + // turn down const g = gainNode(0.3); @@ -114,10 +55,9 @@ export function registerSynthSounds() { node: o.connect(g).connect(envelope), stop: (releaseTime) => { releaseEnvelope(releaseTime); - fmEnvelope?.stop(releaseTime); + triggerRelease?.(releaseTime); let end = releaseTime + release; stop(end); - stopFm?.(end); }, }; }, @@ -156,7 +96,9 @@ export function waveformN(partials, type) { return osc; } -export function getNoiseOscillator({ t, ac, type = 'white' }) { +// expects one of noises as type +export function getNoiseOscillator(t, type = 'white') { + const ac = getAudioContext(); const bufferSize = 2 * ac.sampleRate; const noiseBuffer = ac.createBuffer(1, bufferSize, ac.sampleRate); const output = noiseBuffer.getChannelData(0); @@ -189,82 +131,130 @@ export function getNoiseOscillator({ t, ac, type = 'white' }) { o.buffer = noiseBuffer; o.loop = true; o.start(t); - return { node: o, stop: (time) => o.stop(time), }; } -export function getOscillator({ s, freq, t, vib, vibmod, partials, noise }) { - // Make oscillator with partial count +// expects one of waveforms as s +export function getOscillator( + s, + t, + { + n: partials, + note, + freq, + vib = 0, + vibmod = 0.5, + noise = 0, + // fm + fmh: fmHarmonicity = 1, + fmi: fmModulationIndex, + fmenv: fmEnvelopeType = 'lin', + fmattack: fmAttack, + fmdecay: fmDecay, + fmsustain: fmSustain, + fmrelease: fmRelease, + fmvelocity: fmVelocity, + fmwave: fmWaveform = 'sine', + }, +) { let ac = getAudioContext(); let o; + // If no partials are given, use stock waveforms + if (!partials || s === 'sine') { + o = getAudioContext().createOscillator(); + o.type = s || 'triangle'; + } + // generate custom waveform if partials are given + else { + o = waveformN(partials, s); + } - if (['pink', 'white', 'brown'].includes(s)) { - let noiseOscillator = getNoiseOscillator({ t: t, ac: getAudioContext(), type: s }); - return { - node: noiseOscillator.node, - stop: noiseOscillator.stop, - }; - } else { - if (!partials || s === 'sine') { - o = getAudioContext().createOscillator(); - o.type = s || 'triangle'; - } else { - o = waveformN(partials, s); - } - o.frequency.value = Number(freq); - o.start(t); + // get frequency from note... + 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); + } - // Additional oscillator for vibrato effect - let vibrato_oscillator; - if (vib > 0) { - vibrato_oscillator = getAudioContext().createOscillator(); - vibrato_oscillator.frequency.value = vib; - const gain = getAudioContext().createGain(); - // Vibmod is the amount of vibrato, in semitones - gain.gain.value = vibmod * 100; - vibrato_oscillator.connect(gain); - gain.connect(o.detune); - vibrato_oscillator.start(t); - } + // set frequency + o.frequency.value = Number(freq); + o.start(t); - if (noise > 0) { - // Two gain nodes to set the oscillators to their respective levels - noise = noise > 1 ? 1 : noise; - let o_gain = ac.createGain(); - let n_gain = ac.createGain(); - o_gain.gain.setValueAtTime(1 - noise, ac.currentTime); - n_gain.gain.setValueAtTime(noise, ac.currentTime); + // FM + let stopFm, fmEnvelope; + if (fmModulationIndex) { + const { node: modulator, stop } = fm(o, fmHarmonicity, fmModulationIndex, fmWaveform); + if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) { + // no envelope by default + modulator.connect(o.frequency); + } else { + fmAttack = fmAttack ?? 0.001; + fmDecay = fmDecay ?? 0.001; + fmSustain = fmSustain ?? 1; + fmRelease = fmRelease ?? 0.001; + fmVelocity = fmVelocity ?? 1; + fmEnvelope = getEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); + if (fmEnvelopeType === 'exp') { + fmEnvelope = getExpEnvelope(fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity, t); + fmEnvelope.node.maxValue = fmModulationIndex * 2; + fmEnvelope.node.minValue = 0.00001; + } + modulator.connect(fmEnvelope.node); + fmEnvelope.node.connect(o.frequency); + } + stopFm = stop; + } - // Instanciating a mixer to blend sources together - let mix_gain = ac.createGain(); + // Additional oscillator for vibrato effect + let vibratoOscillator; + if (vib > 0) { + vibratoOscillator = getAudioContext().createOscillator(); + vibratoOscillator.frequency.value = vib; + const gain = getAudioContext().createGain(); + // Vibmod is the amount of vibrato, in semitones + gain.gain.value = vibmod * 100; + vibratoOscillator.connect(gain); + gain.connect(o.detune); + vibratoOscillator.start(t); + } - // Connecting the main oscillator to the gain node - o.connect(o_gain).connect(mix_gain); + let noiseOscillator, noiseMix; + // noise mix + if (noise > 0) { + // Two gain nodes to set the oscillators to their respective levels + noise = noise > 1 ? 1 : noise; + let o_gain = ac.createGain(); + let n_gain = ac.createGain(); + o_gain.gain.setValueAtTime(1 - noise, ac.currentTime); + n_gain.gain.setValueAtTime(noise, ac.currentTime); - // Instanciating a noise oscillator and connecting - const noiseOscillator = getNoiseOscillator({ t: t, ac: ac, type: 'pink' }); - noiseOscillator.node.connect(n_gain).connect(mix_gain); + // Instanciating a mixer to blend sources together + noiseMix = ac.createGain(); - return { - node: mix_gain, - dry_node: o, - stop: (time) => { - vibrato_oscillator?.stop(time); - o.stop(time); - noiseOscillator.stop(time); - }, - }; - } + // Connecting the main oscillator to the gain node + o.connect(o_gain).connect(noiseMix); - return { - node: o, - stop: (time) => { - vibrato_oscillator?.stop(time); - o.stop(time); - }, - }; + // Instanciating a noise oscillator and connecting + noiseOscillator = getNoiseOscillator(t, 'pink'); + noiseOscillator.node.connect(n_gain).connect(noiseMix); } + + return { + node: noiseMix || o, + stop: (time) => { + vibratoOscillator?.stop(time); + noiseOscillator?.stop(time); + stopFm?.(time); + o.stop(time); + }, + triggerRelease: (time) => { + fmEnvelope?.stop(time); + }, + }; } From 2bc6d0841049aaac6943cec2cdf217905aa1ac13 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 3 Oct 2023 12:19:30 +0200 Subject: [PATCH 09/13] proper dry wet + pull out noise to extra file --- packages/superdough/helpers.mjs | 22 +++++++++++ packages/superdough/noise.mjs | 51 ++++++++++++++++++++++++ packages/superdough/synth.mjs | 70 ++++----------------------------- 3 files changed, 80 insertions(+), 63 deletions(-) create mode 100644 packages/superdough/noise.mjs diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 32ca0bb25..576ec3f19 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -112,3 +112,25 @@ export function createFilter( return filter; } + +// stays 1 until .5, then fades out +let wetfade = (d) => (d < 0.5 ? 1 : 1 - (d - 0.5) / 0.5); + +// mix together dry and wet nodes. 0 = only dry 1 = only wet +// still not too sure about how this could be used more generally... +export function drywet(dry, wet, wetAmount = 0) { + const ac = getAudioContext(); + if (!wetAmount) { + return dry; + } + let dry_gain = ac.createGain(); + let wet_gain = ac.createGain(); + dry.connect(dry_gain); + wet.connect(wet_gain); + dry_gain.gain.value = wetfade(wetAmount); + wet_gain.gain.value = wetfade(1 - wetAmount); + let mix = ac.createGain(); + dry_gain.connect(mix); + wet_gain.connect(mix); + return mix; +} diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs new file mode 100644 index 000000000..eeb2a9d0b --- /dev/null +++ b/packages/superdough/noise.mjs @@ -0,0 +1,51 @@ +import { drywet } from './helpers.mjs'; + +// expects one of noises as type +export function getNoiseOscillator(type = 'white', t) { + const ac = getAudioContext(); + const bufferSize = 2 * ac.sampleRate; + const noiseBuffer = ac.createBuffer(1, bufferSize, ac.sampleRate); + const output = noiseBuffer.getChannelData(0); + let lastOut = 0; + let b0, b1, b2, b3, b4, b5, b6; + b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0; + + for (let i = 0; i < bufferSize; i++) { + if (type === 'white') { + output[i] = Math.random() * 2 - 1; + } else if (type === 'brown') { + let white = Math.random() * 2 - 1; + output[i] = (lastOut + 0.02 * white) / 1.02; + lastOut = output[i]; + } else if (type === 'pink') { + let white = Math.random() * 2 - 1; + b0 = 0.99886 * b0 + white * 0.0555179; + b1 = 0.99332 * b1 + white * 0.0750759; + b2 = 0.969 * b2 + white * 0.153852; + b3 = 0.8665 * b3 + white * 0.3104856; + b4 = 0.55 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.016898; + output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; + output[i] *= 0.11; + b6 = white * 0.115926; + } + } + + const o = ac.createBufferSource(); + o.buffer = noiseBuffer; + o.loop = true; + o.start(t); + return { + node: o, + stop: (time) => o.stop(time), + }; +} + +export function getNoiseMix(inputNode, wet, t) { + const noiseOscillator = getNoiseOscillator('pink', t); + const noiseMix = drywet(inputNode, noiseOscillator.node, wet); + return { + node: noiseMix, + stop: (time) => noiseOscillator?.stop(time), + }; +} diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index f12be9c84..8c07f34e1 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,6 +1,7 @@ import { midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext } from './superdough.mjs'; import { gainNode, getEnvelope, getExpEnvelope } from './helpers.mjs'; +import { getNoiseMix } from './noise.mjs'; const mod = (freq, range = 1, type = 'sine') => { const ctx = getAudioContext(); @@ -35,7 +36,7 @@ export function registerSynthSounds() { if (waveforms.includes(s)) { sound = getOscillator(s, t, value); } else { - sound = getNoiseOscillator(t, s); + sound = getNoiseOscillator(s, t); } let { node: o, stop, triggerRelease } = sound; @@ -96,47 +97,6 @@ export function waveformN(partials, type) { return osc; } -// expects one of noises as type -export function getNoiseOscillator(t, type = 'white') { - const ac = getAudioContext(); - const bufferSize = 2 * ac.sampleRate; - const noiseBuffer = ac.createBuffer(1, bufferSize, ac.sampleRate); - const output = noiseBuffer.getChannelData(0); - let lastOut = 0; - let b0, b1, b2, b3, b4, b5, b6; - b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0.0; - - for (let i = 0; i < bufferSize; i++) { - if (type === 'white') { - output[i] = Math.random() * 2 - 1; - } else if (type === 'brown') { - let white = Math.random() * 2 - 1; - output[i] = (lastOut + 0.02 * white) / 1.02; - lastOut = output[i]; - } else if (type === 'pink') { - let white = Math.random() * 2 - 1; - b0 = 0.99886 * b0 + white * 0.0555179; - b1 = 0.99332 * b1 + white * 0.0750759; - b2 = 0.969 * b2 + white * 0.153852; - b3 = 0.8665 * b3 + white * 0.3104856; - b4 = 0.55 * b4 + white * 0.5329522; - b5 = -0.7616 * b5 - white * 0.016898; - output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; - output[i] *= 0.11; - b6 = white * 0.115926; - } - } - - const o = ac.createBufferSource(); - o.buffer = noiseBuffer; - o.loop = true; - o.start(t); - return { - node: o, - stop: (time) => o.stop(time), - }; -} - // expects one of waveforms as s export function getOscillator( s, @@ -224,32 +184,16 @@ export function getOscillator( vibratoOscillator.start(t); } - let noiseOscillator, noiseMix; - // noise mix - if (noise > 0) { - // Two gain nodes to set the oscillators to their respective levels - noise = noise > 1 ? 1 : noise; - let o_gain = ac.createGain(); - let n_gain = ac.createGain(); - o_gain.gain.setValueAtTime(1 - noise, ac.currentTime); - n_gain.gain.setValueAtTime(noise, ac.currentTime); - - // Instanciating a mixer to blend sources together - noiseMix = ac.createGain(); - - // Connecting the main oscillator to the gain node - o.connect(o_gain).connect(noiseMix); - - // Instanciating a noise oscillator and connecting - noiseOscillator = getNoiseOscillator(t, 'pink'); - noiseOscillator.node.connect(n_gain).connect(noiseMix); + let noiseMix; + if (noise) { + noiseMix = getNoiseMix(o, noise, t); } return { - node: noiseMix || o, + node: noiseMix?.node || o, stop: (time) => { vibratoOscillator?.stop(time); - noiseOscillator?.stop(time); + noiseMix?.stop(time); stopFm?.(time); o.stop(time); }, From 4b64168faa0c95b70edac6c6ec17ca178c72278a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 3 Oct 2023 12:20:28 +0200 Subject: [PATCH 10/13] fix: imports --- packages/superdough/noise.mjs | 1 + packages/superdough/synth.mjs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index eeb2a9d0b..0e6c436e6 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -1,4 +1,5 @@ import { drywet } from './helpers.mjs'; +import { getAudioContext } from './superdough.mjs'; // expects one of noises as type export function getNoiseOscillator(type = 'white', t) { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 8c07f34e1..24d1d5ef7 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,7 +1,7 @@ import { midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext } from './superdough.mjs'; import { gainNode, getEnvelope, getExpEnvelope } from './helpers.mjs'; -import { getNoiseMix } from './noise.mjs'; +import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; const mod = (freq, range = 1, type = 'sine') => { const ctx = getAudioContext(); From 047129223e62a0d161083853bcfe84a599e4ccc0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 3 Oct 2023 12:25:47 +0200 Subject: [PATCH 11/13] cache noise --- packages/superdough/noise.mjs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 0e6c436e6..2c8c1d4ae 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -1,9 +1,14 @@ import { drywet } from './helpers.mjs'; import { getAudioContext } from './superdough.mjs'; -// expects one of noises as type -export function getNoiseOscillator(type = 'white', t) { +let noiseCache = {}; + +// lazy generates noise buffers and keeps them forever +function getNoiseBuffer(type) { const ac = getAudioContext(); + if (noiseCache[type]) { + return noiseCache[type]; + } const bufferSize = 2 * ac.sampleRate; const noiseBuffer = ac.createBuffer(1, bufferSize, ac.sampleRate); const output = noiseBuffer.getChannelData(0); @@ -31,9 +36,15 @@ export function getNoiseOscillator(type = 'white', t) { b6 = white * 0.115926; } } + noiseCache[type] = noiseBuffer; + return noiseBuffer; +} +// expects one of noises as type +export function getNoiseOscillator(type = 'white', t) { + const ac = getAudioContext(); const o = ac.createBufferSource(); - o.buffer = noiseBuffer; + o.buffer = getNoiseBuffer(type); o.loop = true; o.start(t); return { From 376cf09565bb9c63a08c4eef43a6909186f00b29 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 3 Oct 2023 12:41:57 +0200 Subject: [PATCH 12/13] rename zzfx noise to znoise --- packages/core/controls.mjs | 11 ++++++++++- packages/superdough/zzfx.mjs | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 6cac6e540..39c2df2f8 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -655,6 +655,15 @@ const generic_params = [ * .vib("<.5 1 2 4 8 16>:12") */ [['vib', 'vibmod'], 'vibrato', 'v'], + /** + * Adds pink noise to the mix + * + * @name noise + * @param {number | Pattern} wet wet amount + * @example + * sound("/2") + */ + ['noise'], /** * Sets the vibrato depth in semitones. Only has an effect if `vibrato` | `vib` | `v` is is also set * @@ -1153,7 +1162,7 @@ const generic_params = [ ['pitchJump'], ['pitchJumpTime'], ['lfo', 'repeatTime'], - ['noise'], + ['znoise'], // noise on the frequency or as bubo calls it "frequency fog" :) ['zmod'], ['zcrush'], // like crush but scaled differently ['zdelay'], diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs index da505d747..a6af82609 100644 --- a/packages/superdough/zzfx.mjs +++ b/packages/superdough/zzfx.mjs @@ -20,7 +20,7 @@ export const getZZFX = (value, t) => { pitchJump = 0, pitchJumpTime = 0, lfo = 0, - noise = 0, + znoise = 0, zmod = 0, zcrush = 0, zdelay = 0, @@ -54,7 +54,7 @@ export const getZZFX = (value, t) => { pitchJump, pitchJumpTime, lfo, - noise, + znoise, zmod, zcrush, zdelay, From 9c9323e04099db415645d95a24e6969d3c21d113 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 3 Oct 2023 12:42:56 +0200 Subject: [PATCH 13/13] snapshot --- test/__snapshots__/examples.test.mjs.snap | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index e026f9c43..4a4c966de 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -2959,6 +2959,15 @@ exports[`runs examples > example "never" example index 0 1`] = ` ] `; +exports[`runs examples > example "noise" example index 0 1`] = ` +[ + "[ (0/1 → 1/1) ⇝ 2/1 | s:white ]", + "[ 0/1 ⇜ (1/1 → 2/1) | s:white ]", + "[ (2/1 → 3/1) ⇝ 4/1 | s:pink ]", + "[ 2/1 ⇜ (3/1 → 4/1) | s:pink ]", +] +`; + exports[`runs examples > example "note" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:c ]",