From 67ef4494e2aba4611e25ffd5529beecff883ab84 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 29 Sep 2023 14:41:03 +0100 Subject: [PATCH] Match behaviour of chunk with tidal, add fastChunk for old strudel behaviour. Also adds repeatCycles. fixes #689 --- packages/core/pattern.mjs | 33 +++++++++++++++++--- packages/core/test/pattern.test.mjs | 16 ++++++++++ test/__snapshots__/examples.test.mjs.snap | 37 ++++++++++++++++++----- 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index e3efb427b..161d3da8f 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -2100,25 +2100,50 @@ export const { iterBack, iterback } = register(['iterBack', 'iterback'], functio return _iter(times, pat, true); }); +/** + * Repeats each cycle the given number of times. + * @name repeatCycles + * @memberof Pattern + * @returns Pattern + * @example + * note(irand(12).add(34)).segment(4).repeatCycles(2).s("gm_acoustic_guitar_nylon") + */ +const _repeatCycles = function (n, pat) { + return slowcat(...Array(n).fill(pat)); +} + +const { repeatCycles } = register('repeatCycles', _repeatCycles); + /** * Divides a pattern into a given number of parts, then cycles through those parts in turn, applying the given function to each part in turn (one part per cycle). * @name chunk + * @synonyms slowChunk, slowchunk * @memberof Pattern * @returns Pattern * @example * "0 1 2 3".chunk(4, x=>x.add(7)).scale('A minor').note() */ -const _chunk = function (n, func, pat, back = false) { +const _chunk = function (n, func, pat, back = false, fast = false) { const binary = Array(n - 1).fill(false); binary.unshift(true); - const binary_pat = _iter(n, sequence(...binary), back); + // Invert the 'back' because we want to shift the pattern forwards, + // and so time backwards + const binary_pat = _iter(n, sequence(...binary), !back); + if (!fast) { + pat = pat.repeatCycles(n); + } return pat.when(binary_pat, func); }; -export const chunk = register('chunk', function (n, func, pat) { - return _chunk(n, func, pat, false); +const {chunk, slowchunk, slowChunk} = register(['chunk', 'slowchunk', 'slowChunk'], function (n, func, pat) { + return _chunk(n, func, pat, false, false); }); +const {fastchunk, fastChunk} = register(['fastchunk', 'fastChunk'], function (n, func, pat) { + return _chunk(n, func, pat, false, true); +}); + + /** * Like `chunk`, but cycles through the parts in reverse order. Known as chunk' in tidalcycles * @name chunkBack diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index c82673060..ad3ed1f0b 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -1003,4 +1003,20 @@ describe('Pattern', () => { ); }); }); + describe('chunk', () => { + it('Processes each cycle of the source pattern multiple times, once for each chunk', () => { + expect(sequence(0, 1, 2, 3).slow(2).chunk(2, add(10)).fast(4).firstCycleValues).toStrictEqual([ 10, 1, 0, 11, 12, 3, 2, 13 ]) + }); + }); + describe('fastChunk', () => { + it('Unlike chunk, cycles of the source pattern proceed cycle-by-cycle', () => { + expect(sequence(0, 1, 2, 3).slow(2).fastChunk(2, add(10)).fast(4).firstCycleValues).toStrictEqual([ 10,1,2,13,10,1,2,13 ]) + }); + }); + describe('repeatCycles', () => { + it('Repeats each cycle of the source pattern the given number of times', () => { + expect(slowcat(0, 1).repeatCycles(2).fast(6).firstCycleValues).toStrictEqual([ 0,0,1,1,0,0 ]) + }); + }); + }); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 48835a481..20fe3f0b4 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1087,17 +1087,17 @@ exports[`runs examples > example "chunk" example index 0 1`] = ` "[ 1/2 → 3/4 | note:C4 ]", "[ 3/4 → 1/1 | note:D4 ]", "[ 1/1 → 5/4 | note:A3 ]", - "[ 5/4 → 3/2 | note:B3 ]", + "[ 5/4 → 3/2 | note:B4 ]", "[ 3/2 → 7/4 | note:C4 ]", - "[ 7/4 → 2/1 | note:D5 ]", + "[ 7/4 → 2/1 | note:D4 ]", "[ 2/1 → 9/4 | note:A3 ]", "[ 9/4 → 5/2 | note:B3 ]", "[ 5/2 → 11/4 | note:C5 ]", "[ 11/4 → 3/1 | note:D4 ]", "[ 3/1 → 13/4 | note:A3 ]", - "[ 13/4 → 7/2 | note:B4 ]", + "[ 13/4 → 7/2 | note:B3 ]", "[ 7/2 → 15/4 | note:C4 ]", - "[ 15/4 → 4/1 | note:D4 ]", + "[ 15/4 → 4/1 | note:D5 ]", ] `; @@ -1108,17 +1108,17 @@ exports[`runs examples > example "chunkBack" example index 0 1`] = ` "[ 1/2 → 3/4 | note:C4 ]", "[ 3/4 → 1/1 | note:D4 ]", "[ 1/1 → 5/4 | note:A3 ]", - "[ 5/4 → 3/2 | note:B4 ]", + "[ 5/4 → 3/2 | note:B3 ]", "[ 3/2 → 7/4 | note:C4 ]", - "[ 7/4 → 2/1 | note:D4 ]", + "[ 7/4 → 2/1 | note:D5 ]", "[ 2/1 → 9/4 | note:A3 ]", "[ 9/4 → 5/2 | note:B3 ]", "[ 5/2 → 11/4 | note:C5 ]", "[ 11/4 → 3/1 | note:D4 ]", "[ 3/1 → 13/4 | note:A3 ]", - "[ 13/4 → 7/2 | note:B3 ]", + "[ 13/4 → 7/2 | note:B4 ]", "[ 7/2 → 15/4 | note:C4 ]", - "[ 15/4 → 4/1 | note:D5 ]", + "[ 15/4 → 4/1 | note:D4 ]", ] `; @@ -3528,6 +3528,27 @@ exports[`runs examples > example "release" example index 0 1`] = ` ] `; +exports[`runs examples > example "repeatCycles" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:42 s:gm_acoustic_guitar_nylon ]", + "[ 1/4 → 1/2 | note:38 s:gm_acoustic_guitar_nylon ]", + "[ 1/2 → 3/4 | note:35 s:gm_acoustic_guitar_nylon ]", + "[ 3/4 → 1/1 | note:38 s:gm_acoustic_guitar_nylon ]", + "[ 1/1 → 5/4 | note:42 s:gm_acoustic_guitar_nylon ]", + "[ 5/4 → 3/2 | note:38 s:gm_acoustic_guitar_nylon ]", + "[ 3/2 → 7/4 | note:35 s:gm_acoustic_guitar_nylon ]", + "[ 7/4 → 2/1 | note:38 s:gm_acoustic_guitar_nylon ]", + "[ 2/1 → 9/4 | note:42 s:gm_acoustic_guitar_nylon ]", + "[ 9/4 → 5/2 | note:36 s:gm_acoustic_guitar_nylon ]", + "[ 5/2 → 11/4 | note:39 s:gm_acoustic_guitar_nylon ]", + "[ 11/4 → 3/1 | note:41 s:gm_acoustic_guitar_nylon ]", + "[ 3/1 → 13/4 | note:42 s:gm_acoustic_guitar_nylon ]", + "[ 13/4 → 7/2 | note:36 s:gm_acoustic_guitar_nylon ]", + "[ 7/2 → 15/4 | note:39 s:gm_acoustic_guitar_nylon ]", + "[ 15/4 → 4/1 | note:41 s:gm_acoustic_guitar_nylon ]", +] +`; + exports[`runs examples > example "reset" example index 0 1`] = ` [ "[ 0/1 → 1/4 | s:hh ]",