From 4f15d681b0cdccf1410c1107567c1f194d08e130 Mon Sep 17 00:00:00 2001 From: Aria Date: Thu, 21 Aug 2025 13:12:36 -0500 Subject: [PATCH 1/9] Add release parameter, avoid clicks, some cleanup --- packages/core/controls.mjs | 56 ++++++++++++-------- packages/superdough/superdough.mjs | 84 ++++++++++++++++++------------ 2 files changed, 87 insertions(+), 53 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index e6181d453..4d6f8d8b7 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -141,8 +141,8 @@ export const { note } = registerControl(['note', 'n']); */ export const { accelerate } = registerControl('accelerate'); /** - * * Sets the velocity from 0 to 1. Is multiplied together with gain. + * * @name velocity * @example * s("hh*8") @@ -254,7 +254,7 @@ export const { fmenv } = registerControl('fmenv'); export const { fmattack } = registerControl('fmattack'); /** - * waveform of the fm modulator + * Waveform of the fm modulator * * @name fmwave * @param {number | Pattern} wave waveform @@ -377,7 +377,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], ' // ['bpq'], export const { bandq, bpq } = registerControl('bandq', 'bpq'); /** - * a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample. + * A pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample. * * @memberof Pattern * @name begin @@ -438,7 +438,7 @@ export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb'); */ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); /** - * bit crusher effect. + * Bit crusher effect. * * @name crush * @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction). @@ -449,7 +449,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); // ['clhatdecay'], export const { crush } = registerControl('crush'); /** - * fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers + * Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * * @name coarse * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. @@ -460,7 +460,7 @@ export const { crush } = registerControl('crush'); export const { coarse } = registerControl('coarse'); /** - * modulate the amplitude of a sound with a continuous waveform + * Modulate the amplitude of a sound with a continuous waveform * * @name tremolo * @synonyms trem @@ -472,7 +472,7 @@ export const { coarse } = registerControl('coarse'); export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremoloskew', 'tremolophase'], 'trem'); /** - * modulate the amplitude of a sound with a continuous waveform + * Modulate the amplitude of a sound with a continuous waveform * * @name tremolosync * @synonyms tremsync @@ -487,7 +487,7 @@ export const { tremolosync } = registerControl( ); /** - * depth of amplitude modulation + * Depth of amplitude modulation * * @name tremolodepth * @synonyms tremdepth @@ -498,7 +498,7 @@ export const { tremolosync } = registerControl( */ export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); /** - * alter the shape of the modulation waveform + * Alter the shape of the modulation waveform * * @name tremoloskew * @synonyms tremskew @@ -510,7 +510,7 @@ export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); /** - * alter the phase of the modulation waveform + * Alter the phase of the modulation waveform * * @name tremolophase * @synonyms tremphase @@ -522,7 +522,7 @@ export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); export const { tremolophase } = registerControl('tremolophase', 'tremphase'); /** - * shape of amplitude modulation + * Shape of amplitude modulation * * @name tremoloshape * @param {number | Pattern} shape tri | square | sine | saw | ramp @@ -532,7 +532,7 @@ export const { tremolophase } = registerControl('tremolophase', 'tremphase'); */ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); /** - * filter overdrive for supported filter types + * Filter overdrive for supported filter types * * @name drive * @param {number | Pattern} amount @@ -542,7 +542,7 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); */ /** - * modulate the amplitude of an orbit to create a "sidechain" like effect + * Modulate the amplitude of an orbit to create a "sidechain" like effect * * @name duckorbit * @param {number | Pattern} orbit target orbit @@ -554,7 +554,7 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); export const { duck } = registerControl('duckorbit', 'duck'); /** - * the amount of ducking applied to target orbit + * The amount of ducking applied to target orbit * * @name duckdepth * @param {number | Pattern} depth depth of modulation from 0 to 1 @@ -566,16 +566,30 @@ export const { duck } = registerControl('duckorbit', 'duck'); export const { duckdepth } = registerControl('duckdepth'); /** - * the attack time of the duck effect + * The attack time of the duck effect. Can be used to prevent clicking or for creative rhythmic effects * * @name duckattack * @param {number | Pattern} time * @example - * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack("<0.2 0 0.4>").duckdepth(1)) + * sound: n(run(8)).scale("c:minor").s("sawtooth").lpf(200).delay(.7).orbit(2) + * duckerWithClick: s("bd*4").duckorbit(2).duckattack(0).postgain(0) + * _duckerWithoutClick: s("bd*4").duckorbit(2).duckattack(0.003).postgain(0) * */ export const { duckattack } = registerControl('duckattack', 'duckatt'); +/** + * The release time of the duck effect + * + * @name duckrelease + * @param {number | Pattern} time + * @example + * sound: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2) + * ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckrelease("<0.2 0 0.4>").duckdepth(1) + * + */ +export const { duckrelease } = registerControl('duckrelease', 'duckrelease'); + export const { drive } = registerControl('drive'); /** @@ -618,7 +632,7 @@ export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime', export const { channels, ch } = registerControl('channels', 'ch'); /** - * controls the pulsewidth of the pulse oscillator + * Controls the pulsewidth of the pulse oscillator * * @name pw * @param {number | Pattern} pulsewidth @@ -630,7 +644,7 @@ export const { channels, ch } = registerControl('channels', 'ch'); export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); /** - * controls the lfo rate for the pulsewidth of the pulse oscillator + * Controls the lfo rate for the pulsewidth of the pulse oscillator * * @name pwrate * @param {number | Pattern} rate @@ -642,7 +656,7 @@ export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); export const { pwrate } = registerControl('pwrate'); /** - * controls the lfo sweep for the pulsewidth of the pulse oscillator + * Controls the lfo sweep for the pulsewidth of the pulse oscillator * * @name pwsweep * @param {number | Pattern} sweep @@ -683,7 +697,7 @@ export const { phaserrate, ph, phaser } = registerControl( export const { phasersweep, phs } = registerControl('phasersweep', 'phs'); /** - * The center frequency of the phaser in HZ. Defaults to 1000 + * The center frequency of the phaser in HZ. Defaults to 1000 * * @name phasercenter * @synonyms phc @@ -711,7 +725,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc'); export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd', 'phasdp'); /** - * choose the channel the pattern is sent to in superdirt + * Choose the channel the pattern is sent to in superdirt * * @name channel * @param {number | Pattern} channel channel number diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index eb1466d91..a75727240 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -331,18 +331,19 @@ function getDelay(orbit, delaytime, delayfeedback, t, channels) { //logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`); } delayfeedback = clamp(delayfeedback, 0, 0.98); - if (!orbits[orbit].delayNode) { + let delayNode = orbits[orbit].delayNode; + if (delayNode === undefined) { const ac = getAudioContext(); const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback); dly.start?.(t); // for some reason, this throws when audion extension is installed.. connectToOrbit(dly, orbit); - orbits[orbit].delayNode = dly; + delayNode = dly; } - orbits[orbit].delayNode.delayTime.value !== delaytime && - orbits[orbit].delayNode.delayTime.setValueAtTime(delaytime, t); - orbits[orbit].delayNode.feedback.value !== delayfeedback && - orbits[orbit].delayNode.feedback.setValueAtTime(delayfeedback, t); - return orbits[orbit].delayNode; + delayNode.delayTime.value !== delaytime && + delayNode.delayTime.setValueAtTime(delaytime, t); + delayNode.feedback.value !== delayfeedback && + delayNode.feedback.setValueAtTime(delayfeedback, t); + return delayNode; } export function getLfo(audioContext, begin, end, properties = {}) { @@ -398,42 +399,59 @@ function getFilterType(ftype) { return typeof ftype === 'number' ? filterTypes[Math.floor(_mod(ftype, filterTypes.length))] : ftype; } -//type orbit { -// gain: number, -// reverbNode: reverbNode -// delayNode: delayNode -//} +// type orbit { +// output: GainNode, +// reverbNode: ConvolverNode +// delayNode: FeedbackDelayNode +// } let orbits = {}; function connectToOrbit(node, orbit) { if (orbits[orbit] == null) { errorLogger(new Error('target orbit does not exist'), 'superdough'); } - node.connect(orbits[orbit].gain); + node.connect(orbits[orbit].output); } function setOrbit(audioContext, orbit, channels) { if (orbits[orbit] == null) { orbits[orbit] = { - gain: new GainNode(audioContext, { gain: 1 }), + // Setup output node through which all audio filters prior to hitting + // the destination (and thus allows for global volume automation) + output: new GainNode(audioContext, { gain: 1 }), }; - connectToDestination(orbits[orbit].gain, channels); + connectToDestination(orbits[orbit].output, channels); } } -function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.1, duckdepth = 1) { - const targetArr = [targetOrbit].flat(); - targetArr.forEach((target) => { +function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.003, releasetime = 0.1, duckdepth = 1) { + const targetArr = [targetOrbit].flat(); + const attackArr = [attacktime].flat(); + const releaseArr = [releasetime].flat(); + const depthArr = [duckdepth].flat(); + + targetArr.forEach((target, idx) => { if (orbits[target] == null) { errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough'); return; } + const attack = attackArr[idx] ?? attackArr[0]; + const release = Math.max(releaseArr[idx] ?? releaseArr[0], 0.002); + const depth = depthArr[idx] ?? depthArr[0]; + const gainParam = orbits[target].output.gain; webAudioTimeout( audioContext, () => { - orbits[target].gain.gain.cancelScheduledValues(t); - const currVal = orbits[target].gain.gain.value; - orbits[target].gain.gain.linearRampToValueAtTime(clamp(1 - Math.pow(duckdepth, 0.5), 0.01, currVal), t); - orbits[target].gain.gain.exponentialRampToValueAtTime(1, t + Math.max(0.002, attacktime)); + gainParam.cancelScheduledValues(t); + const currVal = gainParam.value; + const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal); + + // Guarantees the value is set to currVal at time t. This in conjunction with + // cancelScheduledValues above emulates cancelAndHoldAtTime on browsers which lack + // that method + gainParam.setValueAtTime(currVal, t); + + gainParam.exponentialRampToValueAtTime(duckedVal, t + attack); + gainParam.exponentialRampToValueAtTime(1, t + attack + release); }, 0, t - 0.01, @@ -444,27 +462,28 @@ function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.1, duckdepth = 1 let hasChanged = (now, before) => now !== undefined && now !== before; function getReverb(orbit, duration, fade, lp, dim, ir) { // If no reverb has been created for a given orbit, create one - if (!orbits[orbit].reverbNode) { + let reverbNode = orbits[orbit].reverbNode; + if (reverbNode === undefined) { const ac = getAudioContext(); const reverb = ac.createReverb(duration, fade, lp, dim, ir); connectToOrbit(reverb, orbit); - orbits[orbit].reverbNode = reverb; + reverbNode = reverb; } if ( - hasChanged(duration, orbits[orbit].reverbNode.duration) || - hasChanged(fade, orbits[orbit].reverbNode.fade) || - hasChanged(lp, orbits[orbit].reverbNode.lp) || - hasChanged(dim, orbits[orbit].reverbNode.dim) || - orbits[orbit].reverbNode.ir !== ir + hasChanged(duration, reverbNode.duration) || + hasChanged(fade, reverbNode.fade) || + hasChanged(lp, reverbNode.lp) || + hasChanged(dim, reverbNode.dim) || + reverbNode.ir !== ir ) { // only regenerate when something has changed // avoids endless regeneration on things like // stack(s("a"), s("b").rsize(8)).room(.5) // this only works when args may stay undefined until here // setting default values breaks this - orbits[orbit].reverbNode.generate(duration, fade, lp, dim, ir); + reverbNode.generate(duration, fade, lp, dim, ir); } - return orbits[orbit].reverbNode; + return reverbNode; } export let analysers = {}, @@ -562,6 +581,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) density = getDefaultValue('density'), duckorbit, duckattack, + duckrelease, duckdepth, // filters fanchor = getDefaultValue('fanchor'), @@ -640,7 +660,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) setOrbit(ac, orbit, channels, t, cycle, cps); if (duckorbit != null) { - duckOrbit(ac, duckorbit, t, duckattack, duckdepth); + duckOrbit(ac, duckorbit, t, duckattack, duckrelease, duckdepth); } gain = applyGainCurve(nanFallback(gain, 1)); From bf5d9917ab8366e2d82289945405cc3721b448f6 Mon Sep 17 00:00:00 2001 From: Aria Date: Thu, 21 Aug 2025 13:20:12 -0500 Subject: [PATCH 2/9] Codeformat and examples --- packages/codemirror/autocomplete.mjs | 2 +- packages/superdough/superdough.mjs | 6 +-- test/__snapshots__/examples.test.mjs.snap | 57 ----------------------- website/src/pages/learn/samples.mdx | 2 +- website/src/repl/Repl.css | 1 - 5 files changed, 4 insertions(+), 64 deletions(-) diff --git a/packages/codemirror/autocomplete.mjs b/packages/codemirror/autocomplete.mjs index d3091d874..c0845895e 100644 --- a/packages/codemirror/autocomplete.mjs +++ b/packages/codemirror/autocomplete.mjs @@ -74,7 +74,7 @@ const hasExcludedTags = (doc) => const jsdocCompletions = jsdoc.docs .filter((doc) => isValidDoc(doc) && !hasExcludedTags(doc)) - // https://codemirror.net/docs/ref/#autocomplete.Completion + // https://codemirror.net/docs/ref/#autocomplete.Completion .map((doc) => ({ label: getDocLabel(doc), // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index a75727240..b8975a852 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -339,10 +339,8 @@ function getDelay(orbit, delaytime, delayfeedback, t, channels) { connectToOrbit(dly, orbit); delayNode = dly; } - delayNode.delayTime.value !== delaytime && - delayNode.delayTime.setValueAtTime(delaytime, t); - delayNode.feedback.value !== delayfeedback && - delayNode.feedback.setValueAtTime(delayfeedback, t); + delayNode.delayTime.value !== delaytime && delayNode.delayTime.setValueAtTime(delaytime, t); + delayNode.feedback.value !== delayfeedback && delayNode.feedback.setValueAtTime(delayfeedback, t); return delayNode; } diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 398749359..6002fa849 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3036,63 +3036,6 @@ exports[`runs examples > example "dry" example index 0 1`] = ` ] `; -exports[`runs examples > example "duckattack" example index 0 1`] = ` -[ - "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 0/1 → 1/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 1/8 → 1/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 1/4 → 3/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 3/8 → 1/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 1/2 → 5/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 5/8 → 3/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 3/4 → 7/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 7/8 → 1/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]", - "[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", - "[ 1/1 → 9/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 9/8 → 5/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", - "[ 5/4 → 11/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 11/8 → 3/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", - "[ 3/2 → 13/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 13/8 → 7/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", - "[ 7/4 → 15/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", - "[ 15/8 → 2/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]", - "[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", - "[ 2/1 → 17/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 17/8 → 9/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", - "[ 9/4 → 19/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 19/8 → 5/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", - "[ 5/2 → 21/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 21/8 → 11/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", - "[ 11/4 → 23/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", - "[ 23/8 → 3/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]", - "[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 3/1 → 25/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 25/8 → 13/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 13/4 → 27/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 27/8 → 7/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 7/2 → 29/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 29/8 → 15/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 15/4 → 31/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]", - "[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", - "[ 31/8 → 4/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]", -] -`; - exports[`runs examples > example "duckdepth" example index 0 1`] = ` [ "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", diff --git a/website/src/pages/learn/samples.mdx b/website/src/pages/learn/samples.mdx index 475319748..0deb60704 100644 --- a/website/src/pages/learn/samples.mdx +++ b/website/src/pages/learn/samples.mdx @@ -363,7 +363,7 @@ Sampler effects are functions that can be used to change the behaviour of sample ### scrub - +{' '} ### speed diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css index 62e7dcf24..b8443081f 100644 --- a/website/src/repl/Repl.css +++ b/website/src/repl/Repl.css @@ -198,5 +198,4 @@ } .autocomplete-info-tooltip::-webkit-scrollbar-thumb:hover { - } From 9a9fe83f9c341d5689475854c78de7ace8043d0a Mon Sep 17 00:00:00 2001 From: Aria Date: Thu, 21 Aug 2025 14:13:01 -0500 Subject: [PATCH 3/9] Example tests --- test/__snapshots__/examples.test.mjs.snap | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 6002fa849..85079269e 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3036,6 +3036,27 @@ exports[`runs examples > example "dry" example index 0 1`] = ` ] `; +exports[`runs examples > example "duckattack" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 1/4 → 1/2 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 1/2 → 3/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 3/4 → 1/1 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 1/1 → 5/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 5/4 → 3/2 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 3/2 → 7/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 7/4 → 2/1 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 2/1 → 9/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 9/4 → 5/2 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 5/2 → 11/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 11/4 → 3/1 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 3/1 → 13/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 13/4 → 7/2 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 7/2 → 15/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 15/4 → 4/1 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", +] +`; + exports[`runs examples > example "duckdepth" example index 0 1`] = ` [ "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", @@ -3118,6 +3139,31 @@ exports[`runs examples > example "duckorbit" example index 0 1`] = ` ] `; +exports[`runs examples > example "duckrelease" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", + "[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", + "[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", + "[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", + "[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", + "[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", + "[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", + "[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", + "[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", + "[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", + "[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", +] +`; + exports[`runs examples > example "duration" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:c s:piano duration:0.5 ]", From 3d25aa1b91ec75532682c3d786ca5d862c0c374f Mon Sep 17 00:00:00 2001 From: Aria Date: Tue, 26 Aug 2025 12:06:10 -0500 Subject: [PATCH 4/9] Rename params, more examples --- packages/core/controls.mjs | 74 ++++++--- packages/superdough/superdough.mjs | 16 +- test/__snapshots__/examples.test.mjs.snap | 191 +++++++++++++++++----- 3 files changed, 217 insertions(+), 64 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 4d6f8d8b7..4deb6f77f 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -542,13 +542,19 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); */ /** - * Modulate the amplitude of an orbit to create a "sidechain" like effect + * Modulate the amplitude of an orbit to create a "sidechain" like effect. + * + * Can be applied to multiple orbits with the ':' mininotation, e.g. `duckorbit("2:3")` * * @name duckorbit * @param {number | Pattern} orbit target orbit * @example * $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2) * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth(1) + * @example + * $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2) + * $: s("hh*16").orbit(3) + * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth(1) * */ export const { duck } = registerControl('duckorbit', 'duck'); @@ -556,40 +562,70 @@ export const { duck } = registerControl('duckorbit', 'duck'); /** * The amount of ducking applied to target orbit * + * Can vary across orbits with the ':' mininotation, e.g. `duckdepth("0.3:0.1")`. + * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. + * * @name duckdepth * @param {number | Pattern} depth depth of modulation from 0 to 1 * @example * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>")) + * @example + * $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2) + * $: s("hh*16").orbit(3) + * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth("1:0.5") * */ - export const { duckdepth } = registerControl('duckdepth'); /** - * The attack time of the duck effect. Can be used to prevent clicking or for creative rhythmic effects + * The time required for the ducked signal(s) to reach their lowest volume. + * Can be used to prevent clicking or for creative rhythmic effects. + + * Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`. + * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. + * + * @name duckonset + * @synonyms duckons + * + * @param {number | Pattern} time The onset time in seconds + * @example + * // Clicks + * sound: n(run(8)).scale("c:minor").s("sawtooth").lpf(200).delay(.7).orbit(2) + * duckerWithClick: s("bd*4").duckorbit(2).duckonset(0).postgain(0) + * @example + * // No clicks + * sound: n(run(8)).scale("c:minor").s("sawtooth").lpf(200).delay(.7).orbit(2) + * duckerWithoutClick: s("bd*4").duckorbit(2).duckonset(0.003).postgain(0) + * @example + * // Rhythmic + * noise: s("pink").distort("2:1").orbit(4) // used rhythmically with 0.3 onset below + * hhat: s("hh*16").orbit(7) + * ducker: s("bd*4").bank("tr909").duckorbit("4:7").duckonset("0.3:0.003").duckattack(0.25) + * + */ +export const { duckonset } = registerControl('duckonset', 'duckons'); + +/** + * The time required for the ducked signal(s) to return to their normal volume. + + * Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`. + * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * * @name duckattack - * @param {number | Pattern} time + * @synonyms duckatt + * + * @param {number | Pattern} time The attack time in seconds * @example - * sound: n(run(8)).scale("c:minor").s("sawtooth").lpf(200).delay(.7).orbit(2) - * duckerWithClick: s("bd*4").duckorbit(2).duckattack(0).postgain(0) - * _duckerWithoutClick: s("bd*4").duckorbit(2).duckattack(0.003).postgain(0) + * sound: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2) + * ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack("<0.2 0 0.4>").duckdepth(1) + * @example + * moreduck: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2) + * lessduck: s("hh*16").orbit(5) + * ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:5").duckattack("0.4:0.1") * */ export const { duckattack } = registerControl('duckattack', 'duckatt'); -/** - * The release time of the duck effect - * - * @name duckrelease - * @param {number | Pattern} time - * @example - * sound: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2) - * ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckrelease("<0.2 0 0.4>").duckdepth(1) - * - */ -export const { duckrelease } = registerControl('duckrelease', 'duckrelease'); - export const { drive } = registerControl('drive'); /** diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index b8975a852..98ffae16a 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -421,10 +421,10 @@ function setOrbit(audioContext, orbit, channels) { } } -function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.003, releasetime = 0.1, duckdepth = 1) { +function duckOrbit(audioContext, targetOrbit, t, onsettime = 0.003, attacktime = 0.1, duckdepth = 1) { const targetArr = [targetOrbit].flat(); + const onsetArr = [onsettime].flat(); const attackArr = [attacktime].flat(); - const releaseArr = [releasetime].flat(); const depthArr = [duckdepth].flat(); targetArr.forEach((target, idx) => { @@ -432,8 +432,8 @@ function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.003, releasetime errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough'); return; } - const attack = attackArr[idx] ?? attackArr[0]; - const release = Math.max(releaseArr[idx] ?? releaseArr[0], 0.002); + const onset = onsetArr[idx] ?? onsetArr[0]; + const attack = Math.max(attackArr[idx] ?? attackArr[0], 0.002); const depth = depthArr[idx] ?? depthArr[0]; const gainParam = orbits[target].output.gain; webAudioTimeout( @@ -448,8 +448,8 @@ function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.003, releasetime // that method gainParam.setValueAtTime(currVal, t); - gainParam.exponentialRampToValueAtTime(duckedVal, t + attack); - gainParam.exponentialRampToValueAtTime(1, t + attack + release); + gainParam.exponentialRampToValueAtTime(duckedVal, t + onset); + gainParam.exponentialRampToValueAtTime(1, t + onset + attack); }, 0, t - 0.01, @@ -578,8 +578,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) postgain = getDefaultValue('postgain'), density = getDefaultValue('density'), duckorbit, + duckonset, duckattack, - duckrelease, duckdepth, // filters fanchor = getDefaultValue('fanchor'), @@ -658,7 +658,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) setOrbit(ac, orbit, channels, t, cycle, cps); if (duckorbit != null) { - duckOrbit(ac, duckorbit, t, duckattack, duckrelease, duckdepth); + duckOrbit(ac, duckorbit, t, duckonset, duckattack, duckdepth); } gain = applyGainCurve(nanFallback(gain, 1)); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 85079269e..4655b5cd9 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3038,22 +3038,51 @@ exports[`runs examples > example "dry" example index 0 1`] = ` exports[`runs examples > example "duckattack" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 1/4 → 1/2 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 1/2 → 3/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 3/4 → 1/1 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 1/1 → 5/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 5/4 → 3/2 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 3/2 → 7/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 7/4 → 2/1 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 2/1 → 9/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 9/4 → 5/2 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 5/2 → 11/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 11/4 → 3/1 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 3/1 → 13/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 13/4 → 7/2 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 7/2 → 15/4 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", - "[ 15/4 → 4/1 | s:bd duckorbit:2 duckattack:0.003 postgain:0 ]", + "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", + "[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", + "[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", + "[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", + "[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", + "[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", + "[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", + "[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", + "[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", + "[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", + "[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", + "[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", +] +`; + +exports[`runs examples > example "duckattack" example index 1 1`] = ` +[ + "[ 0/1 → 1/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 1/4 → 5/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 1/2 → 9/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 11/16 → 3/4 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 7/8 → 15/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 1/1 → 17/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 5/4 → 21/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 3/2 → 25/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 27/16 → 7/4 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 15/8 → 31/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 2/1 → 33/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 9/4 → 37/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 5/2 → 41/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 43/16 → 11/4 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 23/8 → 47/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 3/1 → 49/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 13/4 → 53/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 7/2 → 57/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 59/16 → 15/4 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", + "[ 31/8 → 63/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]", ] `; @@ -3114,6 +3143,94 @@ exports[`runs examples > example "duckdepth" example index 0 1`] = ` ] `; +exports[`runs examples > example "duckdepth" example index 1 1`] = ` +[ + "[ 0/1 → 1/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 1/4 → 5/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 1/2 → 9/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 11/16 → 3/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 7/8 → 15/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 1/1 → 17/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 5/4 → 21/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 3/2 → 25/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 27/16 → 7/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 15/8 → 31/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 2/1 → 33/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 9/4 → 37/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 5/2 → 41/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 43/16 → 11/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 23/8 → 47/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 3/1 → 49/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 13/4 → 53/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 7/2 → 57/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 59/16 → 15/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", + "[ 31/8 → 63/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]", +] +`; + +exports[`runs examples > example "duckonset" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 1/4 → 1/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 1/2 → 3/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 3/4 → 1/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 1/1 → 5/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 5/4 → 3/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 3/2 → 7/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 7/4 → 2/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 2/1 → 9/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 9/4 → 5/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 5/2 → 11/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 11/4 → 3/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 3/1 → 13/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 13/4 → 7/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 7/2 → 15/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 15/4 → 4/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", +] +`; + +exports[`runs examples > example "duckonset" example index 1 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 1/4 → 1/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 1/2 → 3/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 3/4 → 1/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 1/1 → 5/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 5/4 → 3/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 3/2 → 7/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 7/4 → 2/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 2/1 → 9/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 9/4 → 5/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 5/2 → 11/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 11/4 → 3/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 3/1 → 13/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 13/4 → 7/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 7/2 → 15/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 15/4 → 4/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", +] +`; + +exports[`runs examples > example "duckonset" example index 2 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 1/4 → 1/2 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 1/2 → 3/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 3/4 → 1/1 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 1/1 → 5/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 5/4 → 3/2 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 3/2 → 7/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 7/4 → 2/1 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 2/1 → 9/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 9/4 → 5/2 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 5/2 → 11/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 11/4 → 3/1 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 3/1 → 13/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 13/4 → 7/2 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 7/2 → 15/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", + "[ 15/4 → 4/1 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]", +] +`; + exports[`runs examples > example "duckorbit" example index 0 1`] = ` [ "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", @@ -3139,28 +3256,28 @@ exports[`runs examples > example "duckorbit" example index 0 1`] = ` ] `; -exports[`runs examples > example "duckrelease" example index 0 1`] = ` +exports[`runs examples > example "duckorbit" example index 1 1`] = ` [ - "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", - "[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", - "[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", - "[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", - "[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckrelease:0 duckdepth:1 ]", - "[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", - "[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", - "[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", - "[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", - "[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckrelease:0.4 duckdepth:1 ]", - "[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", - "[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckrelease:0.2 duckdepth:1 ]", + "[ 0/1 → 1/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 1/4 → 5/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 1/2 → 9/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 11/16 → 3/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 7/8 → 15/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 1/1 → 17/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 5/4 → 21/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 3/2 → 25/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 27/16 → 7/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 15/8 → 31/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 2/1 → 33/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 9/4 → 37/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 5/2 → 41/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 43/16 → 11/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 23/8 → 47/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 3/1 → 49/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 13/4 → 53/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 7/2 → 57/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 59/16 → 15/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", + "[ 31/8 → 63/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]", ] `; From 022ac95bbd169b9a490e3bbccaa9933d0ba61f8e Mon Sep 17 00:00:00 2001 From: Aria Date: Tue, 26 Aug 2025 12:09:12 -0500 Subject: [PATCH 5/9] Typo --- packages/core/controls.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 4deb6f77f..25d2e52f0 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -580,7 +580,7 @@ export const { duckdepth } = registerControl('duckdepth'); /** * The time required for the ducked signal(s) to reach their lowest volume. * Can be used to prevent clicking or for creative rhythmic effects. - + * * Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`. * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * @@ -607,7 +607,7 @@ export const { duckonset } = registerControl('duckonset', 'duckons'); /** * The time required for the ducked signal(s) to return to their normal volume. - + * * Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`. * Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`. * From 4e2e79086446e7b82c5bf382125ef69b09ceba16 Mon Sep 17 00:00:00 2001 From: Aria Date: Mon, 1 Sep 2025 20:45:53 -0500 Subject: [PATCH 6/9] Set delay and reverb nodes properly --- packages/superdough/superdough.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 0c205bcca..290796956 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -336,7 +336,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) { const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback); dly.start?.(t); // for some reason, this throws when audion extension is installed.. connectToOrbit(dly, orbit); - delayNode = dly; + orbits[orbit].delayNode = dly; } delayNode.delayTime.value !== delaytime && delayNode.delayTime.setValueAtTime(delaytime, t); delayNode.feedback.value !== delayfeedback && delayNode.feedback.setValueAtTime(delayfeedback, t); @@ -464,7 +464,7 @@ function getReverb(orbit, duration, fade, lp, dim, ir, irspeed, irbegin) { const ac = getAudioContext(); const reverb = ac.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin); connectToOrbit(reverb, orbit); - reverbNode = reverb; + orbits[orbit].reverbNode = reverb; } if ( From 9c108862ef0edfe1d2c50e51337ff4f646bfbe2f Mon Sep 17 00:00:00 2001 From: Aria Date: Fri, 5 Sep 2025 22:09:50 -0500 Subject: [PATCH 7/9] Corrected piping --- packages/superdough/superdough.mjs | 14 +++++++------- website/src/repl/components/panel/SettingsTab.jsx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 290796956..5e5969fed 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -333,10 +333,10 @@ function getDelay(orbit, delaytime, delayfeedback, t) { let delayNode = orbits[orbit].delayNode; if (delayNode === undefined) { const ac = getAudioContext(); - const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback); - dly.start?.(t); // for some reason, this throws when audion extension is installed.. - connectToOrbit(dly, orbit); - orbits[orbit].delayNode = dly; + delayNode = ac.createFeedbackDelay(1, delaytime, delayfeedback); + delayNode.start?.(t); // for some reason, this throws when audion extension is installed.. + connectToOrbit(delayNode, orbit); + orbits[orbit].delayNode = delayNode; } delayNode.delayTime.value !== delaytime && delayNode.delayTime.setValueAtTime(delaytime, t); delayNode.feedback.value !== delayfeedback && delayNode.feedback.setValueAtTime(delayfeedback, t); @@ -462,9 +462,9 @@ function getReverb(orbit, duration, fade, lp, dim, ir, irspeed, irbegin) { let reverbNode = orbits[orbit].reverbNode; if (reverbNode === undefined) { const ac = getAudioContext(); - const reverb = ac.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin); - connectToOrbit(reverb, orbit); - orbits[orbit].reverbNode = reverb; + reverbNode = ac.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin); + connectToOrbit(reverbNode, orbit); + orbits[orbit].reverbNode = reverbNode; } if ( diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index 8f02ca8b8..26c9ae287 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -312,7 +312,7 @@ export function SettingsTab({ started }) { confirmDialog('Sure?').then((r) => { if (r) { const { userPatterns } = settingsMap.get(); // keep current patterns - settingsMap.set({...defaultSettings, userPatterns}); + settingsMap.set({ ...defaultSettings, userPatterns }); } }); }} From 798eb22d9a5a5be342b2e78a34f845a3c748d184 Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 6 Sep 2025 10:09:44 -0500 Subject: [PATCH 8/9] Use currentTime for exact scheduling --- packages/core/controls.mjs | 8 +-- packages/superdough/superdough.mjs | 21 ++++---- test/__snapshots__/examples.test.mjs.snap | 64 +++++++++++------------ 3 files changed, 47 insertions(+), 46 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 589a0634f..af0c4b1ab 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -590,12 +590,12 @@ export const { duckdepth } = registerControl('duckdepth'); * @param {number | Pattern} time The onset time in seconds * @example * // Clicks - * sound: n(run(8)).scale("c:minor").s("sawtooth").lpf(200).delay(.7).orbit(2) - * duckerWithClick: s("bd*4").duckorbit(2).duckonset(0).postgain(0) + * sound: freq("63.2388").s("sine").orbit(2).gain(4) + * duckerWithClick: s("bd*4").duckorbit(2).duckattack(0.3).duckonset(0).postgain(0) * @example * // No clicks - * sound: n(run(8)).scale("c:minor").s("sawtooth").lpf(200).delay(.7).orbit(2) - * duckerWithoutClick: s("bd*4").duckorbit(2).duckonset(0.003).postgain(0) + * sound: freq("63.2388").s("sine").orbit(2).gain(4) + * duckerWithoutClick: s("bd*4").duckorbit(2).duckattack(0.3).duckonset(0.01).postgain(0) * @example * // Rhythmic * noise: s("pink").distort("2:1").orbit(4) // used rhythmically with 0.3 onset below diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 5e5969fed..59590db6d 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -420,7 +420,7 @@ function setOrbit(audioContext, orbit, channels) { } } -function duckOrbit(audioContext, targetOrbit, t, onsettime = 0.003, attacktime = 0.1, duckdepth = 1) { +function duckOrbit(audioContext, targetOrbit, t, onsettime = 0, attacktime = 0.1, duckdepth = 1) { const targetArr = [targetOrbit].flat(); const onsetArr = [onsettime].flat(); const attackArr = [attacktime].flat(); @@ -438,17 +438,18 @@ function duckOrbit(audioContext, targetOrbit, t, onsettime = 0.003, attacktime = webAudioTimeout( audioContext, () => { - gainParam.cancelScheduledValues(t); + const now = audioContext.currentTime; + + // cancelScheduledValues and setValueAtTime together emulate cancelAndHoldAtTime + // on browsers which lack that method const currVal = gainParam.value; + gainParam.cancelScheduledValues(now); + gainParam.setValueAtTime(currVal, now); + + const t0 = Math.max(t, now); // guard against now > t const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal); - - // Guarantees the value is set to currVal at time t. This in conjunction with - // cancelScheduledValues above emulates cancelAndHoldAtTime on browsers which lack - // that method - gainParam.setValueAtTime(currVal, t); - - gainParam.exponentialRampToValueAtTime(duckedVal, t + onset); - gainParam.exponentialRampToValueAtTime(1, t + onset + attack); + gainParam.exponentialRampToValueAtTime(duckedVal, t0 + onset); + gainParam.exponentialRampToValueAtTime(1, t0 + onset + attack); }, 0, t - 0.01, diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index ba54328b7..7579794fd 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3170,43 +3170,43 @@ exports[`runs examples > example "duckdepth" example index 1 1`] = ` exports[`runs examples > example "duckonset" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 1/4 → 1/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 1/2 → 3/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 3/4 → 1/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 1/1 → 5/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 5/4 → 3/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 3/2 → 7/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 7/4 → 2/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 2/1 → 9/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 9/4 → 5/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 5/2 → 11/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 11/4 → 3/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 3/1 → 13/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 13/4 → 7/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 7/2 → 15/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 15/4 → 4/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 0/1 → 1/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 1/4 → 1/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 1/2 → 3/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 3/4 → 1/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 1/1 → 5/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 5/4 → 3/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 3/2 → 7/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 7/4 → 2/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 2/1 → 9/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 9/4 → 5/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 5/2 → 11/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 11/4 → 3/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 3/1 → 13/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 13/4 → 7/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 7/2 → 15/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 15/4 → 4/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", ] `; exports[`runs examples > example "duckonset" example index 1 1`] = ` [ - "[ 0/1 → 1/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 1/4 → 1/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 1/2 → 3/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 3/4 → 1/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 1/1 → 5/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 5/4 → 3/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 3/2 → 7/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 7/4 → 2/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 2/1 → 9/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 9/4 → 5/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 5/2 → 11/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 11/4 → 3/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 3/1 → 13/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 13/4 → 7/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 7/2 → 15/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 15/4 → 4/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 0/1 → 1/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 1/4 → 1/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 1/2 → 3/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 3/4 → 1/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 1/1 → 5/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 5/4 → 3/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 3/2 → 7/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 7/4 → 2/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 2/1 → 9/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 9/4 → 5/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 5/2 → 11/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 11/4 → 3/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 3/1 → 13/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 13/4 → 7/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 7/2 → 15/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 15/4 → 4/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", ] `; From ed00686804fecfa05609f7c21c93f2ce3512fded Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 13 Sep 2025 14:29:28 -0500 Subject: [PATCH 9/9] Codeformat --- website/src/pages/learn/xen.mdx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/website/src/pages/learn/xen.mdx b/website/src/pages/learn/xen.mdx index 32e2c0b43..b8b27ac7f 100644 --- a/website/src/pages/learn/xen.mdx +++ b/website/src/pages/learn/xen.mdx @@ -16,10 +16,7 @@ These functions allow the use of scales other than your typical chromatic 12 bas Here's an example of how to configure a basic hexany scale: - + Try other scales like `hexany1`, `iraq`, `gumbeng`, `gunkali`, or `tranh3` @@ -67,6 +64,7 @@ Another helpful trick when exploring new tunings is to strum them. Many have a much more enchanting sound that was chosen over many generations of musicians for being strummed. Take the `sanza` tuning: + + This quality is often due to how the tunings were formed with instruments that were played differently than a piano. As such, some tunings are much better strummed, with the subtle clash of the detuned notes actually making the sound much more magical: