From 7f103e77e3a28ba325b2c94a12e795b17b6077c7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 28 Jul 2025 04:05:21 +0200 Subject: [PATCH 01/45] prefix "S" for solo --- packages/core/repl.mjs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 47231af4d..47489054e 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -138,9 +138,9 @@ export function repl({ // allows muting a pattern x with x_ or _x return silence; } - if (id === '$') { + if (id.includes('$')) { // allows adding anonymous patterns with $: - id = `$${anonymousIndex}`; + id = `${id}${anonymousIndex}`; anonymousIndex++; } pPatterns[id] = this; @@ -201,6 +201,13 @@ export function repl({ let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions); if (Object.keys(pPatterns).length) { let patterns = Object.values(pPatterns); + + // if there are solo patterns, only use those + const soloPatterns = Object.entries(pPatterns).filter(([key]) => key.length > 1 && key.startsWith('S')); + if (soloPatterns.length) { + patterns = Object.values(Object.fromEntries(soloPatterns)); + } + if (eachTransform) { // Explicit lambda so only element (not index and array) are passed patterns = patterns.map((x) => eachTransform(x)); From 47c85f8540386d36c42c5bc64f952bde12ac79d5 Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 16 Oct 2025 14:06:33 +0200 Subject: [PATCH 02/45] fix interoperability of `all` with hydra Hydra add methods on Array.prototype which breaks the Array enumeration with for...in. Use for...of instead cf https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#difference_between_for...of_and_for...in for details on the issue without this fix, it is impossible to use both `await initHydra()` and `all` in the same strudel code. Strudel errors with "Error: got "undefined" instead of pattern." example code: https://strudel.cc/#YXdhaXQgaW5pdEh5ZHJhKCkKCmFsbChwaWFub3JvbGwpCgokOiBzKCJiZCIp --- packages/core/repl.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 171697eb9..064ef1eb8 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -227,8 +227,8 @@ export function repl({ pattern = eachTransform(pattern); } if (allTransforms.length) { - for (let i in allTransforms) { - pattern = allTransforms[i](pattern); + for (const transform of allTransforms) { + pattern = transform(pattern); } } From bfa8fa3c757b2b457ee6aa5ef1a55f8bf14902e2 Mon Sep 17 00:00:00 2001 From: jeromew Date: Sun, 16 Nov 2025 09:23:11 +0000 Subject: [PATCH 03/45] [perf] fix `connect leak` when .noise() is in the mix --- packages/superdough/helpers.mjs | 14 +++++++++++++- packages/superdough/noise.mjs | 3 ++- packages/superdough/synth.mjs | 23 +++++++++++++---------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 47161ed61..de64df0e8 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -262,7 +262,15 @@ export function drywet(dry, wet, wetAmount = 0) { let mix = ac.createGain(); dry_gain.connect(mix); wet_gain.connect(mix); - return mix; + return { + node: mix, + onended: () => { + dry_gain.disconnect(mix); + wet_gain.disconnect(mix); + dry.disconnect(dry_gain); + wet.disconnect(wet_gain); + }, + }; } let curves = ['linear', 'exponential']; @@ -297,6 +305,10 @@ export function getVibratoOscillator(param, value, t) { gain.gain.value = vibmod * 100; vibratoOscillator.connect(gain); gain.connect(param); + vibratoOscillator.onended = () => { + gain.disconnect(param); + vibratoOscillator.disconnect(gain); + }; vibratoOscillator.start(t); return vibratoOscillator; } diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 816dd252b..3e41515df 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -65,8 +65,9 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) { export function getNoiseMix(inputNode, wet, t) { const noiseOscillator = getNoiseOscillator('pink', t); const noiseMix = drywet(inputNode, noiseOscillator.node, wet); + noiseOscillator.node.onended = () => noiseMix.onended(); return { - node: noiseMix, + node: noiseMix.node, stop: (time) => noiseOscillator?.stop(time), }; } diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index e35b98806..a9ec7975c 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -47,19 +47,17 @@ export function registerSynthSounds() { [0.001, 0.05, 0.6, 0.01], ); - let sound = getOscillator(s, t, value); - let { node: o, stop, triggerRelease } = sound; - // turn down const g = gainNode(0.3); - const { duration } = value; - - o.onended = () => { - o.disconnect(); + let sound = getOscillator(s, t, value, () => { g.disconnect(); onended(); - }; + }); + + let { node: o, stop, triggerRelease } = sound; + + const { duration } = value; const envGain = gainNode(1); let node = o.connect(g).connect(envGain); @@ -446,7 +444,7 @@ export function waveformN(partials, type) { } // expects one of waveforms as s -export function getOscillator(s, t, value) { +export function getOscillator(s, t, value, onended) { let { n: partials, duration, noise = 0 } = value; let o; // If no partials are given, use stock waveforms @@ -460,7 +458,6 @@ export function getOscillator(s, t, value) { } // set frequency o.frequency.value = getFrequencyFromValue(value); - o.start(t); let vibratoOscillator = getVibratoOscillator(o.detune, value, t); @@ -473,6 +470,12 @@ export function getOscillator(s, t, value) { noiseMix = getNoiseMix(o, noise, t); } + o.onended = () => { + noiseMix?.node.disconnect(); + onended(); + }; + o.start(t); + return { node: noiseMix?.node || o, stop: (time) => { From 30cc46ea66a4cd3d9b5b7dca3ab6446fc76d6845 Mon Sep 17 00:00:00 2001 From: scrappy_fiddler Date: Mon, 17 Nov 2025 20:04:40 +0100 Subject: [PATCH 04/45] wchooseCycles has now notes in an example --- packages/core/signal.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 604d1c805..62fc26ad8 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -524,7 +524,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); * @example * wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8) * @example - * wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s() + * wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note() * @example * // The probability can itself be a pattern * wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s() From 99beb4454deb9099a40f355ba4dc3898f341adbe Mon Sep 17 00:00:00 2001 From: scrappy_fiddler Date: Mon, 17 Nov 2025 20:49:26 +0100 Subject: [PATCH 05/45] update snapshot with note example --- test/__snapshots__/examples.test.mjs.snap | 96 +++++++++++------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 32e61c31d..31e4bddd3 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -12484,54 +12484,54 @@ exports[`runs examples > example "wchooseCycles" example index 0 1`] = ` exports[`runs examples > example "wchooseCycles" example index 1 1`] = ` [ - "[ 0/1 → 1/12 | s:bd ]", - "[ 1/12 → 1/6 | s:bd ]", - "[ 1/6 → 1/4 | s:bd ]", - "[ 1/4 → 1/3 | s:bd ]", - "[ 1/3 → 5/12 | s:bd ]", - "[ 5/12 → 1/2 | s:bd ]", - "[ 1/2 → 7/12 | s:sd ]", - "[ 7/12 → 2/3 | s:sd ]", - "[ 2/3 → 3/4 | s:sd ]", - "[ 3/4 → 5/6 | s:bd ]", - "[ 5/6 → 11/12 | s:bd ]", - "[ 11/12 → 1/1 | s:bd ]", - "[ 1/1 → 13/12 | s:bd ]", - "[ 13/12 → 7/6 | s:bd ]", - "[ 7/6 → 5/4 | s:bd ]", - "[ 5/4 → 4/3 | s:bd ]", - "[ 4/3 → 17/12 | s:bd ]", - "[ 17/12 → 3/2 | s:bd ]", - "[ 3/2 → 19/12 | s:hh ]", - "[ 19/12 → 5/3 | s:hh ]", - "[ 5/3 → 7/4 | s:hh ]", - "[ 7/4 → 11/6 | s:bd ]", - "[ 11/6 → 23/12 | s:bd ]", - "[ 23/12 → 2/1 | s:bd ]", - "[ 2/1 → 25/12 | s:hh ]", - "[ 25/12 → 13/6 | s:hh ]", - "[ 13/6 → 9/4 | s:hh ]", - "[ 9/4 → 7/3 | s:hh ]", - "[ 7/3 → 29/12 | s:hh ]", - "[ 29/12 → 5/2 | s:hh ]", - "[ 5/2 → 31/12 | s:bd ]", - "[ 31/12 → 8/3 | s:bd ]", - "[ 8/3 → 11/4 | s:bd ]", - "[ 11/4 → 17/6 | s:bd ]", - "[ 17/6 → 35/12 | s:bd ]", - "[ 35/12 → 3/1 | s:bd ]", - "[ 3/1 → 37/12 | s:bd ]", - "[ 37/12 → 19/6 | s:bd ]", - "[ 19/6 → 13/4 | s:bd ]", - "[ 13/4 → 10/3 | s:sd ]", - "[ 10/3 → 41/12 | s:sd ]", - "[ 41/12 → 7/2 | s:sd ]", - "[ 7/2 → 43/12 | s:hh ]", - "[ 43/12 → 11/3 | s:hh ]", - "[ 11/3 → 15/4 | s:hh ]", - "[ 15/4 → 23/6 | s:bd ]", - "[ 23/6 → 47/12 | s:bd ]", - "[ 47/12 → 4/1 | s:bd ]", + "[ 0/1 → 1/12 | note:c ]", + "[ 1/12 → 1/6 | note:c ]", + "[ 1/6 → 1/4 | note:c ]", + "[ 1/4 → 1/3 | note:c ]", + "[ 1/3 → 5/12 | note:c ]", + "[ 5/12 → 1/2 | note:c ]", + "[ 1/2 → 7/12 | note:f ]", + "[ 7/12 → 2/3 | note:f ]", + "[ 2/3 → 3/4 | note:f ]", + "[ 3/4 → 5/6 | note:c ]", + "[ 5/6 → 11/12 | note:c ]", + "[ 11/12 → 1/1 | note:c ]", + "[ 1/1 → 13/12 | note:c ]", + "[ 13/12 → 7/6 | note:c ]", + "[ 7/6 → 5/4 | note:c ]", + "[ 5/4 → 4/3 | note:c ]", + "[ 4/3 → 17/12 | note:c ]", + "[ 17/12 → 3/2 | note:c ]", + "[ 3/2 → 19/12 | note:a ]", + "[ 19/12 → 5/3 | note:a ]", + "[ 5/3 → 7/4 | note:a ]", + "[ 7/4 → 11/6 | note:c ]", + "[ 11/6 → 23/12 | note:c ]", + "[ 23/12 → 2/1 | note:c ]", + "[ 2/1 → 25/12 | note:a ]", + "[ 25/12 → 13/6 | note:a ]", + "[ 13/6 → 9/4 | note:a ]", + "[ 9/4 → 7/3 | note:a ]", + "[ 7/3 → 29/12 | note:a ]", + "[ 29/12 → 5/2 | note:a ]", + "[ 5/2 → 31/12 | note:c ]", + "[ 31/12 → 8/3 | note:c ]", + "[ 8/3 → 11/4 | note:c ]", + "[ 11/4 → 17/6 | note:c ]", + "[ 17/6 → 35/12 | note:c ]", + "[ 35/12 → 3/1 | note:c ]", + "[ 3/1 → 37/12 | note:c ]", + "[ 37/12 → 19/6 | note:c ]", + "[ 19/6 → 13/4 | note:c ]", + "[ 13/4 → 10/3 | note:f ]", + "[ 10/3 → 41/12 | note:f ]", + "[ 41/12 → 7/2 | note:f ]", + "[ 7/2 → 43/12 | note:a ]", + "[ 43/12 → 11/3 | note:a ]", + "[ 11/3 → 15/4 | note:a ]", + "[ 15/4 → 23/6 | note:c ]", + "[ 23/6 → 47/12 | note:c ]", + "[ 47/12 → 4/1 | note:c ]", ] `; From dd2c808cd71a7f5892f7e57c0f0a6fe7540d332c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 19 Nov 2025 21:55:35 +0100 Subject: [PATCH 06/45] hotfix: reduce sounds-tab click to play latency --- website/src/repl/components/panel/SoundsTab.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/repl/components/panel/SoundsTab.jsx b/website/src/repl/components/panel/SoundsTab.jsx index a9f10bf12..3d363f8a1 100644 --- a/website/src/repl/components/panel/SoundsTab.jsx +++ b/website/src/repl/components/panel/SoundsTab.jsx @@ -126,7 +126,7 @@ export function SoundsTab() { try { // Pre-load the sample by calling onTrigger with a future time // This triggers the loading but schedules playback for later - const time = ctx.currentTime + 0.5; // Give 500ms for loading + const time = ctx.currentTime + 0.05; const ref = await onTrigger(time, params, onended); trigRef.current = ref; if (ref?.node) { From 8ed72b4573503890d04fffafee50e1f28cca9990 Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 20 Nov 2025 13:05:41 +0000 Subject: [PATCH 07/45] [perf] fix `connect-leak` in `delay` effect --- packages/superdough/superdough.mjs | 3 ++- packages/superdough/superdoughoutput.mjs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 65c444120..c548e7396 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -692,7 +692,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) // delay if (delay > 0 && delaytime > 0 && delayfeedback > 0) { orbitBus.getDelay(delaytime, delayfeedback, t); - orbitBus.sendDelay(post, delay); + const send = orbitBus.sendDelay(post, delay); + audioNodes.push(send) } // reverb if (room > 0) { diff --git a/packages/superdough/superdoughoutput.mjs b/packages/superdough/superdoughoutput.mjs index a4235efd0..d8ead7d06 100644 --- a/packages/superdough/superdoughoutput.mjs +++ b/packages/superdough/superdoughoutput.mjs @@ -82,7 +82,7 @@ export class Orbit { } sendDelay(node, amount) { - effectSend(node, this.delayNode, amount); + return effectSend(node, this.delayNode, amount); } duck(t, onsettime = 0, attacktime = 0.1, depth = 1) { From d9f63b8dfa28112192ed5b6a2d2cfe7cb41cc41d Mon Sep 17 00:00:00 2001 From: jeromew Date: Sun, 16 Nov 2025 09:23:11 +0000 Subject: [PATCH 08/45] [perf] fix `connect leak` when .noise() is in the mix --- packages/superdough/helpers.mjs | 14 +++++++++++++- packages/superdough/noise.mjs | 3 ++- packages/superdough/synth.mjs | 23 +++++++++++++---------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 47161ed61..de64df0e8 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -262,7 +262,15 @@ export function drywet(dry, wet, wetAmount = 0) { let mix = ac.createGain(); dry_gain.connect(mix); wet_gain.connect(mix); - return mix; + return { + node: mix, + onended: () => { + dry_gain.disconnect(mix); + wet_gain.disconnect(mix); + dry.disconnect(dry_gain); + wet.disconnect(wet_gain); + }, + }; } let curves = ['linear', 'exponential']; @@ -297,6 +305,10 @@ export function getVibratoOscillator(param, value, t) { gain.gain.value = vibmod * 100; vibratoOscillator.connect(gain); gain.connect(param); + vibratoOscillator.onended = () => { + gain.disconnect(param); + vibratoOscillator.disconnect(gain); + }; vibratoOscillator.start(t); return vibratoOscillator; } diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 816dd252b..3e41515df 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -65,8 +65,9 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) { export function getNoiseMix(inputNode, wet, t) { const noiseOscillator = getNoiseOscillator('pink', t); const noiseMix = drywet(inputNode, noiseOscillator.node, wet); + noiseOscillator.node.onended = () => noiseMix.onended(); return { - node: noiseMix, + node: noiseMix.node, stop: (time) => noiseOscillator?.stop(time), }; } diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 10df1776b..2dc9e2be8 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -48,19 +48,17 @@ export function registerSynthSounds() { [0.001, 0.05, 0.6, 0.01], ); - let sound = getOscillator(s, t, value); - let { node: o, stop, triggerRelease } = sound; - // turn down const g = gainNode(0.3); - const { duration } = value; - - o.onended = () => { - o.disconnect(); + let sound = getOscillator(s, t, value, () => { g.disconnect(); onended(); - }; + }); + + let { node: o, stop, triggerRelease } = sound; + + const { duration } = value; const envGain = gainNode(1); let node = o.connect(g).connect(envGain); @@ -460,7 +458,7 @@ export function waveformN(partials, phases, type) { } // expects one of waveforms as s -export function getOscillator(s, t, value) { +export function getOscillator(s, t, value, onended) { const { duration, noise = 0 } = value; const partials = value.partials ?? value.n; let o; @@ -482,7 +480,6 @@ export function getOscillator(s, t, value) { } // set frequency o.frequency.value = getFrequencyFromValue(value); - o.start(t); let vibratoOscillator = getVibratoOscillator(o.detune, value, t); @@ -495,6 +492,12 @@ export function getOscillator(s, t, value) { noiseMix = getNoiseMix(o, noise, t); } + o.onended = () => { + noiseMix?.node.disconnect(); + onended(); + }; + o.start(t); + return { node: noiseMix?.node || o, stop: (time) => { From 7267990e20ed07c86b95a5c17985461e4f77f8d6 Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 20 Nov 2025 13:35:37 +0000 Subject: [PATCH 09/45] Fix codeformat --- packages/superdough/superdough.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index c548e7396..650f9c2ce 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -693,7 +693,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) if (delay > 0 && delaytime > 0 && delayfeedback > 0) { orbitBus.getDelay(delaytime, delayfeedback, t); const send = orbitBus.sendDelay(post, delay); - audioNodes.push(send) + audioNodes.push(send); } // reverb if (room > 0) { From 0435711051ee5d890f15a11b8c2fdd0def231996 Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 20 Nov 2025 17:36:00 +0000 Subject: [PATCH 10/45] [perf] fix `connect-leak` added by #1742 when noise() is not used --- packages/superdough/synth.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 2dc9e2be8..768638f04 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -493,6 +493,7 @@ export function getOscillator(s, t, value, onended) { } o.onended = () => { + o.disconnect(); noiseMix?.node.disconnect(); onended(); }; From 907f03f3bfe35bc4b6695490030e6f4822747629 Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 20 Nov 2025 17:40:51 +0000 Subject: [PATCH 11/45] [perf] fix `connect-leak` in fm modulation --- packages/superdough/helpers.mjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index de64df0e8..58c4aa35e 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -362,9 +362,9 @@ const mod = (freq, range = 1, type = 'sine') => { } osc.start(); - const g = new GainNode(ctx, { gain: range }); + const g = gainNode(range); osc.connect(g); // -range, range - return { node: g, stop: (t) => osc.stop(t) }; + return { node: g, stop: (t) => osc.stop(t), osc: osc }; }; const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => { const carrfreq = frequencyparam.value; @@ -416,6 +416,11 @@ export function applyFM(param, value, begin) { modulator.connect(envGain); envGain.connect(param); } + fmmod.osc.onended = () => { + envGain.disconnect(); + modulator.disconnect(); + fmmod.osc.disconnect(); + }; } return { stop }; } From 4758effdc6386fc6e1e38aeb0445db33a264b126 Mon Sep 17 00:00:00 2001 From: Aria Date: Thu, 20 Nov 2025 18:22:44 -0600 Subject: [PATCH 12/45] Use frac due to negative frequencies from FM --- packages/superdough/worklets.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 359bc833a..32367b64c 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -532,7 +532,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n)); // We must wrap this here because it is passed into sawblep below which // has domain [0, 1] - const dt = ffrac(freqVoice * INVSR); + const dt = frac(freqVoice * INVSR); this.phase[n] = this.phase[n] ?? Math.random(); const v = waveshapes.sawblep(this.phase[n], dt); From 21543956c6b26f3ae96dcf43f80f377e1a51e755 Mon Sep 17 00:00:00 2001 From: Aria Date: Thu, 20 Nov 2025 18:26:12 -0600 Subject: [PATCH 13/45] Also for wavetable --- packages/superdough/worklets.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 32367b64c..e60cd223e 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1338,7 +1338,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const tablePos = clamp(pv(parameters.position, i), 0, 1); const idx = tablePos * (this.numFrames - 1); const fIdx = idx | 0; - const frac = idx - fIdx; + const interpT = idx - fIdx; const warpAmount = clamp(pv(parameters.warp, i), 0, 1); const warpMode = pv(parameters.warpMode, i); const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1); @@ -1368,13 +1368,13 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const ph = this._warpPhase(this.phase[n], warpAmount, warpMode); const s0 = this._sampleFrame(table[fIdx], ph); const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph); - let s = s0 + (s1 - s0) * frac; + let s = lerp(s0, s1, interpT); if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) { s = -s; } outL[i] += s * gainL * normalizer; outR[i] += s * gainR * normalizer; - this.phase[n] = ffrac(this.phase[n] + dPhase); + this.phase[n] = frac(this.phase[n] + dPhase); } } return true; From f637b8b0dd0c3bbe29c53d128a943b9e6260be9b Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 20 Nov 2025 17:40:51 +0000 Subject: [PATCH 14/45] [perf] fix `connect-leak` in fm modulation --- packages/superdough/helpers.mjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index de64df0e8..58c4aa35e 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -362,9 +362,9 @@ const mod = (freq, range = 1, type = 'sine') => { } osc.start(); - const g = new GainNode(ctx, { gain: range }); + const g = gainNode(range); osc.connect(g); // -range, range - return { node: g, stop: (t) => osc.stop(t) }; + return { node: g, stop: (t) => osc.stop(t), osc: osc }; }; const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => { const carrfreq = frequencyparam.value; @@ -416,6 +416,11 @@ export function applyFM(param, value, begin) { modulator.connect(envGain); envGain.connect(param); } + fmmod.osc.onended = () => { + envGain.disconnect(); + modulator.disconnect(); + fmmod.osc.disconnect(); + }; } return { stop }; } From e72e26eb81a62bcb9a96dedfa05fb82fac857cca Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 22 Nov 2025 17:11:17 -0500 Subject: [PATCH 15/45] add comment --- packages/core/repl.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 6e3d97c92..bcb70eec9 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -217,6 +217,7 @@ export function repl({ let patterns = []; let soloActive = false; for (const [key, value] of Object.entries(pPatterns)) { + // handle soloed patterns ex: S$: s("bd!4") const isSolod = key.length > 1 && key.startsWith('S'); if (isSolod && soloActive === false) { // first time we see a soloed pattern, clear existing patterns From 19fd0fc649ba8b92271facd3ae9e9e5ac01ebd2c Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 22 Nov 2025 17:17:41 -0500 Subject: [PATCH 16/45] rm unessecary file --- packages/draw/text.mjs | 144 ----------------------------------------- 1 file changed, 144 deletions(-) delete mode 100644 packages/draw/text.mjs diff --git a/packages/draw/text.mjs b/packages/draw/text.mjs deleted file mode 100644 index ba76df079..000000000 --- a/packages/draw/text.mjs +++ /dev/null @@ -1,144 +0,0 @@ -import { Pattern, midiToFreq, getFrequency } from '@strudel/core'; -import { getTheme, getDrawContext } from './draw.mjs'; - -const c = midiToFreq(36); - -const circlePos = (cx, cy, radius, angle) => { - angle = angle * Math.PI * 2; - const x = Math.sin(angle) * radius + cx; - const y = Math.cos(angle) * radius + cy; - return [x, y]; -}; - -const freq2angle = (freq, root) => { - return 0.5 - (Math.log2(freq / root) % 1); -}; - -export function pitchwheel({ - haps, - ctx, - id, - hapcircles = 1, - circle = 0, - edo = 12, - root = c, - thickness = 3, - hapRadius = 6, - mode = 'flake', - margin = 10, -} = {}) { - const connectdots = mode === 'polygon'; - const centerlines = mode === 'flake'; - const w = ctx.canvas.width; - const h = ctx.canvas.height; - ctx.clearRect(0, 0, w, h); - const color = getTheme().foreground; - - const size = Math.min(w, h); - const radius = size / 2 - thickness / 2 - hapRadius - margin; - const centerX = w / 2; - const centerY = h / 2; - - if (id) { - haps = haps.filter((hap) => hap.hasTag(id)); - } - ctx.strokeStyle = color; - ctx.fillStyle = color; - ctx.globalAlpha = 1; - ctx.lineWidth = thickness; - - if (circle) { - ctx.beginPath(); - ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); - ctx.stroke(); - } - - if (edo) { - Array.from({ length: edo }, (_, i) => { - const angle = freq2angle(root * Math.pow(2, i / edo), root); - const [x, y] = circlePos(centerX, centerY, radius, angle); - ctx.beginPath(); - ctx.arc(x, y, hapRadius, 0, 2 * Math.PI); - ctx.fill(); - }); - ctx.stroke(); - } - - let shape = []; - ctx.lineWidth = hapRadius; - haps.forEach((hap) => { - let freq; - try { - freq = getFrequency(hap); - } catch (err) { - return; - } - const angle = freq2angle(freq, root); - const [x, y] = circlePos(centerX, centerY, radius, angle); - const hapColor = hap.value.color || color; - ctx.strokeStyle = hapColor; - ctx.fillStyle = hapColor; - const { velocity = 1, gain = 1 } = hap.value || {}; - const alpha = velocity * gain; - ctx.globalAlpha = alpha; - shape.push([x, y, angle, hapColor, alpha]); - ctx.beginPath(); - if (hapcircles) { - ctx.moveTo(x + hapRadius, y); - ctx.arc(x, y, hapRadius, 0, 2 * Math.PI); - ctx.fill(); - } - if (centerlines) { - ctx.moveTo(centerX, centerY); - ctx.lineTo(x, y); - } - ctx.stroke(); - }); - - ctx.strokeStyle = color; - ctx.globalAlpha = 1; - if (connectdots && shape.length) { - shape = shape.sort((a, b) => a[2] - b[2]); - ctx.beginPath(); - ctx.moveTo(shape[0][0], shape[0][1]); - shape.forEach(([x, y, _, color, alpha]) => { - ctx.strokeStyle = color; - ctx.globalAlpha = alpha; - ctx.lineTo(x, y); - }); - ctx.lineTo(shape[0][0], shape[0][1]); - ctx.stroke(); - } - - return; -} - -/** - * Renders a pitch circle to visualize frequencies within one octave - * @name pitchwheel - * @param {number} hapcircles - * @param {number} circle - * @param {number} edo - * @param {string} root - * @param {number} thickness - * @param {number} hapRadius - * @param {string} mode - * @param {number} margin - * @example - * n("0 .. 12").scale("C:chromatic") - * .s("sawtooth") - * .lpf(500) - * ._pitchwheel() - */ -Pattern.prototype.pitchwheel = function (options = {}) { - let { ctx = getDrawContext(), id = 1 } = options; - return this.tag(id).onPaint((_, time, haps) => - pitchwheel({ - ...options, - time, - ctx, - haps: haps.filter((hap) => hap.isActive(time)), - id, - }), - ); -}; From 2efd56e33155096dd4454f9fe68e6b5f43bdc1e1 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 22 Nov 2025 18:10:28 -0500 Subject: [PATCH 17/45] add synonym --- 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 9a2be7f44..8297746dc 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1840,7 +1840,7 @@ export const { octave } = registerControl('octave'); * s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2) * ) */ -export const { orbit } = registerControl('orbit'); +export const { orbit } = registerControl('orbit', 'o'); // TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that export const { overgain } = registerControl('overgain'); // TODO: what is this? not found in tidal doc. Similar to above, but limited to 1 From 1d5f3a4f30c415b53ea1b0a5d88d31d318725631 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 22 Nov 2025 18:21:24 -0500 Subject: [PATCH 18/45] prevent filter modulation pops --- packages/superdough/helpers.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index de64df0e8..8e45f7bca 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -238,7 +238,7 @@ export function createFilter(context, start, end, params, cps) { if (sync != null) { rate = cps * sync; } - const lfoValues = { depth, dcoffset, skew, shape, frequency: rate }; + const lfoValues = { depth, dcoffset, skew, shape, frequency: rate, min: 10, max: 20000 }; getParamLfo(context, frequencyParam, start, end, lfoValues); return filter; } From adc3a9ac6cb780c653c843c5c296c5fbb8f106b0 Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 22 Nov 2025 20:51:30 -0600 Subject: [PATCH 19/45] Swap with temp variable --- packages/superdough/worklets.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index e60cd223e..bf633a63b 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -543,8 +543,9 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { if (pn >= 1.0) pn -= 1.0; this.phase[n] = pn; // invert right and left gain + const tmp = gainL; gainL = gainR; - gainR = gainL; + gainR = tmp; } } return true; From 30e672a0271bf3233859230ef96cfe8b9cecc1cd Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 22 Nov 2025 22:49:23 -0500 Subject: [PATCH 20/45] wip --- packages/superdough/helpers.mjs | 27 ++++++++++++++++++++++----- packages/superdough/superdough.mjs | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 8e45f7bca..7bca637f2 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -191,8 +191,10 @@ export function applyParameterModulators(audioContext, param, start, end, envelo const lfo = getParamLfo(audioContext, param, start, end, lfoValues); return { lfo, disconnect: () => lfo?.disconnect() }; } - -export function createFilter(context, start, end, params, cps) { +function biloparExponential(x, k) { + return Math.sign(x) * 2 ** (Math.abs(x) * k); +} +export function createFilter(context, start, end, params, cps, cycle) { let { frequency, anchor, @@ -231,6 +233,7 @@ export function createFilter(context, start, end, params, cps) { const offset = envAbs * anchor; let min = clamp(2 ** -offset * frequency, 0, 20000); let max = clamp(2 ** (envAbs - offset) * frequency, 0, 20000); + if (env < 0) [min, max] = [max, min]; getParamADSR(frequencyParam, attack, decay, sustain, release, min, max, start, end, 'exponential'); } @@ -238,8 +241,22 @@ export function createFilter(context, start, end, params, cps) { if (sync != null) { rate = cps * sync; } - const lfoValues = { depth, dcoffset, skew, shape, frequency: rate, min: 10, max: 20000 }; - getParamLfo(context, frequencyParam, start, end, lfoValues); + const hasLFO = [depth, skew, shape, rate].some((v) => v !== undefined); + if (hasLFO) { + depth = depth ?? 1 + + const mag = Math.abs(depth); + const sign = Math.sign(depth); + + const shaped = 2 ** (mag * (1 - dcoffset)); + const dp = clamp((sign * shaped * frequency) - frequency, -20000, 20000); + console.info(dp) + const time = cycle / cps; + // let d = 2 ** -dcoffset + const lfoValues = { depth: dp, dcoffset, skew, shape, frequency: rate, min: 10, max: 20000, time }; + getParamLfo(context, frequencyParam, start, end, lfoValues); + } + return filter; } @@ -386,7 +403,7 @@ export function applyFM(param, value, begin) { duration, } = value; let modulator; - let stop = () => {}; + let stop = () => { }; if (fmModulationIndex) { const ac = getAudioContext(); diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 650f9c2ce..4e1a145c0 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -559,7 +559,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) }; const lpParams = pickAndRename(value, lpMap); lpParams.type = 'lowpass'; - let lp = () => createFilter(ac, t, end, lpParams, cps); + let lp = () => createFilter(ac, t, end, lpParams, cps, cycle); chain.push(lp()); if (ftype === '24db') { chain.push(lp()); From 7bbb653963ace62fdf7f5aba33600da576927d02 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 22 Nov 2025 23:42:20 -0500 Subject: [PATCH 21/45] fixed --- packages/core/controls.mjs | 7 +++++++ packages/superdough/helpers.mjs | 28 +++++++++++++--------------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 9a2be7f44..299169973 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1294,6 +1294,8 @@ export const { fanchor } = registerControl('fanchor'); * * @name lprate * @param {number | Pattern} rate rate in hertz + * @example + * note("*16").s("sawtooth").lpf(600).lprate("<4 8 2 1>") */ export const { lprate } = registerControl('lprate'); @@ -1302,6 +1304,8 @@ export const { lprate } = registerControl('lprate'); * * @name lpsync * @param {number | Pattern} rate rate in cycles + * @example + * note("*16").s("sawtooth").lpf(600).lpsync("<4 8 2 1>") */ export const { lpsync } = registerControl('lpsync'); @@ -1310,7 +1314,10 @@ export const { lpsync } = registerControl('lpsync'); * * @name lpdepth * @param {number | Pattern} depth depth of modulation + * @example + * note("*16").s("sawtooth").lpf(600).lpdepth("<1 .5 1.8 0>") */ + export const { lpdepth } = registerControl('lpdepth'); /** diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 7bca637f2..65f5cc36d 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -191,9 +191,6 @@ export function applyParameterModulators(audioContext, param, start, end, envelo const lfo = getParamLfo(audioContext, param, start, end, lfoValues); return { lfo, disconnect: () => lfo?.disconnect() }; } -function biloparExponential(x, k) { - return Math.sign(x) * 2 ** (Math.abs(x) * k); -} export function createFilter(context, start, end, params, cps, cycle) { let { frequency, @@ -233,7 +230,6 @@ export function createFilter(context, start, end, params, cps, cycle) { const offset = envAbs * anchor; let min = clamp(2 ** -offset * frequency, 0, 20000); let max = clamp(2 ** (envAbs - offset) * frequency, 0, 20000); - if (env < 0) [min, max] = [max, min]; getParamADSR(frequencyParam, attack, decay, sustain, release, min, max, start, end, 'exponential'); } @@ -243,17 +239,19 @@ export function createFilter(context, start, end, params, cps, cycle) { } const hasLFO = [depth, skew, shape, rate].some((v) => v !== undefined); if (hasLFO) { - depth = depth ?? 1 - - const mag = Math.abs(depth); - const sign = Math.sign(depth); - - const shaped = 2 ** (mag * (1 - dcoffset)); - const dp = clamp((sign * shaped * frequency) - frequency, -20000, 20000); - console.info(dp) + depth = depth ?? 1; const time = cycle / cps; - // let d = 2 ** -dcoffset - const lfoValues = { depth: dp, dcoffset, skew, shape, frequency: rate, min: 10, max: 20000, time }; + const lfoValues = { + depth: (depth ?? 1) * frequency, + dcoffset, + skew, + shape, + frequency: rate ?? cps, + min: -frequency + 30, + max: 20000 - frequency, + time, + curve: 1, + }; getParamLfo(context, frequencyParam, start, end, lfoValues); } @@ -403,7 +401,7 @@ export function applyFM(param, value, begin) { duration, } = value; let modulator; - let stop = () => { }; + let stop = () => {}; if (fmModulationIndex) { const ac = getAudioContext(); From bde17594ceae18ed4a483715932b124638b62d72 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 23 Nov 2025 00:33:52 -0500 Subject: [PATCH 22/45] test --- test/__snapshots__/examples.test.mjs.snap | 207 ++++++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 31e4bddd3..4345eedbf 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -5969,6 +5969,75 @@ exports[`runs examples > example "lpdecay" example index 0 1`] = ` ] `; +exports[`runs examples > example "lpdepth" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepth:1 ]", + "[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]", + "[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]", + "[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]", + "[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepth:0 ]", +] +`; + exports[`runs examples > example "lpenv" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lpattack:0.5 lpenv:4 ]", @@ -6157,6 +6226,75 @@ exports[`runs examples > example "lpq" example index 0 1`] = ` ] `; +exports[`runs examples > example "lprate" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]", + "[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lprate:4 ]", + "[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]", + "[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lprate:4 ]", + "[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]", + "[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lprate:4 ]", + "[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]", + "[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]", + "[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lprate:8 ]", + "[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]", + "[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]", + "[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lprate:8 ]", + "[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]", + "[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lprate:2 ]", + "[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]", + "[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lprate:2 ]", + "[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]", + "[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lprate:2 ]", + "[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lprate:2 ]", + "[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]", + "[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lprate:1 ]", + "[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]", + "[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lprate:1 ]", + "[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lprate:1 ]", + "[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]", + "[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lprate:1 ]", +] +`; + exports[`runs examples > example "lprelease" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:c2 s:sawtooth clip:0.5 cutoff:300 lpenv:4 lprelease:0.5 release:0.5 ]", @@ -6199,6 +6337,75 @@ exports[`runs examples > example "lpsustain" example index 0 1`] = ` ] `; +exports[`runs examples > example "lpsync" example index 0 1`] = ` +[ + "[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]", + "[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]", + "[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]", + "[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]", + "[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]", + "[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpsync:4 ]", + "[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]", + "[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]", + "[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]", + "[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]", + "[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]", + "[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpsync:8 ]", + "[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]", + "[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]", + "[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]", + "[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]", + "[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]", + "[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]", + "[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]", + "[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]", + "[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]", + "[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]", + "[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]", + "[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]", + "[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]", + "[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpsync:1 ]", +] +`; + exports[`runs examples > example "lrate" example index 0 1`] = ` [ "[ 0/1 → 1/1 | n:0 s:supersquare leslie:1 lrate:1 ]", From cf12e3e1d8892de00d20a5c6f4e1a5a164cbc3f5 Mon Sep 17 00:00:00 2001 From: Aria Date: Sun, 23 Nov 2025 12:21:55 -0600 Subject: [PATCH 23/45] Hook up the octave control --- packages/superdough/helpers.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index de64df0e8..59608698d 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -517,7 +517,7 @@ export const getDistortion = (distort, postgain, algorithm) => { }; export const getFrequencyFromValue = (value, defaultNote = 36) => { - let { note, freq } = value; + let { note, freq, octave = 0 } = value; note = note || defaultNote; if (typeof note === 'string') { note = noteToMidi(note); // e.g. c3 => 48 @@ -526,7 +526,7 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => { if (!freq && typeof note === 'number') { freq = midiToFreq(note); // + 48); } - + freq *= Math.pow(2, octave); return Number(freq); }; From 6c0d550b6b3629312eb8e65719781c28effab943 Mon Sep 17 00:00:00 2001 From: Aria Date: Sun, 23 Nov 2025 12:25:40 -0600 Subject: [PATCH 24/45] Fix example --- packages/core/controls.mjs | 3 +-- test/__snapshots__/examples.test.mjs.snap | 24 +++++++++++------------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 9a2be7f44..5c5883e9a 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1822,8 +1822,7 @@ export const { nudge } = registerControl('nudge'); * @name octave * @param {number | Pattern} octave octave number * @example - * n("0,4,7").s('supersquare').octave("<3 4 5 6>").osc() - * @superDirtOnly + * n("0,4,7").scale("F:minor").s('supersaw').octave("<0 1 2 3>") */ export const { octave } = registerControl('octave'); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 31e4bddd3..4d4da8b22 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -6888,18 +6888,18 @@ exports[`runs examples > example "nrpv" example index 0 1`] = ` exports[`runs examples > example "octave" example index 0 1`] = ` [ - "[ 0/1 → 1/1 | n:0 s:supersquare octave:3 ]", - "[ 0/1 → 1/1 | n:4 s:supersquare octave:3 ]", - "[ 0/1 → 1/1 | n:7 s:supersquare octave:3 ]", - "[ 1/1 → 2/1 | n:0 s:supersquare octave:4 ]", - "[ 1/1 → 2/1 | n:4 s:supersquare octave:4 ]", - "[ 1/1 → 2/1 | n:7 s:supersquare octave:4 ]", - "[ 2/1 → 3/1 | n:0 s:supersquare octave:5 ]", - "[ 2/1 → 3/1 | n:4 s:supersquare octave:5 ]", - "[ 2/1 → 3/1 | n:7 s:supersquare octave:5 ]", - "[ 3/1 → 4/1 | n:0 s:supersquare octave:6 ]", - "[ 3/1 → 4/1 | n:4 s:supersquare octave:6 ]", - "[ 3/1 → 4/1 | n:7 s:supersquare octave:6 ]", + "[ 0/1 → 1/1 | note:F3 s:supersaw octave:0 ]", + "[ 0/1 → 1/1 | note:C4 s:supersaw octave:0 ]", + "[ 0/1 → 1/1 | note:F4 s:supersaw octave:0 ]", + "[ 1/1 → 2/1 | note:F3 s:supersaw octave:1 ]", + "[ 1/1 → 2/1 | note:C4 s:supersaw octave:1 ]", + "[ 1/1 → 2/1 | note:F4 s:supersaw octave:1 ]", + "[ 2/1 → 3/1 | note:F3 s:supersaw octave:2 ]", + "[ 2/1 → 3/1 | note:C4 s:supersaw octave:2 ]", + "[ 2/1 → 3/1 | note:F4 s:supersaw octave:2 ]", + "[ 3/1 → 4/1 | note:F3 s:supersaw octave:3 ]", + "[ 3/1 → 4/1 | note:C4 s:supersaw octave:3 ]", + "[ 3/1 → 4/1 | note:F4 s:supersaw octave:3 ]", ] `; From 6221099af80f87e783339d46f31b685b0ce0f3ed Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 23 Nov 2025 18:09:38 -0500 Subject: [PATCH 25/45] working --- packages/core/repl.mjs | 1 + .../repl/components/button/action-button.jsx | 24 ++++++++++++ .../panel/ImportPrebakeScriptButton.jsx | 39 +++++++++++++++++++ website/src/repl/components/panel/Panel.jsx | 2 +- .../src/repl/components/panel/PatternsTab.jsx | 2 +- .../src/repl/components/panel/SettingsTab.jsx | 12 ++++-- website/src/repl/prebake.mjs | 11 +++++- website/src/repl/useReplContext.jsx | 11 ++++-- website/src/settings.mjs | 3 ++ 9 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 website/src/repl/components/panel/ImportPrebakeScriptButton.jsx diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index bcb70eec9..90b4fcbb8 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -147,6 +147,7 @@ export function repl({ // set pattern methods that use this repl via closure const injectPatternMethods = () => { + Pattern.prototype.p = function (id) { if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) { // allows muting a pattern x with x_ or _x diff --git a/website/src/repl/components/button/action-button.jsx b/website/src/repl/components/button/action-button.jsx index d589b7bed..eb395212c 100644 --- a/website/src/repl/components/button/action-button.jsx +++ b/website/src/repl/components/button/action-button.jsx @@ -8,3 +8,27 @@ export function ActionButton({ children, label, labelIsHidden, className, ...but ); } +export function ActionInput({ label, className, ...inputProps }) { + return ( + + ); +} + +export function SpecialActionButton(props) { + const { className, ...buttonProps } = props + + return + +} +export function SpecialActionInput(props) { + const { className, ...inputProps } = props + return +} + + diff --git a/website/src/repl/components/panel/ImportPrebakeScriptButton.jsx b/website/src/repl/components/panel/ImportPrebakeScriptButton.jsx new file mode 100644 index 000000000..c521c527b --- /dev/null +++ b/website/src/repl/components/panel/ImportPrebakeScriptButton.jsx @@ -0,0 +1,39 @@ +import { errorLogger } from '@strudel/core'; +import { useSettings, storeStartupScript } from '../../../settings.mjs'; +import { SpecialActionInput } from '../button/action-button'; + +async function importScript(script) { + const reader = new FileReader() + reader.readAsText(script) + + reader.onload = () => { + const text = reader.result; + console.info(text) + storeStartupScript(text) + + }; + + reader.onerror = () => { + errorLogger(new Error('failed to import prebake script'), 'ImportPrebakeScriptButton') + } + +} +export function ImportPrebakeScriptButton() { + const settings = useSettings(); + + return importScript(e.target.files[0])} /> + + // return +} \ No newline at end of file diff --git a/website/src/repl/components/panel/Panel.jsx b/website/src/repl/components/panel/Panel.jsx index b26c2edef..1af859f81 100644 --- a/website/src/repl/components/panel/Panel.jsx +++ b/website/src/repl/components/panel/Panel.jsx @@ -127,7 +127,7 @@ function PanelContent({ context, tab }) { case tabNames.reference: return ; case tabNames.settings: - return ; + return ; case tabNames.files: return ; default: diff --git a/website/src/repl/components/panel/PatternsTab.jsx b/website/src/repl/components/panel/PatternsTab.jsx index 5e0d50e72..67585d20a 100644 --- a/website/src/repl/components/panel/PatternsTab.jsx +++ b/website/src/repl/components/panel/PatternsTab.jsx @@ -83,7 +83,7 @@ function UserPatterns({ context }) { const activePattern = useActivePattern(); const viewingPatternStore = useViewingPatternData(); const viewingPatternData = parseJSON(viewingPatternStore); - const { userPatterns, patternFilter, patternAutoStart } = useSettings(); + const { userPatterns, patternFilter, patternAutoStart, } = useSettings(); const viewingPatternID = viewingPatternData?.id; return (
diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index 85991f55c..2c66ed1a3 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -7,6 +7,8 @@ import { AudioDeviceSelector } from './AudioDeviceSelector.jsx'; import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx'; import { confirmDialog } from '../../util.mjs'; import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio'; +import { ActionButton, SpecialActionButton } from '../button/action-button.jsx'; +import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx'; function Checkbox({ label, value, onChange, disabled = false }) { return ( @@ -86,7 +88,7 @@ const fontFamilyOptions = { const RELOAD_MSG = 'Changing this setting requires the window to reload itself. OK?'; -export function SettingsTab({ started }) { +export function SettingsTab({ started,context }) { const { theme, keybindings, @@ -113,6 +115,7 @@ export function SettingsTab({ started }) { isTabIndentationEnabled, isMultiCursorEnabled, patternAutoStart, + startupScript, } = useSettings(); const shouldAlwaysSync = isUdels(); const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; @@ -204,6 +207,8 @@ export function SettingsTab({ started }) { />
+ + {/* context.editStartupScript(startupScript)}> */} Try clicking the logo in the top left! - + ); diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 8a6ccc7e0..960771f05 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -1,14 +1,23 @@ -import { Pattern, noteToMidi, valueToMidi } from '@strudel/core'; +import { Pattern, noteToMidi, valueToMidi } from '@strudel/core'; import { aliasBank, registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio'; import { registerSamplesFromDB } from './idbutils.mjs'; import './piano.mjs'; import './files.mjs'; +import { settingsMap } from '@src/settings.mjs'; +import { evaluate } from '@strudel/transpiler'; const { BASE_URL } = import.meta.env; const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; const baseCDN = 'https://strudel.b-cdn.net'; export async function prebake() { + + // const settings = settingsMap.get() + + + // const prebakeScript = settings.startupScript || ''; + // await evaluate(prebakeScript); + // https://archive.org/details/SalamanderGrandPianoV3 // License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm await Promise.all([ diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index ac30fe342..4e8b2b946 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th import { code2hash, getPerformanceTimeSeconds, logger, silence } from '@strudel/core'; import { getDrawContext } from '@strudel/draw'; -import { transpiler } from '@strudel/transpiler'; +import { evaluate, transpiler } from '@strudel/transpiler'; import { getAudioContextCurrentTime, webaudioOutput, @@ -47,6 +47,7 @@ if (typeof window !== 'undefined') { multiChannelOrbits: parseBoolean(multiChannelOrbits), }); modulesLoading = loadModules(); + // prebakeScript = evaluate(settingsMap.get().startupScript ?? '') presets = prebake(); drawContext = getDrawContext(); clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); @@ -63,11 +64,10 @@ async function getModule(name) { const initialCode = `// LOADING`; export function useReplContext() { - const { isSyncEnabled, audioEngineTarget } = useSettings(); + const { isSyncEnabled, audioEngineTarget, startupScript } = useSettings(); const shouldUseWebaudio = audioEngineTarget !== audioEngineTargets.osc; const defaultOutput = shouldUseWebaudio ? webaudioOutput : superdirtOutput; const getTime = shouldUseWebaudio ? getAudioContextCurrentTime : getPerformanceTimeSeconds; - const init = useCallback(() => { const drawTime = [-2, 2]; const drawContext = getDrawContext(); @@ -84,7 +84,9 @@ export function useReplContext() { pattern: silence, drawTime, drawContext, - prebake: async () => Promise.all([modulesLoading, presets]), + prebake: async () => Promise.all([modulesLoading, presets,]).then(() => { + return evaluate(startupScript ?? '') + }), onUpdateState: (state) => { setReplState({ ...state }); }, @@ -200,6 +202,7 @@ export function useReplContext() { } }; + const handleEvaluate = () => { editorRef.current.evaluate(); }; diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 7c62b0ded..1fff439be 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -45,6 +45,7 @@ export const defaultSettings = { isPanelOpen: true, togglePanelTrigger: 'click', //click | hover userPatterns: '{}', + startupScript: '//edit this script to run code on startup\n', audioEngineTarget: audioEngineTargets.webaudio, isButtonRowHidden: false, isCSSAnimationDisabled: false, @@ -108,6 +109,8 @@ export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab); export const setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool); export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool); +export const storeStartupScript = (script) => settingsMap.setKey('startupScript', script); + export const setIsZen = (active) => settingsMap.setKey('isZen', !!active); const patternSetting = (key) => From d5dfbd7aa4d4236bdf143b94b7abddf7a9fe93c5 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 23 Nov 2025 18:45:13 -0500 Subject: [PATCH 26/45] cleanup --- packages/core/repl.mjs | 1 - .../repl/components/button/action-button.jsx | 45 ++++++++++------- .../panel/ImportPrebakeScriptButton.jsx | 48 ++++++++----------- website/src/repl/components/panel/Panel.jsx | 2 +- .../src/repl/components/panel/PatternsTab.jsx | 2 +- .../src/repl/components/panel/SettingsTab.jsx | 3 +- website/src/repl/prebake.mjs | 4 +- website/src/repl/useReplContext.jsx | 8 ++-- 8 files changed, 54 insertions(+), 59 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 90b4fcbb8..bcb70eec9 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -147,7 +147,6 @@ export function repl({ // set pattern methods that use this repl via closure const injectPatternMethods = () => { - Pattern.prototype.p = function (id) { if (typeof id === 'string' && (id.startsWith('_') || id.endsWith('_'))) { // allows muting a pattern x with x_ or _x diff --git a/website/src/repl/components/button/action-button.jsx b/website/src/repl/components/button/action-button.jsx index eb395212c..7364d2af0 100644 --- a/website/src/repl/components/button/action-button.jsx +++ b/website/src/repl/components/button/action-button.jsx @@ -8,27 +8,36 @@ export function ActionButton({ children, label, labelIsHidden, className, ...but ); } -export function ActionInput({ label, className, ...inputProps }) { + +export function SpecialActionButton(props) { + const { className, ...buttonProps } = props; + return ( -