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 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);