diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 866be4fb..a03658a8 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -380,6 +380,62 @@ const generic_params = [ * */ ['coarse'], + + ['phaserrate', 'phasr'], // superdirt only + + /** + * Phaser audio effect that approximates popular guitar pedals. + * + * @name phaser + * @synonyms ph + * @param {number | Pattern} speed speed of modulation + * @example + * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) + * .phaser("<1 2 4 8>") + * + */ + [['phaser', 'phaserdepth', 'phasercenter', 'phasersweep'], 'ph'], + + /** + * The frequency sweep range of the lfo for the phaser effect. Defaults to 2000 + * + * @name phasersweep + * @synonyms phs + * @param {number | Pattern} phasersweep most useful values are between 0 and 4000 + * @example + * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) + * .phaser(2).phasersweep("<800 2000 4000>") + * + */ + ['phasersweep', 'phs'], + + /** + * The center frequency of the phaser in HZ. Defaults to 1000 + * + * @name phasercenter + * @synonyms phc + * @param {number | Pattern} centerfrequency in HZ + * @example + * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) + * .phaser(2).phasercenter("<800 2000 4000>") + * + */ + + ['phasercenter', 'phc'], + + /** + * The amount the signal is affected by the phaser effect. Defaults to 0.75 + * + * @name phaserdepth + * @synonyms phd + * @param {number | Pattern} depth number between 0 and 1 + * @example + * n(run(8)).scale("D:pentatonic").s("sawtooth").release(0.5) + * .phaser(2).phaserdepth("<0 .5 .75 1>") + * + */ + ['phaserdepth', 'phd', 'phasdp'], // also a superdirt control + /** * choose the channel the pattern is sent to in superdirt * @@ -1036,7 +1092,8 @@ const generic_params = [ */ ['roomfade', 'rfade'], /** - * Sets the sample to use as an impulse response for the reverb. * * @name iresponse + * Sets the sample to use as an impulse response for the reverb. + * @name iresponse * @param {string | Pattern} sample to use as an impulse response * @synonyms ir * @example @@ -1174,9 +1231,6 @@ const generic_params = [ */ ['tremolodepth', 'tremdp'], ['tremolorate', 'tremr'], - // TODO: doesn't seem to do anything - ['phaserdepth', 'phasdp'], - ['phaserrate', 'phasr'], ['fshift'], ['fshiftnote'], diff --git a/packages/superdough/README.md b/packages/superdough/README.md index c5950dbf..f32aa32d 100644 --- a/packages/superdough/README.md +++ b/packages/superdough/README.md @@ -67,6 +67,10 @@ superdough({ s: 'bd', delay: 0.5 }, 0, 1); - `crush`: amplitude bit crusher using given number of bits - `shape`: distortion effect from 0 (none) to 1 (full). might get loud! - `pan`: stereo panning from 0 (left) to 1 (right) + - `phaser`: sets the speed of the modulation + - `phaserdepth`: the amount the signal is affected by the phaser effect. + - `phasersweep`: the frequency sweep range of the lfo for the phaser effect. + - `phasercenter`: the amount the signal is affected by the phaser effect. - `vowel`: vowel filter. possible values: "a", "e", "i", "o", "u" - `delay`: delay mix - `delayfeedback`: delay feedback diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index e3033afe..00e2f42c 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -112,6 +112,48 @@ function getDelay(orbit, delaytime, delayfeedback, t) { return delays[orbit]; } +// each orbit will have its own lfo +const phaserLFOs = {}; +function getPhaser(orbit, t, speed = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) { + //gain + const ac = getAudioContext(); + const lfoGain = ac.createGain(); + lfoGain.gain.value = sweep; + + //LFO + if (phaserLFOs[orbit] == null) { + phaserLFOs[orbit] = ac.createOscillator(); + phaserLFOs[orbit].frequency.value = speed; + phaserLFOs[orbit].type = 'sine'; + phaserLFOs[orbit].start(); + } + + phaserLFOs[orbit].connect(lfoGain); + if (phaserLFOs[orbit].frequency.value != speed) { + phaserLFOs[orbit].frequency.setValueAtTime(speed, t); + } + + //filters + const numStages = 2; //num of filters in series + let fOffset = 0; + const filterChain = []; + for (let i = 0; i < numStages; i++) { + const filter = ac.createBiquadFilter(); + filter.type = 'notch'; + filter.gain.value = 1; + filter.frequency.value = centerFrequency + fOffset; + filter.Q.value = 2 - Math.min(Math.max(depth * 2, 0), 1.9); + + lfoGain.connect(filter.detune); + fOffset += 282; + if (i > 0) { + filterChain[i - 1].connect(filter); + } + filterChain.push(filter); + } + return filterChain[filterChain.length - 1]; +} + let reverbs = {}; let hasChanged = (now, before) => now !== undefined && now !== before; @@ -226,6 +268,12 @@ export const superdough = async (value, deadline, hapDuration) => { bpsustain = 1, bprelease = 0.01, bandq = 1, + + //phaser + phaser, + phaserdepth = 0.75, + phasersweep, + phasercenter, // coarse, crush, @@ -260,6 +308,7 @@ export const superdough = async (value, deadline, hapDuration) => { if (bank && s) { s = `${bank}_${s}`; } + // get source AudioNode let sourceNode; if (source) { @@ -377,6 +426,11 @@ export const superdough = async (value, deadline, hapDuration) => { panner.pan.value = 2 * pan - 1; chain.push(panner); } + // phaser + if (phaser !== undefined && phaserdepth > 0) { + const phaserFX = getPhaser(orbit, t, phaser, phaserdepth, phasercenter, phasersweep); + chain.push(phaserFX); + } // last gain const post = gainNode(postgain); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index d02acdef..a12fcb65 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -2439,6 +2439,19 @@ exports[`runs examples > example "irand" example index 0 1`] = ` ] `; +exports[`runs examples > example "iresponse" example index 0 1`] = ` +[ + "[ 0/1 → 1/2 | s:bd room:0.8 ir:shaker_large i:0 ]", + "[ 1/2 → 1/1 | s:sd room:0.8 ir:shaker_large i:0 ]", + "[ 1/1 → 3/2 | s:bd room:0.8 ir:shaker_large i:2 ]", + "[ 3/2 → 2/1 | s:sd room:0.8 ir:shaker_large i:2 ]", + "[ 2/1 → 5/2 | s:bd room:0.8 ir:shaker_large i:0 ]", + "[ 5/2 → 3/1 | s:sd room:0.8 ir:shaker_large i:0 ]", + "[ 3/1 → 7/2 | s:bd room:0.8 ir:shaker_large i:2 ]", + "[ 7/2 → 4/1 | s:sd room:0.8 ir:shaker_large i:2 ]", +] +`; + exports[`runs examples > example "iter" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:A3 ]", @@ -3306,6 +3319,154 @@ exports[`runs examples > example "perlin" example index 0 1`] = ` ] `; +exports[`runs examples > example "phaser" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:1 ]", + "[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:1 ]", + "[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:1 ]", + "[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:1 ]", + "[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:1 ]", + "[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:1 ]", + "[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:1 ]", + "[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:1 ]", + "[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 ]", + "[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 ]", + "[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 ]", + "[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 ]", + "[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 ]", + "[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 ]", + "[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 ]", + "[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 ]", + "[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:4 ]", + "[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:4 ]", + "[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:4 ]", + "[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:4 ]", + "[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:4 ]", + "[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:4 ]", + "[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:4 ]", + "[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:4 ]", + "[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:8 ]", + "[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:8 ]", + "[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:8 ]", + "[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:8 ]", + "[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:8 ]", + "[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:8 ]", + "[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:8 ]", + "[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:8 ]", +] +`; + +exports[`runs examples > example "phasercenter" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]", + "[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]", + "[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]", + "[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]", + "[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]", + "[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]", + "[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]", + "[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:2000 ]", + "[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]", + "[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]", + "[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]", + "[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]", + "[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]", + "[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]", + "[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]", + "[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:4000 ]", + "[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", + "[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasercenter:800 ]", +] +`; + +exports[`runs examples > example "phaserdepth" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]", + "[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]", + "[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]", + "[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]", + "[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]", + "[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]", + "[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]", + "[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0 ]", + "[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]", + "[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]", + "[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]", + "[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]", + "[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]", + "[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]", + "[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]", + "[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.5 ]", + "[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]", + "[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]", + "[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]", + "[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]", + "[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]", + "[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]", + "[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]", + "[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:0.75 ]", + "[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]", + "[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]", + "[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]", + "[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]", + "[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]", + "[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]", + "[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]", + "[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phaserdepth:1 ]", +] +`; + +exports[`runs examples > example "phasersweep" example index 0 1`] = ` +[ + "[ 0/1 → 1/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 1/8 → 1/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 1/4 → 3/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 3/8 → 1/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 1/2 → 5/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 5/8 → 3/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 3/4 → 7/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 7/8 → 1/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 1/1 → 9/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]", + "[ 9/8 → 5/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]", + "[ 5/4 → 11/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]", + "[ 11/8 → 3/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]", + "[ 3/2 → 13/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]", + "[ 13/8 → 7/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]", + "[ 7/4 → 15/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]", + "[ 15/8 → 2/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:2000 ]", + "[ 2/1 → 17/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]", + "[ 17/8 → 9/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]", + "[ 9/4 → 19/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]", + "[ 19/8 → 5/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]", + "[ 5/2 → 21/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]", + "[ 21/8 → 11/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]", + "[ 11/4 → 23/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]", + "[ 23/8 → 3/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:4000 ]", + "[ 3/1 → 25/8 | note:D3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 25/8 → 13/4 | note:E3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 13/4 → 27/8 | note:F#3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 27/8 → 7/2 | note:A3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 7/2 → 29/8 | note:B3 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 29/8 → 15/4 | note:D4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 15/4 → 31/8 | note:E4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", + "[ 31/8 → 4/1 | note:F#4 s:sawtooth release:0.5 phaser:2 phasersweep:800 ]", +] +`; + exports[`runs examples > example "pianoroll" example index 0 1`] = ` [ "[ 0/1 → 1/8 | note:C2 s:piano clip:1 ]", diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index b1323a8e..a3ae91d7 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -240,3 +240,21 @@ global effects use the same chain for all events of the same orbit: Next, we'll look at strudel's support for [Csound](/learn/csound). + +## Phaser + +### phaser + + + +### phaserdepth + + + +### phasercenter + + + +### phasersweep + +