From bdc2af9733574ae4fbe9817a426a943a3b9b329e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 15 Jan 2024 23:25:22 +0100 Subject: [PATCH 01/11] basic pitch envelope --- packages/core/controls.mjs | 7 +++++++ packages/superdough/synth.mjs | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 97129779..13856608 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -891,6 +891,13 @@ const generic_params = [ * */ ['freq'], + // pitch envelope + ['pattack', 'patt'], + ['pdecay', 'pdec'], + ['psustain', 'psus'], + ['prelease', 'prel'], + ['penv'], + ['panchor'], // TODO // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate ['gate', 'gat'], // ['hatgrain'], diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 6a3e381a..d484284b 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -126,6 +126,12 @@ export function getOscillator( fmvelocity: fmVelocity, fmwave: fmWaveform = 'sine', duration, + penv, + // panchor = 0, // TODO + pattack, + pdecay, + psustain, + prelease, }, ) { let ac = getAudioContext(); @@ -196,6 +202,12 @@ export function getOscillator( vibratoOscillator.start(t); } + // pitch envelope + if (penv) { + const holdEnd = t + duration; + getParamADSR(o.detune, pattack, pdecay, psustain, prelease, 0, penv * 100, t, holdEnd, 'linear'); + } + let noiseMix; if (noise) { noiseMix = getNoiseMix(o, noise, t); From 32456d69663acce0b6932f06c8c64e6e2c4e7b73 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 15 Jan 2024 23:55:49 +0100 Subject: [PATCH 02/11] pitch envelope for sampler and soundfonts + added getPitchEnvelope helper --- packages/soundfonts/fontloader.mjs | 13 ++++++++++++- packages/superdough/helpers.mjs | 13 +++++++++++++ packages/superdough/sampler.mjs | 7 ++++++- packages/superdough/synth.mjs | 21 ++++++--------------- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/packages/soundfonts/fontloader.mjs b/packages/soundfonts/fontloader.mjs index dc3f4b61..fced1c28 100644 --- a/packages/soundfonts/fontloader.mjs +++ b/packages/soundfonts/fontloader.mjs @@ -1,5 +1,11 @@ import { noteToMidi, freqToMidi, getSoundIndex } from '@strudel.cycles/core'; -import { getAudioContext, registerSound, getParamADSR, getADSRValues } from '@strudel.cycles/webaudio'; +import { + getAudioContext, + registerSound, + getParamADSR, + getADSRValues, + getPitchEnvelope, +} from '@strudel.cycles/webaudio'; import gm from './gm.mjs'; let loadCache = {}; @@ -149,6 +155,11 @@ export function registerSoundfonts() { getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear'); let envEnd = holdEnd + release + 0.01; + // pitch envelope + if (value.penv) { + getPitchEnvelope(bufferSource.detune, value, time, holdEnd); + } + bufferSource.stop(envEnd); const stop = (releaseTime) => {}; bufferSource.onended = () => { diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index bee26f38..d69afd36 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -144,3 +144,16 @@ export function drywet(dry, wet, wetAmount = 0) { wet_gain.connect(mix); return mix; } + +export function getPitchEnvelope(param, value, t, holdEnd) { + if (value.penv) { + let [pattack, pdecay, psustain, prelease] = getADSRValues([ + value.pattack, + value.pdecay, + value.psustain, + value.prelease, + ]); + const cents = value.penv * 100; + getParamADSR(param, pattack, pdecay, psustain, prelease, 0, cents, t, holdEnd, 'linear'); + } +} diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 932be995..5e842da5 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -1,6 +1,6 @@ import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs'; import { getAudioContext, registerSound } from './index.mjs'; -import { getADSRValues, getParamADSR } from './helpers.mjs'; +import { getADSRValues, getParamADSR, getPitchEnvelope } from './helpers.mjs'; import { logger } from './logger.mjs'; const bufferCache = {}; // string: Promise @@ -310,6 +310,11 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear'); + // pitch envelope + if (value.penv) { + getPitchEnvelope(bufferSource.detune, value, t, holdEnd); + } + const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox... node.connect(out); bufferSource.onended = function () { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index d484284b..9d70e037 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,6 +1,6 @@ import { midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext } from './superdough.mjs'; -import { gainNode, getADSRValues, getParamADSR } from './helpers.mjs'; +import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope } from './helpers.mjs'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; const mod = (freq, range = 1, type = 'sine') => { @@ -105,10 +105,8 @@ export function waveformN(partials, type) { } // expects one of waveforms as s -export function getOscillator( - s, - t, - { +export function getOscillator(s, t, value) { + let { n: partials, note, freq, @@ -126,14 +124,7 @@ export function getOscillator( fmvelocity: fmVelocity, fmwave: fmWaveform = 'sine', duration, - penv, - // panchor = 0, // TODO - pattack, - pdecay, - psustain, - prelease, - }, -) { + } = value; let ac = getAudioContext(); let o; // If no partials are given, use stock waveforms @@ -203,9 +194,9 @@ export function getOscillator( } // pitch envelope - if (penv) { + if (value.penv) { const holdEnd = t + duration; - getParamADSR(o.detune, pattack, pdecay, psustain, prelease, 0, penv * 100, t, holdEnd, 'linear'); + getPitchEnvelope(o.detune, value, t, holdEnd); } let noiseMix; From 3506de8d1a5c412537961ba32c2821b7c666598a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 16 Jan 2024 00:07:56 +0100 Subject: [PATCH 03/11] vibrato for soundfonts + add getVibratoOscillator helper --- packages/soundfonts/fontloader.mjs | 4 ++++ packages/superdough/helpers.mjs | 16 ++++++++++++++++ packages/superdough/sampler.mjs | 16 ++-------------- packages/superdough/synth.mjs | 16 ++-------------- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/soundfonts/fontloader.mjs b/packages/soundfonts/fontloader.mjs index fced1c28..4be29fa1 100644 --- a/packages/soundfonts/fontloader.mjs +++ b/packages/soundfonts/fontloader.mjs @@ -5,6 +5,7 @@ import { getParamADSR, getADSRValues, getPitchEnvelope, + getVibratoOscillator, } from '@strudel.cycles/webaudio'; import gm from './gm.mjs'; @@ -155,6 +156,8 @@ export function registerSoundfonts() { getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, time, holdEnd, 'linear'); let envEnd = holdEnd + release + 0.01; + // vibrato + let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time); // pitch envelope if (value.penv) { getPitchEnvelope(bufferSource.detune, value, time, holdEnd); @@ -164,6 +167,7 @@ export function registerSoundfonts() { const stop = (releaseTime) => {}; bufferSource.onended = () => { bufferSource.disconnect(); + vibratoOscillator?.stop(); node.disconnect(); onended(); }; diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index d69afd36..7a11d84d 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -157,3 +157,19 @@ export function getPitchEnvelope(param, value, t, holdEnd) { getParamADSR(param, pattack, pdecay, psustain, prelease, 0, cents, t, holdEnd, 'linear'); } } + +export function getVibratoOscillator(param, value, t) { + const { vibmod = 0.5, vib } = value; + 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(param); + vibratoOscillator.start(t); + return vibratoOscillator; + } +} diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 5e842da5..67ee06c0 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -1,6 +1,6 @@ import { noteToMidi, valueToMidi, getSoundIndex } from './util.mjs'; import { getAudioContext, registerSound } from './index.mjs'; -import { getADSRValues, getParamADSR, getPitchEnvelope } from './helpers.mjs'; +import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { logger } from './logger.mjs'; const bufferCache = {}; // string: Promise @@ -244,8 +244,6 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { loopEnd = 1, end = 1, duration, - vib, - vibmod = 0.5, } = value; // load sample if (speed === 0) { @@ -263,17 +261,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank, resolveUrl); // vibrato - 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(bufferSource.detune); - vibratoOscillator.start(0); - } + let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t); // asny stuff above took too long? if (ac.currentTime > t) { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 9d70e037..7bde4d3a 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,6 +1,6 @@ import { midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext } from './superdough.mjs'; -import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope } from './helpers.mjs'; +import { gainNode, getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { getNoiseMix, getNoiseOscillator } from './noise.mjs'; const mod = (freq, range = 1, type = 'sine') => { @@ -110,8 +110,6 @@ export function getOscillator(s, t, value) { n: partials, note, freq, - vib = 0, - vibmod = 0.5, noise = 0, // fm fmh: fmHarmonicity = 1, @@ -181,17 +179,7 @@ export function getOscillator(s, t, value) { } // 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); - } + let vibratoOscillator = getVibratoOscillator(o.detune, value, t); // pitch envelope if (value.penv) { From fd73571744e46565e797dc729c43ecf5f8897fae Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 16 Jan 2024 17:56:38 +0100 Subject: [PATCH 04/11] make sure the sustained pitch is the selected note --- packages/superdough/helpers.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 7a11d84d..797d6db1 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -153,8 +153,11 @@ export function getPitchEnvelope(param, value, t, holdEnd) { value.psustain, value.prelease, ]); - const cents = value.penv * 100; - getParamADSR(param, pattack, pdecay, psustain, prelease, 0, cents, t, holdEnd, 'linear'); + let panchor = value.panchor ?? psustain; + const cents = value.penv * 100; // penv is in semitones + const min = 0 - cents * panchor; + const max = cents - cents * panchor; + getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, 'linear'); } } From 3fc0fdbe62c549c8dd53a0076d991d6a41e01128 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 16 Jan 2024 17:57:52 +0100 Subject: [PATCH 05/11] remove todo --- packages/core/controls.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 13856608..83499300 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -897,7 +897,7 @@ const generic_params = [ ['psustain', 'psus'], ['prelease', 'prel'], ['penv'], - ['panchor'], // TODO + ['panchor'], // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate ['gate', 'gat'], // ['hatgrain'], From 0655af4d4fbcc50bf6354015489a13bfbad7ef9b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 17 Jan 2024 17:35:35 +0100 Subject: [PATCH 06/11] add pcurve --- packages/core/controls.mjs | 1 + packages/superdough/helpers.mjs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 83499300..08037b1a 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -897,6 +897,7 @@ const generic_params = [ ['psustain', 'psus'], ['prelease', 'prel'], ['penv'], + ['pcurve'], ['panchor'], // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate ['gate', 'gat'], diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 797d6db1..a56d985a 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -145,6 +145,7 @@ export function drywet(dry, wet, wetAmount = 0) { return mix; } +let curves = ['linear', 'exponential']; export function getPitchEnvelope(param, value, t, holdEnd) { if (value.penv) { let [pattack, pdecay, psustain, prelease] = getADSRValues([ @@ -157,7 +158,8 @@ export function getPitchEnvelope(param, value, t, holdEnd) { const cents = value.penv * 100; // penv is in semitones const min = 0 - cents * panchor; const max = cents - cents * panchor; - getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, 'linear'); + const curve = curves[value.pcurve ?? 0]; + getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve); } } From d3ea4959b4513ddff3e2fe2825df5bb843c02ff1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 18 Jan 2024 06:47:12 +0100 Subject: [PATCH 07/11] - more flexible pitch envelopes - fix: exponential envelopes could break due to 0 values --- packages/superdough/helpers.mjs | 42 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index a56d985a..39e9977a 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -31,10 +31,10 @@ export const getParamADSR = ( decay = nanFallback(decay); sustain = nanFallback(sustain); release = nanFallback(release); - const ramp = curve === 'exponential' ? 'exponentialRampToValueAtTime' : 'linearRampToValueAtTime'; if (curve === 'exponential') { - min = Math.max(0.0001, min); + min = min === 0 ? 0.001 : min; + max = max === 0 ? 0.001 : max; } const range = max - min; const peak = max; @@ -42,12 +42,17 @@ export const getParamADSR = ( const duration = end - begin; const envValAtTime = (time) => { + let val; if (attack > time) { let slope = getSlope(min, peak, 0, attack); - return time * slope + (min > peak ? min : 0); + val = time * slope + (min > peak ? min : 0); } else { - return (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak; + val = (time - attack) * getSlope(peak, sustainVal, 0, decay) + peak; } + if (curve === 'exponential') { + val = val || 0.001; + } + return val; }; param.setValueAtTime(min, begin); @@ -147,20 +152,23 @@ export function drywet(dry, wet, wetAmount = 0) { let curves = ['linear', 'exponential']; export function getPitchEnvelope(param, value, t, holdEnd) { - if (value.penv) { - let [pattack, pdecay, psustain, prelease] = getADSRValues([ - value.pattack, - value.pdecay, - value.psustain, - value.prelease, - ]); - let panchor = value.panchor ?? psustain; - const cents = value.penv * 100; // penv is in semitones - const min = 0 - cents * panchor; - const max = cents - cents * panchor; - const curve = curves[value.pcurve ?? 0]; - getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve); + // envelope is active when any of these values is set + const hasEnvelope = value.pattack ?? value.pdecay ?? value.psustain ?? value.prelease ?? value.penv; + if (!hasEnvelope) { + return; } + const penv = nanFallback(value.penv, 1, true); + const curve = curves[value.pcurve ?? 0]; + let [pattack, pdecay, psustain, prelease] = getADSRValues( + [value.pattack, value.pdecay, value.psustain, value.prelease], + curve, + [0.2, 0.001, 1, 0.001], + ); + let panchor = value.panchor ?? psustain; + const cents = penv * 100; // penv is in semitones + const min = 0 - cents * panchor; + const max = cents - cents * panchor; + getParamADSR(param, pattack, pdecay, psustain, prelease, min, max, t, holdEnd, curve); } export function getVibratoOscillator(param, value, t) { From 1c99944a32ba8b3822a6504c16f6cac180fe0078 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 18 Jan 2024 06:47:44 +0100 Subject: [PATCH 08/11] pitch envelope now also works when setting one of the controls (penv not needed) --- packages/soundfonts/fontloader.mjs | 4 +--- packages/superdough/sampler.mjs | 4 +--- packages/superdough/synth.mjs | 5 +---- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/soundfonts/fontloader.mjs b/packages/soundfonts/fontloader.mjs index 4be29fa1..1a3395d5 100644 --- a/packages/soundfonts/fontloader.mjs +++ b/packages/soundfonts/fontloader.mjs @@ -159,9 +159,7 @@ export function registerSoundfonts() { // vibrato let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time); // pitch envelope - if (value.penv) { - getPitchEnvelope(bufferSource.detune, value, time, holdEnd); - } + getPitchEnvelope(bufferSource.detune, value, time, holdEnd); bufferSource.stop(envEnd); const stop = (releaseTime) => {}; diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 67ee06c0..19fec661 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -299,9 +299,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear'); // pitch envelope - if (value.penv) { - getPitchEnvelope(bufferSource.detune, value, t, holdEnd); - } + getPitchEnvelope(bufferSource.detune, value, t, holdEnd); const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox... node.connect(out); diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 7bde4d3a..6d646862 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -182,10 +182,7 @@ export function getOscillator(s, t, value) { let vibratoOscillator = getVibratoOscillator(o.detune, value, t); // pitch envelope - if (value.penv) { - const holdEnd = t + duration; - getPitchEnvelope(o.detune, value, t, holdEnd); - } + getPitchEnvelope(o.detune, value, t, t + duration); let noiseMix; if (noise) { From 67f0758cb56573124b276b9d9855b23f9ee1ec3d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 18 Jan 2024 06:47:54 +0100 Subject: [PATCH 09/11] document pitch envelope --- packages/core/controls.mjs | 68 +++++++++++++++++++++++++++++ website/src/pages/learn/effects.mdx | 54 +++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 08037b1a..4aa5349d 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -892,12 +892,80 @@ const generic_params = [ */ ['freq'], // pitch envelope + /** + * Attack time of pitch envelope. + * + * @name pattack + * @synonyms patt + * @param {number | Pattern} time time in seconds + * @example + * note("").pattack("<0 .1 .25 .5>") + * + */ ['pattack', 'patt'], + /** + * Decay time of pitch envelope. + * + * @name pdecay + * @synonyms pdec + * @param {number | Pattern} time time in seconds + * @example + * note("").pdecay("<0 .1 .25 .5>") + * + */ ['pdecay', 'pdec'], + // TODO: how to use psustain?! ['psustain', 'psus'], + /** + * Release time of pitch envelope + * + * @name prelease + * @synonyms prel + * @param {number | Pattern} time time in seconds + * @example + * note(" ~") + * .release(.5) // to hear the pitch release + * .prelease("<0 .1 .25 .5>") + * + */ ['prelease', 'prel'], + /** + * Amount of pitch envelope. Negative values will flip the envelope. + * If you don't set other pitch envelope controls, `pattack:.2` will be the default. + * + * @name penv + * @param {number | Pattern} semitones change in semitones + * @example + * note("c") + * .penv("<12 7 1 .5 0 -1 -7 -12>") + * + */ ['penv'], + /** + * Curve of envelope. Defaults to linear. exponential is good for kicks + * + * @name pcurve + * @param {number | Pattern} type 0 = linear, 1 = exponential + * @example + * note("g1*2") + * .s("sine").pdec(.5) + * .penv(32) + * .pcurve("<0 1>") + * + */ ['pcurve'], + /** + * Sets the range anchor of the envelope: + * - anchor 0: range = [note, note + penv] + * - anchor 1: range = [note - penv, note] + * If you don't set an anchor, the value will default to the psustain value. + * + * @name panchor + * @param {number | Pattern} anchor anchor offset + * @example + * note("c").penv(12).panchor("<0 .5 1 .5>") + * + */ ['panchor'], // TODO: https://tidalcycles.org/docs/configuration/MIDIOSC/control-voltage/#gate ['gate', 'gat'], diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index a3ae91d7..d0615dd9 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -138,6 +138,60 @@ There is one filter envelope for each filter type and thus one set of envelope f +## Pitch Envelope + +You can also control the pitch with envelopes! +Pitch envelopes can breathe life into static sounds: + +*<2!3 4>") + .scale("/8:pentatonic") + .s("gm_electric_guitar_jazz") + .penv("<.5 0 7 -2>*2").vib("4:.1") + .phaser(2).delay(.25).room(.3) + .size(4).fast(.75)`} +/> + +You also create some lovely chiptune-style sounds: + +/16")).jux(rev) +.chord(">") +.dict('ireal') +.voicing().add(note("<0 1>/8")) +.dec(.1).room(.2) +.segment("<4 [2 8]>") +.penv("<0 <2 -2>>").patt(.02)`} +/> + +Let's break down all pitch envelope controls: + +## pattack + + + +## pdecay + + + +## prelease + + + +## penv + + + +## pcurve + + + +## panchor + + + # Dynamics ## gain From 4107711dae2667f637f3f3e32055973417683568 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 18 Jan 2024 06:48:21 +0100 Subject: [PATCH 10/11] snapshot --- test/__snapshots__/examples.test.mjs.snap | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index dd7392d1..002ccb5f 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3332,6 +3332,55 @@ exports[`runs examples > example "pan" example index 0 1`] = ` ] `; +exports[`runs examples > example "panchor" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | note:c penv:12 panchor:0 ]", + "[ 1/1 → 2/1 | note:c penv:12 panchor:0.5 ]", + "[ 2/1 → 3/1 | note:c penv:12 panchor:1 ]", + "[ 3/1 → 4/1 | note:c penv:12 panchor:0.5 ]", +] +`; + +exports[`runs examples > example "pattack" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | note:c pattack:0 ]", + "[ 1/1 → 2/1 | note:eb pattack:0.1 ]", + "[ 2/1 → 3/1 | note:g pattack:0.25 ]", + "[ 3/1 → 4/1 | note:bb pattack:0.5 ]", +] +`; + +exports[`runs examples > example "pcurve" example index 0 1`] = ` +[ + "[ 0/1 → 1/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]", + "[ 1/2 → 1/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]", + "[ 1/1 → 3/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]", + "[ 3/2 → 2/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]", + "[ 2/1 → 5/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]", + "[ 5/2 → 3/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:0 ]", + "[ 3/1 → 7/2 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]", + "[ 7/2 → 4/1 | note:g1 s:sine pdecay:0.5 penv:32 pcurve:1 ]", +] +`; + +exports[`runs examples > example "pdecay" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | note:c pdecay:0 ]", + "[ 1/1 → 2/1 | note:eb pdecay:0.1 ]", + "[ 2/1 → 3/1 | note:g pdecay:0.25 ]", + "[ 3/1 → 4/1 | note:bb pdecay:0.5 ]", +] +`; + +exports[`runs examples > example "penv" example index 0 1`] = ` +[ + "[ 0/1 → 1/1 | note:c penv:12 ]", + "[ 1/1 → 2/1 | note:c penv:7 ]", + "[ 2/1 → 3/1 | note:c penv:1 ]", + "[ 3/1 → 4/1 | note:c penv:0.5 ]", +] +`; + exports[`runs examples > example "perlin" example index 0 1`] = ` [ "[ 0/1 → 1/4 | s:hh cutoff:512.5097280354112 ]", @@ -3660,6 +3709,15 @@ exports[`runs examples > example "postgain" example index 0 1`] = ` ] `; +exports[`runs examples > example "prelease" example index 0 1`] = ` +[ + "[ 0/1 → 1/2 | note:c release:0.5 prelease:0 ]", + "[ 1/1 → 3/2 | note:eb release:0.5 prelease:0.1 ]", + "[ 2/1 → 5/2 | note:g release:0.5 prelease:0.25 ]", + "[ 3/1 → 7/2 | note:bb release:0.5 prelease:0.5 ]", +] +`; + exports[`runs examples > example "press" example index 0 1`] = ` [ "[ 0/1 → 1/2 | s:hh ]", From 95cf201c6d0f7d2a68dde16ae860164daa99b548 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 18 Jan 2024 06:49:09 +0100 Subject: [PATCH 11/11] fix: heading --- website/src/pages/learn/effects.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/learn/effects.mdx b/website/src/pages/learn/effects.mdx index d0615dd9..be4a1184 100644 --- a/website/src/pages/learn/effects.mdx +++ b/website/src/pages/learn/effects.mdx @@ -138,7 +138,7 @@ There is one filter envelope for each filter type and thus one set of envelope f -## Pitch Envelope +# Pitch Envelope You can also control the pitch with envelopes! Pitch envelopes can breathe life into static sounds: