From e2cdce5ac9454f9c242fcb0e410a6888c89c496f Mon Sep 17 00:00:00 2001 From: "Lu[ke] Wilson" Date: Fri, 29 Aug 2025 15:35:41 +0100 Subject: [PATCH 01/91] add flux function --- packages/core/pattern.mjs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index f1ebbe3e9..154643c74 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2337,6 +2337,17 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, return stack(left, right).setSteps(__steps ? lcm(left._steps, right._steps) : undefined); }); +/** + * Like juxBy, except it flips the ears each cycle. + * @name fluxBy + * @synonyms fluxby + * @example + * s("bd lt [~ ht] mt cp ~ bd hh").fluxBy(".8", rev) + */ +export const { fluxBy, fluxby } = register(['fluxBy', 'fluxby'], function (by, func, pat) { + return slowcat(pat._juxBy(by, func, pat), pat._juxBy(-by, func, pat)); +}); + /** * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. * @example @@ -2350,6 +2361,19 @@ export const jux = register('jux', function (func, pat) { return pat._juxBy(1, func, pat); }); +/** + * Like jux, but flips the ears each cycle. + * @example + * s("bd lt [~ ht] mt cp ~ bd hh").flux(rev) + * @example + * s("bd lt [~ ht] mt cp ~ bd hh").flux(press) + * @example + * s("bd lt [~ ht] mt cp ~ bd hh").flux(iter(4)) + */ +export const flux = register('flux', function (func, pat) { + return pat._fluxBy(1, func, pat); +}); + /** * Superimpose and offset multiple times, applying the given function each time. * @name echoWith From 781c477078f1429a7dabe4e628fa287a3bf7ef5f Mon Sep 17 00:00:00 2001 From: "Lu[ke] Wilson" Date: Fri, 29 Aug 2025 17:20:18 +0100 Subject: [PATCH 02/91] rename to juxFlip --- packages/core/pattern.mjs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 154643c74..c68d45104 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2339,14 +2339,17 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, /** * Like juxBy, except it flips the ears each cycle. - * @name fluxBy - * @synonyms fluxby + * @name juxFlipBy + * @synonyms juxflipby, fluxBy, fluxby * @example - * s("bd lt [~ ht] mt cp ~ bd hh").fluxBy(".8", rev) + * s("bd lt [~ ht] mt cp ~ bd hh").juxFlipBy(".8", rev) */ -export const { fluxBy, fluxby } = register(['fluxBy', 'fluxby'], function (by, func, pat) { - return slowcat(pat._juxBy(by, func, pat), pat._juxBy(-by, func, pat)); -}); +export const { juxFlipBy, juxflipby, fluxBy, fluxby } = register( + ['juxFlipBy', 'juxflipby', 'fluxBy', 'fluxby'], + function (by, func, pat) { + return slowcat(pat._juxBy(by, func, pat), pat._juxBy(-by, func, pat)); + }, +); /** * The jux function creates strange stereo effects, by applying a function to a pattern, but only in the right-hand channel. @@ -2363,15 +2366,17 @@ export const jux = register('jux', function (func, pat) { /** * Like jux, but flips the ears each cycle. + * @name juxFlip + * @synonyms flux * @example - * s("bd lt [~ ht] mt cp ~ bd hh").flux(rev) + * s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(rev) * @example - * s("bd lt [~ ht] mt cp ~ bd hh").flux(press) + * s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(press) * @example - * s("bd lt [~ ht] mt cp ~ bd hh").flux(iter(4)) + * s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(iter(4)) */ -export const flux = register('flux', function (func, pat) { - return pat._fluxBy(1, func, pat); +export const { juxFlip, flux } = register(['juxFlip', 'flux'], function (func, pat) { + return pat._jux(1, func, pat); }); /** From 3373fa39a6be2d2d3765dcbd52b63c30e4f3b10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 18 Oct 2025 12:14:39 +0200 Subject: [PATCH 03/91] Fix the documentation of `stretch` --- packages/core/controls.mjs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 7e3c2a8e2..130608447 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1935,12 +1935,16 @@ export const { compressorRelease } = registerControl('compressorRelease'); export const { speed } = registerControl('speed'); /** - * Changes the speed of sample playback, i.e. a cheap way of changing pitch. + * Changes the pitch of the sample without changing its speed. + * The frequencies are multiplied by (factor + 1) for positive numbers + * and by max(factor / 4 + 1, 0) for negative numbers. + * So tuning up by octaves can be done with 1, 3, 7, ... + * and tuning down by octaves with -2, -3, -3.5... * * @name stretch - * @param {number | Pattern} factor -inf to inf, negative numbers play the sample backwards. + * @param {number | Pattern} factor between `-4` and `inf`. Positive increases pitch, 0 does nothing, negative decreases the pitch. * @example - * s("gm_flute").stretch("1 2 .5") + * s("gm_flute").stretch("<3 2 1 0 -2>") * */ export const { stretch } = registerControl('stretch'); From 7d948fd1a76cb92d0da8a29a8710a977fa010083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 18 Oct 2025 12:25:15 +0200 Subject: [PATCH 04/91] Fix example snapshot --- packages/core/controls.mjs | 2 +- test/__snapshots__/examples.test.mjs.snap | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 130608447..03de38666 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1944,7 +1944,7 @@ export const { speed } = registerControl('speed'); * @name stretch * @param {number | Pattern} factor between `-4` and `inf`. Positive increases pitch, 0 does nothing, negative decreases the pitch. * @example - * s("gm_flute").stretch("<3 2 1 0 -2>") + * s("gm_flute").stretch("<2 1 0 -2>") * */ export const { stretch } = registerControl('stretch'); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 73278947d..a23806c1c 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -10439,18 +10439,10 @@ exports[`runs examples > example "stepcat" example index 1 1`] = ` exports[`runs examples > example "stretch" example index 0 1`] = ` [ - "[ (0/1 → 1/3) ⇝ 1/1 | s:gm_flute stretch:1 ]", - "[ 0/1 ⇜ (1/3 → 2/3) ⇝ 1/1 | s:gm_flute stretch:2 ]", - "[ 0/1 ⇜ (2/3 → 1/1) | s:gm_flute stretch:0.5 ]", - "[ (1/1 → 4/3) ⇝ 2/1 | s:gm_flute stretch:1 ]", - "[ 1/1 ⇜ (4/3 → 5/3) ⇝ 2/1 | s:gm_flute stretch:2 ]", - "[ 1/1 ⇜ (5/3 → 2/1) | s:gm_flute stretch:0.5 ]", - "[ (2/1 → 7/3) ⇝ 3/1 | s:gm_flute stretch:1 ]", - "[ 2/1 ⇜ (7/3 → 8/3) ⇝ 3/1 | s:gm_flute stretch:2 ]", - "[ 2/1 ⇜ (8/3 → 3/1) | s:gm_flute stretch:0.5 ]", - "[ (3/1 → 10/3) ⇝ 4/1 | s:gm_flute stretch:1 ]", - "[ 3/1 ⇜ (10/3 → 11/3) ⇝ 4/1 | s:gm_flute stretch:2 ]", - "[ 3/1 ⇜ (11/3 → 4/1) | s:gm_flute stretch:0.5 ]", + "[ 0/1 → 1/1 | s:gm_flute stretch:3 ]", + "[ 1/1 → 2/1 | s:gm_flute stretch:2 ]", + "[ 2/1 → 3/1 | s:gm_flute stretch:1 ]", + "[ 3/1 → 4/1 | s:gm_flute stretch:-2 ]", ] `; From 54fd877be1272031972f9b0df183874c11f3e32f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Va=CC=81clav=20Volhejn?= Date: Sat, 18 Oct 2025 13:19:18 +0200 Subject: [PATCH 05/91] Update snapshot --- test/__snapshots__/examples.test.mjs.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index a23806c1c..9eebfed35 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -10439,9 +10439,9 @@ exports[`runs examples > example "stepcat" example index 1 1`] = ` exports[`runs examples > example "stretch" example index 0 1`] = ` [ - "[ 0/1 → 1/1 | s:gm_flute stretch:3 ]", - "[ 1/1 → 2/1 | s:gm_flute stretch:2 ]", - "[ 2/1 → 3/1 | s:gm_flute stretch:1 ]", + "[ 0/1 → 1/1 | s:gm_flute stretch:2 ]", + "[ 1/1 → 2/1 | s:gm_flute stretch:1 ]", + "[ 2/1 → 3/1 | s:gm_flute stretch:0 ]", "[ 3/1 → 4/1 | s:gm_flute stretch:-2 ]", ] `; From c77675c492c194bc2db8020c5c6ba5824969d0b6 Mon Sep 17 00:00:00 2001 From: "Lu[ke] Wilson" Date: Sat, 29 Nov 2025 18:01:27 +0000 Subject: [PATCH 06/91] add another synonym --- packages/core/pattern.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 8019a3eab..11304060b 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2383,7 +2383,7 @@ export const jux = register('jux', function (func, pat) { /** * Like jux, but flips the ears each cycle. * @name juxFlip - * @synonyms flux + * @synonyms juxflip, flux * @example * s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(rev) * @example @@ -2391,8 +2391,8 @@ export const jux = register('jux', function (func, pat) { * @example * s("bd lt [~ ht] mt cp ~ bd hh").juxFlip(iter(4)) */ -export const { juxFlip, flux } = register(['juxFlip', 'flux'], function (func, pat) { - return pat._jux(1, func, pat); +export const { juxFlip, flux } = register(['juxFlip', 'juxflip', 'flux'], function (func, pat) { + return pat._juxFlipBy(1, func, pat); }); /** From cf845ddbab3da24ea23bb83ec90af15d513bba95 Mon Sep 17 00:00:00 2001 From: Aria Date: Thu, 12 Feb 2026 18:30:50 -0600 Subject: [PATCH 07/91] Fix typo in phase computation --- packages/superdough/synth.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 38ad643de..224936914 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -485,8 +485,10 @@ export function waveformN(partials, phases, type) { if (phase !== 0) { const c = Math.cos(PI2 * phase); const s = Math.sin(PI2 * phase); - R = c * R - s * I; - I = s * R + c * I; + const R0 = R; + const I0 = I; + R = c * R0 - s * I0; + I = s * R0 + c * I0; } real[n + 1] = R; imag[n + 1] = I; From 8520044f3eff00a9cf6c3ac7eb55fda75a5764e0 Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 20 Feb 2026 13:10:16 -0500 Subject: [PATCH 08/91] fixed bug where withBase would silence a pattern if freq was unset --- packages/xen/xen.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index 828cab6ea..3f940c89a 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -149,6 +149,7 @@ export const withBase = register('withBase', (b, pat) => { let hVal = hap.value; const isObject = typeof hVal === 'object'; let freq = isObject ? hVal.freq : hVal; + if (!freq) return hap; freq = (freq * base) / originalBase; hap.value = isObject ? { ...hap.value, freq } : { freq }; return hap; From daf27ed971bd3fa54e15a231ec36529c1d6d6ca1 Mon Sep 17 00:00:00 2001 From: scrappy_fiddler Date: Mon, 23 Feb 2026 00:11:17 +0100 Subject: [PATCH 09/91] added lfo documentation --- website/src/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/config.ts b/website/src/config.ts index eb8bd1af8..eb098d009 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -98,6 +98,7 @@ export const SIDEBAR: Sidebar = { { text: 'Random Modifiers', link: 'learn/random-modifiers' }, { text: 'Conditional Modifiers', link: 'learn/conditional-modifiers' }, { text: 'Accumulation', link: 'learn/accumulation' }, + { text: 'LFOs', link: 'learn/lfo' }, { text: 'Tonal Functions', link: 'learn/tonal' }, { text: 'Stepwise Functions', link: 'learn/stepwise' }, ], From d930057000bff98866bdb4aa9b6b0f3f37b26241 Mon Sep 17 00:00:00 2001 From: scrappy_fiddler Date: Mon, 23 Feb 2026 00:18:42 +0100 Subject: [PATCH 10/91] lfo documentation --- website/src/pages/learn/lfo.mdx | 294 ++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 website/src/pages/learn/lfo.mdx diff --git a/website/src/pages/learn/lfo.mdx b/website/src/pages/learn/lfo.mdx new file mode 100644 index 000000000..d4dfd273f --- /dev/null +++ b/website/src/pages/learn/lfo.mdx @@ -0,0 +1,294 @@ +--- +title: Low-frequency oscillators (LFO) +layout: ../../layouts/MainLayout.astro +--- + +import { MiniRepl } from '../../docs/MiniRepl'; +import { JsDoc } from '../../docs/JsDoc'; + +# Low frequency oscillators (LFO) + +A low frequency oscillator (or short LFO) is a common way on synthesizers to continuously modulate various signals. + +This documentation is an interactive version of glossing's tutorial https://www.youtube.com/watch?v=11frBA9L638 + +## Applying an LFO + +Here, the LFO will change the frequency of the `saw`. Put a comment like this `//.lfo()` to see and hear how it changes, and remove the comment again. + + + +By default, the LFO will modulate the control parameter which is right before `.lfo()`: + + + +Here, the LFO will modulate the low pass filter `.lpf`. + +## Moving away from the default + +The following sections explain how pass parameters to `.lfo`. Similar to `._spectrum` above, almost all the configuration of `lfo` lives inside a json object, starting with `{` and ending with `}`. +All the parameters (except `id`) are written as `key: value` inside and separated by `,`. + +The reference refers to them as `config.key`, i.e. for the following one as `config.control` but you use them like below. + +## Control + +`control` determines which parameter will be modulated. This allows you to place your `lfo` at different places, +not necessarily immediately after the controlled parameter. + +Here, we place `lfo` after the `s`, but it modulates the low pass filter. + + +You can even influence parameters which are always present, even if you haven't explicitly written them, like `gain`. + + + +`control` has an alias `c`. + +## Rate + +`rate` determines how often the `lfo` oscillates per second: + +"}) + .lpf(800) + ._spectrum({height: 300, width: 800})`} +/> +The alias of `rate` is `r`.{' '} + +## Sync + +Instead of controlling the frequency with `rate` by setting a frequency in Hz, you can use `sync` +to snychronize your lfo with your other patterns. + +`sync` expresses this frequency as "times per cycle" + +Try removing the `sync: "<2 4 8 0.5 >"` from the pattern and notice that there is something not in sync. + +"}) + ._spectrum({height: 300, width: 800})`} +/> + +You can put patterns into the parameters of `lfo` if you want them to change over time, as seen above. + +## Relative Depth + +`.depth` is relative depth, a value of 1 (the default) means that +the value goes above and below by half of the value which is being modulated + +E.g. for depth `1`, it modulates the frequency to oscillate between 32 = (64/2) and 96 (= 64 + 64/2). + +"}) + .lpf(800) + ._spectrum({height: 300, width: 800})`} +/> +The `freq` is not needed here, as this is the default frequency, it's just for instructive purposes. + +The aliases of `depth` are `dr` and `dep`. + +## Absolute Depth + +`depthabs` controls the absolute modulation depth. For example you can modulate +the low pass filter by exactly 250 Hz up and below: + + + +The alias of `depthabs` is `da`. + +## DC offset + +If you don't want to go up or down by the same amount with your modulation, then you can shift the center of the modulation with `dcoffset`. +The default value is -0.5, which is the middle point between: +"dcoffset = 0: All modulations increase the control parameter (or keep it constant)" +"dcoffset = -1: All modulations decrease the control parameter (or keep it constant)" + +Other values will work as well. + +"})`} +/> +The marvellous alias of `dcoffset` is `dc`. + +## Shape + +You can change the shape of the modulation with `shape`. The default is `triangle`, but other shapes are available too: + +"}) `} +/> +You can add `._spectrum()` to see the shape of the modulations in the spectrum. + +You can also get these shapes by using numbers: + +| Shape | number | +| -------- | ------ | +| triangle | 0 | +| sine | 1 | +| ramp | 2 | +| saw | 3 | +| square | 4 | + +this way, you can use a function like `irand` to generate numbers: + + + +The alias of `shape` is `sh`. + +## Skewing some shapes + +You can influence some of the shapes (`triangle` and `square`) in more detail. + +The default skew is 0.5, it does different things for these two. + +For `triangle`, it skews the top of the triangle to the left or right, +where 0 makes it look like `saw` and 1 makes it look like `ramp`. + +For `square`, the skew influences the pulse width (see reference for `pulse` and `.pw`): + +/5", + skew: "<0 0.25 0.5 0.75 1>"}) `} +/> +The alias of `skew` is `sk`.{' '} + +## Curve + +You can change the curves of your lfo and can make it more intense. +The default value is 1. Writing numbers greater than one can make it more intense, +numbers between 0 and 1 will make it less intense. +This will raise the lfo to the power of curve, so larger numbers (such as 10) can have very unexpected results. + +", curve: "<1.3 1 0.75 1>"}) `} +/> +## Referencing your lfos with id + +All the lfos are numbered from the first starting with 0, the second having an `id` of 1 and so on. + +You can refer to this in a later call if you want to modify a specific `lfo`. + +The ´id´ is outside of the config json (which is different from the other parameters of `lfo()` + +Try out how the sound changes when you replace the 0 with a 1. + +"}) +.sometimes(x => x.lfo({dr: "4"},0))`} +/> + +You can also name your lfos and refer to them by name, using the `id` parameter + +"}, "lfo_lpf") +.sometimes(x => x.lfo({dr: "4"},""))`} +/> + +## FX index + +If you are using `FX()` to reorder your effects, you dont need to write your lfos inside the `FX`, +but instead can refer to them by their FX index (starting with 0) + + + +## Modulating other LFOs with Sub-control + +LFOs can modulate other lfos and will modulate their frequency (given by `r` or `s`): + + +To modulate other parameters of the first lfo (like `skew`, `depth` and so on), we can specify this with `subControl`or +its alias `sc` + + From 5cd45d5ea1002e33df8d7e844fa76def08a15bac Mon Sep 17 00:00:00 2001 From: scrappy_fiddler Date: Mon, 23 Feb 2026 00:22:30 +0100 Subject: [PATCH 11/91] typo in lfo docs --- website/src/pages/learn/lfo.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/learn/lfo.mdx b/website/src/pages/learn/lfo.mdx index d4dfd273f..a75584d9f 100644 --- a/website/src/pages/learn/lfo.mdx +++ b/website/src/pages/learn/lfo.mdx @@ -234,7 +234,7 @@ All the lfos are numbered from the first starting with 0, the second having an ` You can refer to this in a later call if you want to modify a specific `lfo`. -The ´id´ is outside of the config json (which is different from the other parameters of `lfo()` +The `id` is outside of the config json (which is different from the other parameters of `lfo()` Try out how the sound changes when you replace the 0 with a 1. From 82e88d4a28303d51565168f69e994a5b406f67b5 Mon Sep 17 00:00:00 2001 From: jagen31 Date: Mon, 23 Feb 2026 01:24:53 +0100 Subject: [PATCH 12/91] fix midi numbers as notes for osc backend note -> midinote --- packages/osc/osc.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/osc/osc.mjs b/packages/osc/osc.mjs index 0744cb8c9..9eda56149 100644 --- a/packages/osc/osc.mjs +++ b/packages/osc/osc.mjs @@ -42,7 +42,7 @@ export function parseControlsFromHap(hap, cps) { if (isNote(controls.note)) { controls.midinote = noteToMidi(controls.note, controls.octave || 3); } else { - controls.note = parseNumeral(controls.note); + controls.midinote = parseNumeral(controls.note); } } controls.bank && (controls.s = controls.bank + controls.s); From 3805ba8f76c5dee1c6e31f0fe0135265dc4ba16d Mon Sep 17 00:00:00 2001 From: KuboOrk Date: Mon, 9 Feb 2026 19:39:26 +0300 Subject: [PATCH 13/91] Added .dockerignore --- .dockerignore | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..ddaea8d69 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +**/node_modules +**/.angular +**/dist +.astro/ +.forgejo/ +.idea/ +.vscode/ +test/ +.gitignore +Dockerfile +.dockerignore From 63f42faca85bc87153eef9d77ada7523a08edce3 Mon Sep 17 00:00:00 2001 From: TodePond Date: Tue, 3 Mar 2026 11:27:05 +0100 Subject: [PATCH 14/91] Update packages/core/pattern.mjs --- packages/core/pattern.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 11304060b..491ca84d3 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2363,7 +2363,7 @@ export const { juxBy, juxby } = register(['juxBy', 'juxby'], function (by, func, export const { juxFlipBy, juxflipby, fluxBy, fluxby } = register( ['juxFlipBy', 'juxflipby', 'fluxBy', 'fluxby'], function (by, func, pat) { - return slowcat(pat._juxBy(by, func, pat), pat._juxBy(-by, func, pat)); + return pat.juxBy(slowcat(by, -by), func); }, ); From cdae661b24fd141e0b5d95c9e60979ddc6a4cbff Mon Sep 17 00:00:00 2001 From: Bernhard Wagner Date: Mon, 9 Mar 2026 11:15:14 +0100 Subject: [PATCH 15/91] docs: note that all/each require labeled patterns --- packages/core/repl.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index c1b539819..ff2734967 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -307,8 +307,12 @@ export function repl({ let allTransforms = []; /** - * Applies a function to all the running patterns. Note that the patterns are groups together into a single `stack` before the function is applied. This is probably what you want, but see `each` for + * Applies a function to all the running patterns. Note that the patterns are grouped together into a single `stack` before the function is applied. This is probably what you want, but see `each` for * a version that applies the function to each pattern separately. + * + * **Note:** Patterns must be labeled (e.g. with `$:`) to be picked up by `all`. An unlabeled + * pattern such as `note("c4")` is not registered and will produce no audio when `all` is present. + * Use `$: note("c4")` instead. * ``` * $: sound("bd - cp sd") * $: sound("hh*8") @@ -328,6 +332,9 @@ export function repl({ }; /** Applies a function to each of the running patterns separately. This is intended for future use with upcoming 'stepwise' features. See `all` for a version that applies the function to all the patterns stacked together into a single pattern. * + * **Note:** Patterns must be labeled (e.g. with `$:`) to be picked up by `each`. An unlabeled + * pattern such as `note("c4")` is not registered and will produce no audio when `each` is present. + * Use `$: note("c4")` instead. * ``` * $: sound("bd - cp sd") * $: sound("hh*8") From 7294cd54430fa9455c86d3dc5aec67a7f64932ac Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 13 Mar 2026 14:11:19 -0400 Subject: [PATCH 16/91] Add error for when name arg in register is a pattern --- packages/core/pattern.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index c5b7953c9..0612c10f1 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1741,6 +1741,10 @@ export const func = curry((a, b) => reify(b).func(a)); * */ export function register(name, func, patternify = true, preserveSteps = false, join = (x) => x.innerJoin()) { + if (isPattern(name)) { + throw new Error("Name argument for register is a pattern, try using single quotes ('name') instead of double quotes (\"name\")"); + } + if (Array.isArray(name)) { const result = {}; for (const name_item of name) { From ae32743f12904c4a54c4492049860cfe56164e83 Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 13 Mar 2026 14:11:38 -0400 Subject: [PATCH 17/91] format --- packages/core/pattern.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 0612c10f1..305dd5bb2 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1742,9 +1742,11 @@ export const func = curry((a, b) => reify(b).func(a)); */ export function register(name, func, patternify = true, preserveSteps = false, join = (x) => x.innerJoin()) { if (isPattern(name)) { - throw new Error("Name argument for register is a pattern, try using single quotes ('name') instead of double quotes (\"name\")"); + throw new Error( + 'Name argument for register is a pattern, try using single quotes (\'name\') instead of double quotes ("name")', + ); } - + if (Array.isArray(name)) { const result = {}; for (const name_item of name) { From e15ce7d402af1e856088b3e37b813c0d0f6bfc15 Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 14 Mar 2026 14:48:57 -0500 Subject: [PATCH 18/91] Add replace and insert code helpers to codemirror --- packages/codemirror/codemirror.mjs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 6aac0c8a4..71e5c49ab 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -444,14 +444,16 @@ export class StrudelMirror { this.setFontSize(value); } } - setCode(code) { - const changes = { - from: 0, - to: this.editor.state.doc.length, - insert: code, - }; + replaceCode(code, from, to) { + const changes = { from, to, insert: code }; this.editor.dispatch({ changes }); } + insertCode(code, position) { + this.replaceCode(code, position, position); + } + setCode(code) { + this.replaceCode(code, 0, this.editor.state.doc.length); + } // used for debugging but could serve other purposes getActiveWidgets() { return getActiveWidgets(this.editor); From 86d65c72f056b5f31f7a3558e8dcb64bd41b9c00 Mon Sep 17 00:00:00 2001 From: tyow Date: Mon, 30 Mar 2026 14:37:26 -0400 Subject: [PATCH 19/91] colorless version display implementation --- website/src/repl/components/panel/Panel.jsx | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/website/src/repl/components/panel/Panel.jsx b/website/src/repl/components/panel/Panel.jsx index 3b4dd5482..76987d1a4 100644 --- a/website/src/repl/components/panel/Panel.jsx +++ b/website/src/repl/components/panel/Panel.jsx @@ -44,6 +44,25 @@ export function LogoButton({ context, isEmbedded }) { export function MainPanel({ context, isEmbedded = false, className }) { const { isZen, isButtonRowHidden, fontFamily } = useSettings(); + let loc = window.location; + let ver = 'unofficial'; + let hot = false; + let b = loc.hostname.match(/^(.+)\.(strudel)/); + if (/(strudel)/.test(loc.hostname)) { + // if there's no text before 'strudel', it's warm, otherwise use the text before strudel + ver = b ? b[1] : 'warm'; + } else { + // match both versions of localhost + if (/(localhost)|(127.0.0.1)/.test(loc.hostname)) ver = 'dev'; + } + let pr = ver.match(/pr-([0-9]+)/); + if (pr) { + pr = pr[1]; + ver = `hot: ${pr}`; + hot = true; + pr = `https://codeberg.org/uzu/strudel/pulls/${pr}`; + } + return (