From 12c6f4e95fe8cee0753afeda8b28fca73ad0c6d1 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 18 Feb 2024 15:30:24 +0100 Subject: [PATCH 01/26] Update signal.mjs added pickr() and pickrmod() --- packages/core/signal.mjs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index c70805d8..270f81be 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -244,6 +244,30 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { return pat.apply(pickmod(lookup, funcs)); }); +/** +/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). + * Similar to `pick`, but restart() is invoked everytime a new index is triggered. + * In case of stacked indexes, collect() function is used to avoid multiple restarting triggers + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickr = register('pickr', function (lookup, pat) { + return _pick(lookup.map((x)=>x.restart(pat.collect().fmap(v=>v+1))), pat, false).innerJoin(); +}); + +/** * The same as `pickr`, but if you pick a number greater than the size of the list, + * it wraps around, rather than sticking at the maximum value. + * For example, if you pick the fifth pattern of a list of three, you'll get the + * second one. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickrmod = register('pickrmod', function (lookup, pat) { + return _pick(lookup.map((x)=>x.restart(pat.collect().fmap(v=>v+1))), pat, true).innerJoin(); +}); + /** /** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). * Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern. @@ -258,7 +282,7 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { * s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4) */ export const inhabit = register('inhabit', function (lookup, pat) { - return _pick(lookup, pat, true).squeezeJoin(); + return _pick(lookup, pat, false).squeezeJoin(); }); /** * The same as `inhabit`, but if you pick a number greater than the size of the list, @@ -270,8 +294,8 @@ export const inhabit = register('inhabit', function (lookup, pat) { * @returns {Pattern} */ -export const inhabitmod = register('inhabit', function (lookup, pat) { - return _pick(lookup, pat, false).squeezeJoin(); +export const inhabitmod = register('inhabitmod', function (lookup, pat) { + return _pick(lookup, pat, true).squeezeJoin(); }); /** From 23c55c963662b3892a7ebf17d2706cfe06d4f184 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 18 Feb 2024 16:03:01 +0100 Subject: [PATCH 02/26] Update signal.mjs prettier fix --- packages/core/signal.mjs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 270f81be..12e0043d 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -253,7 +253,11 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { * @returns {Pattern} */ export const pickr = register('pickr', function (lookup, pat) { - return _pick(lookup.map((x)=>x.restart(pat.collect().fmap(v=>v+1))), pat, false).innerJoin(); + return _pick( + lookup.map((x) => x.restart(pat.collect().fmap((v) => v + 1))), + pat, + false, + ).innerJoin(); }); /** * The same as `pickr`, but if you pick a number greater than the size of the list, @@ -265,7 +269,11 @@ export const pickr = register('pickr', function (lookup, pat) { * @returns {Pattern} */ export const pickrmod = register('pickrmod', function (lookup, pat) { - return _pick(lookup.map((x)=>x.restart(pat.collect().fmap(v=>v+1))), pat, true).innerJoin(); + return _pick( + lookup.map((x) => x.restart(pat.collect().fmap((v) => v + 1))), + pat, + true, + ).innerJoin(); }); /** From 03a73fb95e5246f8108095c8f026b366220fc032 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 18 Feb 2024 16:21:50 +0100 Subject: [PATCH 03/26] Update signal.mjs Fixed pickr() description --- packages/core/signal.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 12e0043d..f8a8426d 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -244,9 +244,7 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { return pat.apply(pickmod(lookup, funcs)); }); -/** -/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). - * Similar to `pick`, but restart() is invoked everytime a new index is triggered. +/** * Similar to `pick`, but restart() is invoked everytime a new index is triggered. * In case of stacked indexes, collect() function is used to avoid multiple restarting triggers * @param {Pattern} pat * @param {*} xs From 88ece92a78a63f53155b655018a9b12708f9a168 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 18 Feb 2024 16:40:27 +0100 Subject: [PATCH 04/26] Update signal.mjs Make pickr() handle dictionaries also --- packages/core/signal.mjs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index f8a8426d..a2bc42d2 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -160,7 +160,7 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); */ export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); -const _pick = function (lookup, pat, modulo = true) { +const _pick = function (lookup, pat, modulo = true, restart = false) { const array = Array.isArray(lookup); const len = Object.keys(lookup).length; @@ -174,7 +174,7 @@ const _pick = function (lookup, pat, modulo = true) { if (array) { key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1); } - return lookup[key]; + return restart ? lookup[key].restart(pat.collect().fmap((v) => v + 1)) : lookup[key]; }); }; @@ -251,11 +251,7 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { * @returns {Pattern} */ export const pickr = register('pickr', function (lookup, pat) { - return _pick( - lookup.map((x) => x.restart(pat.collect().fmap((v) => v + 1))), - pat, - false, - ).innerJoin(); + return _pick(lookup, pat, false, true).innerJoin(); }); /** * The same as `pickr`, but if you pick a number greater than the size of the list, @@ -267,11 +263,7 @@ export const pickr = register('pickr', function (lookup, pat) { * @returns {Pattern} */ export const pickrmod = register('pickrmod', function (lookup, pat) { - return _pick( - lookup.map((x) => x.restart(pat.collect().fmap((v) => v + 1))), - pat, - true, - ).innerJoin(); + return _pick(lookup, pat, true, true).innerJoin(); }); /** From 3347817350d3b24511ca058efefe50285a667ef5 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:41:06 +0100 Subject: [PATCH 05/26] Update signal.mjs using trigZeroJoin in pickr() instead --- packages/core/signal.mjs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index a2bc42d2..7bc97c7b 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -160,7 +160,7 @@ export const _irand = (i) => rand.fmap((x) => Math.trunc(x * i)); */ export const irand = (ipat) => reify(ipat).fmap(_irand).innerJoin(); -const _pick = function (lookup, pat, modulo = true, restart = false) { +const _pick = function (lookup, pat, modulo = true) { const array = Array.isArray(lookup); const len = Object.keys(lookup).length; @@ -174,7 +174,7 @@ const _pick = function (lookup, pat, modulo = true, restart = false) { if (array) { key = modulo ? Math.round(key) % len : clamp(Math.round(key), 0, lookup.length - 1); } - return restart ? lookup[key].restart(pat.collect().fmap((v) => v + 1)) : lookup[key]; + return lookup[key]; }); }; @@ -244,14 +244,13 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { return pat.apply(pickmod(lookup, funcs)); }); -/** * Similar to `pick`, but restart() is invoked everytime a new index is triggered. - * In case of stacked indexes, collect() function is used to avoid multiple restarting triggers +/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered. * @param {Pattern} pat * @param {*} xs * @returns {Pattern} */ export const pickr = register('pickr', function (lookup, pat) { - return _pick(lookup, pat, false, true).innerJoin(); + return _pick(lookup, pat, false).trigzeroJoin(); }); /** * The same as `pickr`, but if you pick a number greater than the size of the list, @@ -263,7 +262,7 @@ export const pickr = register('pickr', function (lookup, pat) { * @returns {Pattern} */ export const pickrmod = register('pickrmod', function (lookup, pat) { - return _pick(lookup, pat, true, true).innerJoin(); + return _pick(lookup, pat, true).trigzeroJoin(); }); /** From 00c7da45c0a43fb285e217b1375482667bffadc5 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:55:05 +0100 Subject: [PATCH 06/26] Update signal.mjs now pickRestart() and pickReset() --- packages/core/signal.mjs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 7bc97c7b..e468b0d1 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -249,22 +249,39 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { * @param {*} xs * @returns {Pattern} */ -export const pickr = register('pickr', function (lookup, pat) { +export const pickRestart = register('pickRestart', function (lookup, pat) { return _pick(lookup, pat, false).trigzeroJoin(); }); /** * The same as `pickr`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. - * For example, if you pick the fifth pattern of a list of three, you'll get the - * second one. * @param {Pattern} pat * @param {*} xs * @returns {Pattern} */ -export const pickrmod = register('pickrmod', function (lookup, pat) { +export const pickmodRestart = register('pickmodRestart', function (lookup, pat) { return _pick(lookup, pat, true).trigzeroJoin(); }); +/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickReset = register('pickReset', function (lookup, pat) { + return _pick(lookup, pat, false).trigJoin(); +}); + +/** * The same as `pickr`, but if you pick a number greater than the size of the list, + * it wraps around, rather than sticking at the maximum value. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickmodReset = register('pickmodReset', function (lookup, pat) { + return _pick(lookup, pat, true).trigJoin(); +}); + /** /** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). * Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern. From d5e67fe13fad29f4a87e5343dcc6bb15032b4fb2 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 18 Feb 2024 19:01:05 +0100 Subject: [PATCH 07/26] Update signal.mjs typo --- packages/core/signal.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index e468b0d1..c645133d 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -253,7 +253,7 @@ export const pickRestart = register('pickRestart', function (lookup, pat) { return _pick(lookup, pat, false).trigzeroJoin(); }); -/** * The same as `pickr`, but if you pick a number greater than the size of the list, +/** * The same as `pickRestart`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. * @param {Pattern} pat * @param {*} xs @@ -272,7 +272,7 @@ export const pickReset = register('pickReset', function (lookup, pat) { return _pick(lookup, pat, false).trigJoin(); }); -/** * The same as `pickr`, but if you pick a number greater than the size of the list, +/** * The same as `pickReset`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. * @param {Pattern} pat * @param {*} xs From 0986b14508345238210160fbcdda72b80dca0503 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 25 Feb 2024 14:31:07 +0100 Subject: [PATCH 08/26] added pickOuter, pickmodOuter --- packages/core/signal.mjs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index c645133d..cceb0a13 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -244,6 +244,25 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { return pat.apply(pickmod(lookup, funcs)); }); +/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickOuter = register('pickOuter', function (lookup, pat) { + return _pick(lookup, pat, false).outerJoin(); +}); + +/** * The same as `pickRestart`, but if you pick a number greater than the size of the list, + * it wraps around, rather than sticking at the maximum value. + * @param {Pattern} pat + * @param {*} xs + * @returns {Pattern} + */ +export const pickmodOuter = register('pickmodOuter', function (lookup, pat) { + return _pick(lookup, pat, true).outerJoin(); +}); + /** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered. * @param {Pattern} pat * @param {*} xs From dbfdbcf2b7bf057efdfa61be561e134b9e2763fc Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Sun, 25 Feb 2024 14:39:38 +0100 Subject: [PATCH 09/26] typo --- 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 cceb0a13..998dcb26 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -253,7 +253,7 @@ export const pickOuter = register('pickOuter', function (lookup, pat) { return _pick(lookup, pat, false).outerJoin(); }); -/** * The same as `pickRestart`, but if you pick a number greater than the size of the list, +/** * The same as `pickOuter`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. * @param {Pattern} pat * @param {*} xs From d4724b0e12278dfb2fc2e222b7ef537278a37687 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 25 Feb 2024 14:43:02 +0100 Subject: [PATCH 10/26] feat: can now invert euclid pulses with negative numbers --- packages/core/euclid.mjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index 7a952cf8..ad0b0148 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -41,11 +41,17 @@ const _bjork = function (n, x) { }; export const bjork = function (ons, steps) { + const inverted = ons < 0; + ons = Math.abs(ons); const offs = steps - ons; const x = Array(ons).fill([1]); const y = Array(offs).fill([0]); const result = _bjork([ons, offs], [x, y]); - return flatten(result[1][0]).concat(flatten(result[1][1])); + const p = flatten(result[1][0]).concat(flatten(result[1][1])); + if (inverted) { + return p.map((x) => (x === 0 ? 1 : 0)); + } + return p; }; /** From 4aaf9eaed4100b17f72d5c8f87cdd86aea38c977 Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Mon, 26 Feb 2024 00:35:00 +0100 Subject: [PATCH 11/26] mux operator, muxers.mjs with pick variants prettier prettier2 clamp? Revert "clamp?" This reverts commit 83a3b9123aece9f0e366c83d97f99e8b8d20db54. Revert "prettier2" This reverts commit de13871a8be4a450b79a4d9e65b9d1982485577e. Revert "prettier" This reverts commit 1550bf49527f591d05fa2766ca66ccb16e142011. Revert "mux operator, muxers.mjs with pick variants" This reverts commit af062f93c23ded4440f968186bfc69851d8aec67. From 5c71bb95a0ec1384b45a0716d9fb2963036dea3d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 28 Feb 2024 18:51:07 +0100 Subject: [PATCH 12/26] remove legacy legato + make legato a synonym of clip --- packages/core/controls.mjs | 18 ++---------------- packages/core/pattern.mjs | 15 --------------- website/src/pages/learn/time-modifiers.mdx | 6 +----- 3 files changed, 3 insertions(+), 36 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 42fa8cba..b6964564 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -400,19 +400,6 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); * s(",hh*3").fast(2).crush("<16 8 7 6 5 4 3 2>") * */ -// TODO: currently duplicated with "native" legato -// TODO: superdirt legato will do more: https://youtu.be/dQPmE1WaD1k?t=419 -/** - * 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. - * - * @name legato - * @param {number | Pattern} duration between 0 and 1, where 1 is the length of the whole hap time - * @noAutocomplete - * @example - * "c4 eb4 g4 bb4".legato("<0.125 .25 .5 .75 1 2 4>") - * - */ -// ['legato'], // ['clhatdecay'], export const { crush } = registerControl('crush'); /** @@ -1455,16 +1442,15 @@ export const { val } = registerControl('val'); export const { cps } = registerControl('cps'); /** * Multiplies the duration with the given number. Also cuts samples off at the end if they exceed the duration. - * In tidal, this would be done with legato, [which has a complicated history in strudel](https://github.com/tidalcycles/strudel/issues/111). - * For now, if you're coming from tidal, just think clip = legato. * * @name clip + * @synonyms legato * @param {number | Pattern} factor >= 0 * @example * note("c a f e").s("piano").clip("<.5 1 2>") * */ -export const { clip } = registerControl('clip'); +export const { clip, legato } = registerControl('clip', 'legato'); // ZZFX export const { zrand } = registerControl('zrand'); diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 3d859728..6efcc758 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2241,21 +2241,6 @@ export const velocity = register('velocity', function (velocity, pat) { return pat.withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity })); }); -/** - * - * Multiplies the hap duration with the given factor. - * With samples, `clip` might be a better function to use ([more info](https://github.com/tidalcycles/strudel/pull/598)) - * @name legato - * @memberof Pattern - * @example - * note("c3 eb3 g3 c4").legato("<.25 .5 1 2>") - */ -// TODO - fix -export const legato = register('legato', function (value, pat) { - value = Fraction(value); - return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value)))); -}); - ////////////////////////////////////////////////////////////////////// // Control-related functions, i.e. ones that manipulate patterns of // objects diff --git a/website/src/pages/learn/time-modifiers.mdx b/website/src/pages/learn/time-modifiers.mdx index e1226512..1baef9ea 100644 --- a/website/src/pages/learn/time-modifiers.mdx +++ b/website/src/pages/learn/time-modifiers.mdx @@ -34,11 +34,7 @@ Some of these have equivalent operators in the Mini Notation: -## legato - - - -## clip +## clip / legato From f779e81993668b56308b4e125a9c82f436ef4c3b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 28 Feb 2024 18:52:08 +0100 Subject: [PATCH 13/26] remove old snapshots --- test/__snapshots__/examples.test.mjs.snap | 42 ----------------------- 1 file changed, 42 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 0c28f04b..09aec7b2 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3521,48 +3521,6 @@ exports[`runs examples > example "layer" example index 0 1`] = ` ] `; -exports[`runs examples > example "legato" example index 0 1`] = ` -[ - "[ 0/1 → 1/32 | c4 ]", - "[ 1/4 → 9/32 | eb4 ]", - "[ 1/2 → 17/32 | g4 ]", - "[ 3/4 → 25/32 | bb4 ]", - "[ 1/1 → 17/16 | c4 ]", - "[ 5/4 → 21/16 | eb4 ]", - "[ 3/2 → 25/16 | g4 ]", - "[ 7/4 → 29/16 | bb4 ]", - "[ 2/1 → 17/8 | c4 ]", - "[ 9/4 → 19/8 | eb4 ]", - "[ 5/2 → 21/8 | g4 ]", - "[ 11/4 → 23/8 | bb4 ]", - "[ 3/1 → 51/16 | c4 ]", - "[ 13/4 → 55/16 | eb4 ]", - "[ 7/2 → 59/16 | g4 ]", - "[ 15/4 → 63/16 | bb4 ]", -] -`; - -exports[`runs examples > example "legato" example index 0 2`] = ` -[ - "[ 0/1 → 1/16 | note:c3 ]", - "[ 1/4 → 5/16 | note:eb3 ]", - "[ 1/2 → 9/16 | note:g3 ]", - "[ 3/4 → 13/16 | note:c4 ]", - "[ 1/1 → 9/8 | note:c3 ]", - "[ 5/4 → 11/8 | note:eb3 ]", - "[ 3/2 → 13/8 | note:g3 ]", - "[ 7/4 → 15/8 | note:c4 ]", - "[ 2/1 → 9/4 | note:c3 ]", - "[ 9/4 → 5/2 | note:eb3 ]", - "[ 5/2 → 11/4 | note:g3 ]", - "[ 11/4 → 3/1 | note:c4 ]", - "[ 3/1 → 7/2 | note:c3 ]", - "[ 13/4 → 15/4 | note:eb3 ]", - "[ 7/2 → 4/1 | note:g3 ]", - "[ 15/4 → 17/4 | note:c4 ]", -] -`; - exports[`runs examples > example "leslie" example index 0 1`] = ` [ "[ 0/1 → 1/1 | n:0 s:supersquare leslie:0 ]", From b870ab204a7a45a25e6abcb7b24c5f94ba0684cb Mon Sep 17 00:00:00 2001 From: eefano <77832+eefano@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:07:02 +0100 Subject: [PATCH 14/26] fix for transpose(): preserve hap value object structure --- packages/tonal/tonal.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/tonal/tonal.mjs b/packages/tonal/tonal.mjs index 48cfdb9f..78ec1101 100644 --- a/packages/tonal/tonal.mjs +++ b/packages/tonal/tonal.mjs @@ -103,6 +103,8 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval; return hap.withValue(() => hap.value + semitones); } + if (typeof hap.value === 'object') + return hap.withValue(() => ({ ...hap.value, note: Note.simplify(Note.transpose(hap.value.note, interval)) })); // TODO: move simplify to player to preserve enharmonics // tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3 return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval))); @@ -133,6 +135,11 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n if (!hap.context.scale) { throw new Error('can only use scaleTranspose after .scale'); } + if (typeof hap.value === 'object') + return hap.withValue(() => ({ + ...hap.value, + note: scaleOffset(hap.context.scale, Number(offset), hap.value.note), + })); if (typeof hap.value !== 'string') { throw new Error('can only use scaleTranspose with notes'); } From eb93a6c1495df43a01ad9c943dbf58a64a71b1a8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 29 Feb 2024 04:05:05 +0100 Subject: [PATCH 15/26] refactor: duration is now a regular control --- packages/core/controls.mjs | 14 ++++++++++++-- packages/core/draw.mjs | 2 +- packages/core/hap.mjs | 12 +++++++++++- packages/core/pattern.mjs | 6 ------ packages/superdough/sampler.mjs | 4 ++-- packages/webaudio/webaudio.mjs | 1 - 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index b6964564..671566be 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1376,8 +1376,6 @@ export const { waveloss } = registerControl('waveloss'); * */ export const { density } = registerControl('density'); -// TODO: midi effects? -export const { dur } = registerControl('dur'); // ['modwheel'], export const { expression } = registerControl('expression'); export const { sustainpedal } = registerControl('sustainpedal'); @@ -1452,6 +1450,18 @@ export const { cps } = registerControl('cps'); */ export const { clip, legato } = registerControl('clip', 'legato'); +/** + * Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration. + * + * @name duration + * @synonyms dur + * @param {number | Pattern} seconds >= 0 + * @example + * note("c a f e").s("piano").dur("<.5 1 2>") + * + */ +export const { duration, dur } = registerControl('duration', 'dur'); + // ZZFX export const { zrand } = registerControl('zrand'); export const { curve } = registerControl('curve'); diff --git a/packages/core/draw.mjs b/packages/core/draw.mjs index 7a9454f2..30de7ba8 100644 --- a/packages/core/draw.mjs +++ b/packages/core/draw.mjs @@ -134,7 +134,7 @@ export class Drawer { this.lastFrame = phase; this.visibleHaps = (this.visibleHaps || []) // filter out haps that are too far in the past (think left edge of screen for pianoroll) - .filter((h) => h.whole?.end >= phase - lookbehind - lookahead) + .filter((h) => h.endClipped >= phase - lookbehind - lookahead) // add new haps with onset (think right edge bars scrolling in) .concat(haps.filter((h) => h.hasOnset())); const time = phase - lookahead; diff --git a/packages/core/hap.mjs b/packages/core/hap.mjs index 6a1eb987..ab5f0c97 100644 --- a/packages/core/hap.mjs +++ b/packages/core/hap.mjs @@ -3,6 +3,7 @@ hap.mjs - Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +import Fraction from './fraction.mjs'; export class Hap { /* @@ -32,7 +33,16 @@ export class Hap { } get duration() { - return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1); + let duration; + if (typeof this.value?.duration === 'number') { + duration = Fraction(this.value.duration); + } else { + duration = this.whole.end.sub(this.whole.begin); + } + if (typeof this.value?.clip === 'number') { + return duration.mul(this.value.clip); + } + return duration; } get endClipped() { diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 6efcc758..13ac8cfd 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2203,12 +2203,6 @@ export const bypass = register('bypass', function (on, pat) { */ export const ribbon = register('ribbon', (offset, cycles, pat) => pat.early(offset).restart(pure(1).slow(cycles))); -// sets absolute duration of haps -// TODO - fix -export const duration = register('duration', function (value, pat) { - return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value))); -}); - export const hsla = register('hsla', (h, s, l, a, pat) => { return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`); }); diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index e7d1dae3..437347ca 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -251,7 +251,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { nudge = 0, // TODO: is this in seconds? cut, loop, - clip = undefined, // if 1, samples will be cut off when the hap ends + clip = undefined, // if set, samples will be cut off when the hap ends n = 0, note, speed = 1, // sample playback speed @@ -306,7 +306,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { bufferSource.start(time, offset); const envGain = ac.createGain(); const node = bufferSource.connect(envGain); - if (clip == null && loop == null && value.release == null) { + if (duration == null && clip == null && loop == null && value.release == null) { const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value; duration = (end - begin) * bufferDuration; } diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index 19dbb804..a176d2cc 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -15,7 +15,6 @@ const hap2value = (hap) => { return { ...hap.value, velocity: hap.context.velocity }; }; -// TODO: bind logger export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps); export const webaudioOutput = (hap, deadline, hapDuration) => superdough(hap2value(hap), deadline, hapDuration); From 7556da783989d94f413e833c29377651b2f04b6a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 29 Feb 2024 04:06:15 +0100 Subject: [PATCH 16/26] snapshot --- test/__snapshots__/examples.test.mjs.snap | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 09aec7b2..dc6da174 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -2001,6 +2001,27 @@ exports[`runs examples > example "dry" example index 0 1`] = ` ] `; +exports[`runs examples > example "duration" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:c s:piano duration:0.5 ]", + "[ 1/4 → 1/2 | note:a s:piano duration:0.5 ]", + "[ 1/2 → 3/4 | note:f s:piano duration:0.5 ]", + "[ 3/4 → 1/1 | note:e s:piano duration:0.5 ]", + "[ 1/1 → 5/4 | note:c s:piano duration:1 ]", + "[ 5/4 → 3/2 | note:a s:piano duration:1 ]", + "[ 3/2 → 7/4 | note:f s:piano duration:1 ]", + "[ 7/4 → 2/1 | note:e s:piano duration:1 ]", + "[ 2/1 → 9/4 | note:c s:piano duration:2 ]", + "[ 9/4 → 5/2 | note:a s:piano duration:2 ]", + "[ 5/2 → 11/4 | note:f s:piano duration:2 ]", + "[ 11/4 → 3/1 | note:e s:piano duration:2 ]", + "[ 3/1 → 13/4 | note:c s:piano duration:0.5 ]", + "[ 13/4 → 7/2 | note:a s:piano duration:0.5 ]", + "[ 7/2 → 15/4 | note:f s:piano duration:0.5 ]", + "[ 15/4 → 4/1 | note:e s:piano duration:0.5 ]", +] +`; + exports[`runs examples > example "early" example index 0 1`] = ` [ "[ -1/10 ⇜ (0/1 → 2/5) | s:hh ]", From fcb247b62a563924464ac7f5034a136612798225 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 29 Feb 2024 04:25:00 +0100 Subject: [PATCH 17/26] add debounce to logger --- packages/core/logger.mjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/core/logger.mjs b/packages/core/logger.mjs index 16d38f91..e13bf86c 100644 --- a/packages/core/logger.mjs +++ b/packages/core/logger.mjs @@ -1,6 +1,16 @@ export const logKey = 'strudel.log'; +let debounce = 1000, + lastMessage, + lastTime; + export function logger(message, type, data = {}) { + let t = performance.now(); + if (lastMessage === message && t - lastTime < debounce) { + return; + } + lastMessage = message; + lastTime = t; console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px'); if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') { document.dispatchEvent( From 96eef75f74c355a95d8cba2b420271ddf3794a85 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 29 Feb 2024 10:33:27 +0100 Subject: [PATCH 18/26] fix: end / begin. sampler now needs clip to choose duration... --- packages/superdough/sampler.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 437347ca..85fccc97 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -306,7 +306,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { bufferSource.start(time, offset); const envGain = ac.createGain(); const node = bufferSource.connect(envGain); - if (duration == null && clip == null && loop == null && value.release == null) { + if (clip == null && loop == null && value.release == null) { const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value; duration = (end - begin) * bufferDuration; } From 0deb905cdf4702b3794aa9287d1db0ea4220bcab Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 29 Feb 2024 15:21:44 +0100 Subject: [PATCH 19/26] rename pick*Outer > pickOut + add pick*Squeeze alias to inhabit* --- packages/core/signal.mjs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 998dcb26..1d453646 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -249,17 +249,17 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) { * @param {*} xs * @returns {Pattern} */ -export const pickOuter = register('pickOuter', function (lookup, pat) { +export const pickOut = register('pickOut', function (lookup, pat) { return _pick(lookup, pat, false).outerJoin(); }); -/** * The same as `pickOuter`, but if you pick a number greater than the size of the list, +/** * The same as `pickOut`, but if you pick a number greater than the size of the list, * it wraps around, rather than sticking at the maximum value. * @param {Pattern} pat * @param {*} xs * @returns {Pattern} */ -export const pickmodOuter = register('pickmodOuter', function (lookup, pat) { +export const pickmodOut = register('pickmodOut', function (lookup, pat) { return _pick(lookup, pat, true).outerJoin(); }); @@ -314,7 +314,7 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) { * @example * s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4) */ -export const inhabit = register('inhabit', function (lookup, pat) { +export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) { return _pick(lookup, pat, false).squeezeJoin(); }); @@ -327,7 +327,7 @@ export const inhabit = register('inhabit', function (lookup, pat) { * @returns {Pattern} */ -export const inhabitmod = register('inhabitmod', function (lookup, pat) { +export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) { return _pick(lookup, pat, true).squeezeJoin(); }); From c524ebc7f3caa4b95a5de0a4eb5af583a9c35105 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 29 Feb 2024 15:25:31 +0100 Subject: [PATCH 20/26] fix: add name tags --- packages/core/signal.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 1d453646..7e9a9099 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -304,6 +304,8 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) { /** /** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name). * Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern. + * @name inhabit + * @synonyms pickSqueeze * @param {Pattern} pat * @param {*} xs * @returns {Pattern} @@ -322,6 +324,8 @@ export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], fun * it wraps around, rather than sticking at the maximum value. * For example, if you pick the fifth pattern of a list of three, you'll get the * second one. + * @name inhabitmod + * @synonyms pickmodSqueeze * @param {Pattern} pat * @param {*} xs * @returns {Pattern} From 416a03aea1e4f398749c9515cd29b62bc320cf63 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 29 Feb 2024 18:09:26 -0500 Subject: [PATCH 21/26] updated params --- packages/core/controls.mjs | 4 ++-- packages/superdough/superdough.mjs | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 1f49cf06..bbc9a5bd 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -1287,7 +1287,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size', * s("bd sd [~ bd] sd,hh*8").shape("<0 .2 .4 .6 .8>") * */ -export const { shape } = registerControl('shape'); +export const { shape } = registerControl(['shape', 'shapevol']); /** * Wave shaping distortion. CAUTION: it can get loud. * Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output. @@ -1302,7 +1302,7 @@ export const { shape } = registerControl('shape'); * note("d1!8").s("sine").penv(36).pdecay(.12).decay(.23).distort("8:.4") * */ -export const { distort, dist } = registerControl('distort', 'dist'); +export const { distort, dist } = registerControl(['distort', 'distortvol'], 'dist'); /** * Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")` * More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 83499f01..878e6c1e 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -316,7 +316,9 @@ export const superdough = async (value, deadline, hapDuration) => { coarse, crush, shape, + shapevol, distort, + distortvol, pan, vowel, delay = 0, @@ -458,14 +460,8 @@ export const superdough = async (value, deadline, hapDuration) => { // effects coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })); crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); - if (shape !== undefined) { - const input = Array.isArray(shape) ? { shape: shape[0], postgain: shape[1] } : { shape }; - chain.push(getWorklet(ac, 'shape-processor', input)); - } - if (distort !== undefined) { - const input = Array.isArray(distort) ? { distort: distort[0], postgain: distort[1] } : { distort }; - chain.push(getWorklet(ac, 'distort-processor', input)); - } + shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol })); + distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol })); compressorThreshold !== undefined && chain.push( From c9b18f6fa44fc7ca03d8a0be1d45cb2e10c55412 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 29 Feb 2024 18:15:30 -0500 Subject: [PATCH 22/26] fix test --- test/__snapshots__/examples.test.mjs.snap | 88 +++++++++++------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index d6c0fe70..ac0f47e9 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1971,55 +1971,55 @@ exports[`runs examples > example "distort" example index 0 1`] = ` "[ 11/4 → 23/8 | s:hh distort:3 ]", "[ 11/4 → 3/1 | s:sd distort:3 ]", "[ 23/8 → 3/1 | s:hh distort:3 ]", - "[ 3/1 → 25/8 | s:hh distort:[10 0.5] ]", - "[ 3/1 → 13/4 | s:bd distort:[10 0.5] ]", - "[ 25/8 → 13/4 | s:hh distort:[10 0.5] ]", - "[ 13/4 → 27/8 | s:hh distort:[10 0.5] ]", - "[ 13/4 → 7/2 | s:sd distort:[10 0.5] ]", - "[ 27/8 → 7/2 | s:hh distort:[10 0.5] ]", - "[ 7/2 → 29/8 | s:hh distort:[10 0.5] ]", - "[ 29/8 → 15/4 | s:bd distort:[10 0.5] ]", - "[ 29/8 → 15/4 | s:hh distort:[10 0.5] ]", - "[ 15/4 → 31/8 | s:hh distort:[10 0.5] ]", - "[ 15/4 → 4/1 | s:sd distort:[10 0.5] ]", - "[ 31/8 → 4/1 | s:hh distort:[10 0.5] ]", + "[ 3/1 → 25/8 | s:hh distort:10 distortvol:0.5 ]", + "[ 3/1 → 13/4 | s:bd distort:10 distortvol:0.5 ]", + "[ 25/8 → 13/4 | s:hh distort:10 distortvol:0.5 ]", + "[ 13/4 → 27/8 | s:hh distort:10 distortvol:0.5 ]", + "[ 13/4 → 7/2 | s:sd distort:10 distortvol:0.5 ]", + "[ 27/8 → 7/2 | s:hh distort:10 distortvol:0.5 ]", + "[ 7/2 → 29/8 | s:hh distort:10 distortvol:0.5 ]", + "[ 29/8 → 15/4 | s:bd distort:10 distortvol:0.5 ]", + "[ 29/8 → 15/4 | s:hh distort:10 distortvol:0.5 ]", + "[ 15/4 → 31/8 | s:hh distort:10 distortvol:0.5 ]", + "[ 15/4 → 4/1 | s:sd distort:10 distortvol:0.5 ]", + "[ 31/8 → 4/1 | s:hh distort:10 distortvol:0.5 ]", ] `; exports[`runs examples > example "distort" example index 1 1`] = ` [ - "[ 0/1 → 1/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 1/8 → 1/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 1/4 → 3/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 3/8 → 1/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 1/2 → 5/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 5/8 → 3/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 3/4 → 7/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 7/8 → 1/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 1/1 → 9/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 9/8 → 5/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 5/4 → 11/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 11/8 → 3/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 3/2 → 13/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 13/8 → 7/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 7/4 → 15/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 15/8 → 2/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 2/1 → 17/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 17/8 → 9/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 9/4 → 19/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 19/8 → 5/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 5/2 → 21/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 21/8 → 11/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 11/4 → 23/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 23/8 → 3/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 3/1 → 25/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 25/8 → 13/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 13/4 → 27/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 27/8 → 7/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 7/2 → 29/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 29/8 → 15/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 15/4 → 31/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", - "[ 31/8 → 4/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:[8 0.4] ]", + "[ 0/1 → 1/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 1/8 → 1/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 1/4 → 3/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 3/8 → 1/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 1/2 → 5/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 5/8 → 3/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 3/4 → 7/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 7/8 → 1/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 1/1 → 9/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 9/8 → 5/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 5/4 → 11/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 11/8 → 3/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 3/2 → 13/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 13/8 → 7/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 7/4 → 15/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 15/8 → 2/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 2/1 → 17/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 17/8 → 9/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 9/4 → 19/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 19/8 → 5/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 5/2 → 21/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 21/8 → 11/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 11/4 → 23/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 23/8 → 3/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 3/1 → 25/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 25/8 → 13/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 13/4 → 27/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 27/8 → 7/2 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 7/2 → 29/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 29/8 → 15/4 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 15/4 → 31/8 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", + "[ 31/8 → 4/1 | note:d1 s:sine penv:36 pdecay:0.12 decay:0.23 distort:8 distortvol:0.4 ]", ] `; From 4f55144232a0c89c779ea26f52df6203d690de48 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 1 Mar 2024 00:31:33 +0100 Subject: [PATCH 23/26] nested controls poc --- packages/core/controls.mjs | 44 ++++++++++++++++------------ packages/core/test/controls.test.mjs | 4 +++ 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 671566be..aaa6421b 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -7,26 +7,32 @@ This program is free software: you can redistribute it and/or modify it under th import { Pattern, register, sequence } from './pattern.mjs'; export function createParam(names) { - const name = Array.isArray(names) ? names[0] : names; + let isMulti = Array.isArray(names); + names = !isMulti ? [names] : names; + const name = names[0]; - var withVal; - if (Array.isArray(names)) { - withVal = (xs) => { - if (Array.isArray(xs)) { - const result = {}; - xs.forEach((x, i) => { - if (i < names.length) { - result[names[i]] = x; - } - }); - return result; - } else { - return { [name]: xs }; - } - }; - } else { - withVal = (x) => ({ [name]: x }); - } + const withVal = (xs) => { + let bag; + // check if we have an object with an unnamed control (.value) + if (typeof xs === 'object' && xs.value !== undefined) { + bag = xs; // grab props that are already there + xs = xs.value; // grab the unnamed control for this one + delete bag.value; + } + if (isMulti && Array.isArray(xs)) { + const result = bag || {}; + xs.forEach((x, i) => { + if (i < names.length) { + result[names[i]] = x; + } + }); + return result; + } else if (bag) { + return { ...bag, [name]: xs }; + } else { + return { [name]: xs }; + } + }; const func = (...pats) => sequence(...pats).withValue(withVal); diff --git a/packages/core/test/controls.test.mjs b/packages/core/test/controls.test.mjs index aa66bf98..69d63645 100644 --- a/packages/core/test/controls.test.mjs +++ b/packages/core/test/controls.test.mjs @@ -25,4 +25,8 @@ describe('controls', () => { { s: 'sd', n: 4, gain: 0.5 }, ]); }); + it('should support nested controls', () => { + expect(s(mini('bd').pan(1)).firstCycleValues).toEqual([{ s: 'bd', pan: 1 }]); + expect(s(mini('bd:1').pan(1)).firstCycleValues).toEqual([{ s: 'bd', n: 1, pan: 1 }]); + }); }); From bf343eb499bb1c24cd83242d09a985493fb3b05d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 1 Mar 2024 00:32:45 +0100 Subject: [PATCH 24/26] simplify _composeOp --- packages/core/pattern.mjs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 13ac8cfd..00f20adf 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -896,16 +896,15 @@ addToPrototype('weaveWith', function (t, ...funcs) { ////////////////////////////////////////////////////////////////////// // compose matrix functions -// TODO - adopt value.mjs fully.. +function _nonArrayObject(x) { + return !Array.isArray(x) && typeof x === 'object'; +} function _composeOp(a, b, func) { - function _nonFunctionObject(x) { - return x instanceof Object && !(x instanceof Function); - } - if (_nonFunctionObject(a) || _nonFunctionObject(b)) { - if (!_nonFunctionObject(a)) { + if (_nonArrayObject(a) || _nonArrayObject(b)) { + if (!_nonArrayObject(a)) { a = { value: a }; } - if (!_nonFunctionObject(b)) { + if (!_nonArrayObject(b)) { b = { value: b }; } return unionWithObj(a, b, func); From daa62202fad99b2a3499ecaae6b7538062287c95 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Fri, 1 Mar 2024 13:12:52 -0500 Subject: [PATCH 25/26] default values needed in superdough for worklet input --- 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 878e6c1e..ef62118e 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -316,9 +316,9 @@ export const superdough = async (value, deadline, hapDuration) => { coarse, crush, shape, - shapevol, + shapevol = 1, distort, - distortvol, + distortvol = 1, pan, vowel, delay = 0, From 4af7262f45a4b41267adec38d3f534534c09ea6e Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 2 Mar 2024 01:16:41 -0500 Subject: [PATCH 26/26] fixed performance issue --- packages/superdough/worklets.mjs | 99 +++++++++++++++++++------------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 3f6d7ac9..4f2965ee 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1,23 +1,6 @@ -const processSample = (inputs, outputs, processBlock) => { - const input = inputs[0]; - const output = outputs[0]; - const blockSize = 128; - if (input == null || output == null) { - return false; - } - - for (let n = 0; n < blockSize; n++) { - input.forEach((inChannel, i) => { - const outChannel = output[i % output.length]; - const block = inChannel[n]; - outChannel[n] = processBlock(block, n, inChannel, outChannel); - }); - } - return true; -}; - // coarse, crush, and shape processors adapted from dktr0's webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js // LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE + class CoarseProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [{ name: 'coarse', defaultValue: 1 }]; @@ -28,15 +11,24 @@ class CoarseProcessor extends AudioWorkletProcessor { } process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const blockSize = 128; + let coarse = parameters.coarse[0] ?? 0; coarse = Math.max(1, coarse); - return processSample(inputs, outputs, (block, n, inChannel, outChannel) => { - const value = n % coarse === 0 ? block : outChannel[n - 1]; - return value; - }); + + if (input[0] == null || output[0] == null) { + return false; + } + for (let n = 0; n < blockSize; n++) { + for (let i = 0; i < input.length; i++) { + output[i][n] = n % coarse === 0 ? input[i][n] : output[i][n - 1]; + } + } + return true; } } - registerProcessor('coarse-processor', CoarseProcessor); class CrushProcessor extends AudioWorkletProcessor { @@ -49,13 +41,23 @@ class CrushProcessor extends AudioWorkletProcessor { } process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const blockSize = 128; + let crush = parameters.crush[0] ?? 8; crush = Math.max(1, crush); - return processSample(inputs, outputs, (block) => { - const x = Math.pow(2, crush - 1); - return Math.round(block * x) / x; - }); + if (input[0] == null || output[0] == null) { + return false; + } + for (let n = 0; n < blockSize; n++) { + for (let i = 0; i < input.length; i++) { + const x = Math.pow(2, crush - 1); + output[i][n] = Math.round(input[i][n] * x) / x; + } + } + return true; } } registerProcessor('crush-processor', CrushProcessor); @@ -73,17 +75,26 @@ class ShapeProcessor extends AudioWorkletProcessor { } process(inputs, outputs, parameters) { + const input = inputs[0]; + const output = outputs[0]; + const blockSize = 128; + let shape = parameters.shape[0]; - const postgain = Math.max(0.001, Math.min(1, parameters.postgain[0])); shape = shape < 1 ? shape : 1.0 - 4e-10; shape = (2.0 * shape) / (1.0 - shape); - return processSample(inputs, outputs, (block) => { - const val = ((1 + shape) * block) / (1 + shape * Math.abs(block)); - return val * postgain; - }); + const postgain = Math.max(0.001, Math.min(1, parameters.postgain[0])); + + if (input[0] == null || output[0] == null) { + return false; + } + for (let n = 0; n < blockSize; n++) { + for (let i = 0; i < input.length; i++) { + output[i][n] = (((1 + shape) * input[i][n]) / (1 + shape * Math.abs(input[i][n]))) * postgain; + } + } + return true; } } - registerProcessor('shape-processor', ShapeProcessor); class DistortProcessor extends AudioWorkletProcessor { @@ -99,14 +110,22 @@ class DistortProcessor extends AudioWorkletProcessor { } process(inputs, outputs, parameters) { - let shape = parameters.distort[0]; + const input = inputs[0]; + const output = outputs[0]; + const blockSize = 128; + + const shape = Math.expm1(parameters.distort[0]); const postgain = Math.max(0.001, Math.min(1, parameters.postgain[0])); - shape = Math.expm1(shape); - return processSample(inputs, outputs, (block) => { - const val = ((1 + shape) * block) / (1 + shape * Math.abs(block)); - return val * postgain; - }); + + if (input[0] == null || output[0] == null) { + return false; + } + for (let n = 0; n < blockSize; n++) { + for (let i = 0; i < input.length; i++) { + output[i][n] = (((1 + shape) * input[i][n]) / (1 + shape * Math.abs(input[i][n]))) * postgain; + } + } + return true; } } - registerProcessor('distort-processor', DistortProcessor);