diff --git a/packages/core/euclid.mjs b/packages/core/euclid.mjs index c477ddfc7..eba3f3303 100644 --- a/packages/core/euclid.mjs +++ b/packages/core/euclid.mjs @@ -142,11 +142,38 @@ const _euclidRot = function (pulses, steps, rotation) { return b; }; -export const euclid = register('euclid', function (pulses, steps, pat) { - if (steps < 0) { - return _euclidLegato(pulses, -steps, 0, pat); +const _euclidLegato = function (pulses, steps, rotation, pat) { + var bin_pat = _euclidRot(pulses, steps, 0); + // Because the `gapless` algorithm logic assumes the first entry in bin_pat is a 1, + // we rotate the pattern so the first onset is at the start. + const firstOnset = bin_pat.indexOf(1); + bin_pat = rotate(bin_pat, firstOnset); + steps = Math.abs(steps); + const gapless = bin_pat + .join('') + .split('1') + .slice(1) + .map((s) => [s.length + 1, true]); + return pat.struct(timeCat(...gapless).late(Fraction(rotation + firstOnset).div(steps))); +}; + +const _euclid = function (pulses, steps, rotation, pat) { + if (steps === 0 || pulses === 0) { + return silence; } - return pat.struct(_euclidRot(pulses, steps, 0)); + const cycles = Math.ceil(Math.abs(pulses) / Math.abs(steps)); + const totalSteps = cycles * steps; + if (totalSteps < 0) { + return _euclidLegato(pulses, -totalSteps, rotation, pat.fast(cycles)).slow(cycles); + } + return pat + .fast(cycles) + .struct(_euclidRot(pulses, totalSteps, rotation)) + .slow(cycles); +}; + +export const euclid = register('euclid', function (pulses, steps, pat) { + return _euclid(pulses, steps, 0, pat); }); export const bjork = register('bjork', function (euc, pat) { @@ -154,14 +181,11 @@ export const bjork = register('bjork', function (euc, pat) { euc = [euc]; } const [pulses, steps = pulses, rot = 0] = euc; - return pat.struct(_euclidRot(pulses, steps, rot)); + return _euclid(pulses, steps, rot, pat); }); export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], function (pulses, steps, rotation, pat) { - if (steps < 0) { - return _euclidLegato(pulses, -steps, rotation, pat); - } - return pat.struct(_euclidRot(pulses, steps, rotation)); + return _euclid(pulses, steps, rotation, pat); }); /** @@ -180,26 +204,8 @@ export const { euclidrot, euclidRot } = register(['euclidrot', 'euclidRot'], fun * note("c3").euclidLegato(3,8) */ -const _euclidLegato = function (pulses, steps, rotation, pat) { - var bin_pat = _euclidRot(pulses, steps, 0); - // Because the `gapless` algorithm logic assumes the first entry in bin_pat is a 1, - // we rotate the pattern so the first onset is at the start. - const firstOnset = bin_pat.indexOf(1); - bin_pat = rotate(bin_pat, firstOnset); - steps = Math.abs(steps); - const gapless = bin_pat - .join('') - .split('1') - .slice(1) - .map((s) => [s.length + 1, true]); - return pat.struct(timeCat(...gapless)).late(Fraction(rotation + firstOnset).div(steps)); -}; - export const euclidLegato = register(['euclidLegato'], function (pulses, steps, pat) { - if (steps < 0) { - return pat.struct(_euclidRot(pulses, -steps, 0)); - } - return _euclidLegato(pulses, steps, 0, pat); + return _euclid(pulses, -steps, 0, pat); }); /** @@ -218,10 +224,7 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps, * note("c3").euclidLegatoRot(3,5,2) */ export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) { - if (steps < 0) { - return pat.struct(_euclidRot(pulses, -steps, rotation)); - } - return _euclidLegato(pulses, steps, rotation, pat); + return _euclid(pulses, -steps, rotation, pat); }); /** diff --git a/packages/core/test/euclid.test.js b/packages/core/test/euclid.test.js index bf02a6109..a7623b3e0 100644 --- a/packages/core/test/euclid.test.js +++ b/packages/core/test/euclid.test.js @@ -57,6 +57,16 @@ describe('euclid', () => { .map((a) => a.showWhole(true)), ); }); + it('stretches over multiple cycles if pulses is greater than steps', () => { + expect( + fastcat('a') + .euclid(5, 4) + .fast(2) + .firstCycle() + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/8: a', '1/4 → 3/8: a', '3/8 → 1/2: a', '5/8 → 3/4: a', '3/4 → 7/8: a']); + }); }); describe('euclidRot', () => { @@ -123,6 +133,17 @@ describe('euclidLegato', () => { .map((a) => a.showWhole(true)), ); }); + it('stretches over multiple cycles if pulses is greater than steps', () => { + expect( + fastcat('a') + .euclidLegato(5, 4) + .fast(2) + .firstCycle() + .filter(hap => hap.hasOnset()) + .sort((a, b) => a.part.begin.sub(b.part.begin)) + .map((a) => a.showWhole(true)), + ).toStrictEqual(['0/1 → 1/4: a', '1/4 → 3/8: a', '3/8 → 5/8: a', '5/8 → 3/4: a', '3/4 → 1/1: a']); + }); }); describe('euclidLegatoRot', () => { diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index ccb91377f..a0a81ada8 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -4264,13 +4264,16 @@ exports[`runs examples > example "euclidLegatoRot" example index 0 1`] = ` "[ -1/5 ⇜ (0/1 → 1/5) | note:c3 ]", "[ 1/5 → 2/5 | note:c3 ]", "[ 2/5 → 4/5 | note:c3 ]", - "[ 4/5 → 6/5 | note:c3 ]", + "[ (4/5 → 1/1) ⇝ 6/5 | note:c3 ]", + "[ 4/5 ⇜ (1/1 → 6/5) | note:c3 ]", "[ 6/5 → 7/5 | note:c3 ]", "[ 7/5 → 9/5 | note:c3 ]", - "[ 9/5 → 11/5 | note:c3 ]", + "[ (9/5 → 2/1) ⇝ 11/5 | note:c3 ]", + "[ 9/5 ⇜ (2/1 → 11/5) | note:c3 ]", "[ 11/5 → 12/5 | note:c3 ]", "[ 12/5 → 14/5 | note:c3 ]", - "[ 14/5 → 16/5 | note:c3 ]", + "[ (14/5 → 3/1) ⇝ 16/5 | note:c3 ]", + "[ 14/5 ⇜ (3/1 → 16/5) | note:c3 ]", "[ 16/5 → 17/5 | note:c3 ]", "[ 17/5 → 19/5 | note:c3 ]", "[ (19/5 → 4/1) ⇝ 21/5 | note:c3 ]", diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index 1fb178801..d33592bb4 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -7322,12 +7322,12 @@ exports[`renders tunes > tune: randomBells 1`] = ` [ "[ -9/8 ⇜ (0/1 → 3/8) | gain:0.6 note:A3 velocity:0.598990320228 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ -3/4 ⇜ (0/1 → 3/4) | gain:0.6 note:C5 velocity:0.836992966942 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", - "[ 0/1 → 3/2 | note:F2 s:bass clip:1 gain:0.8 ]", + "[ 0/1 → 3/2 | note:D2 s:bass clip:1 gain:0.8 ]", "[ 0/1 → 9/4 | gain:0.6 note:D3 velocity:0.5 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 3/8 → 21/8 | gain:0.6 note:F5 velocity:0.921303892508 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 3/4 → 3/1 | gain:0.6 note:C5 velocity:0.842607785016 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", - "[ 3/2 → 9/4 | note:F2 s:bass clip:1 gain:0.8 ]", - "[ 9/4 → 3/1 | note:F2 s:bass clip:1 gain:0.8 ]", + "[ 3/2 → 9/4 | note:D2 s:bass clip:1 gain:0.8 ]", + "[ 9/4 → 3/1 | note:D2 s:bass clip:1 gain:0.8 ]", "[ 9/4 → 9/2 | gain:0.6 note:D4 velocity:0.700696257874 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 21/8 → 39/8 | gain:0.6 note:C4 velocity:0.650794394314 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 3/1 → 9/2 | note:D2 s:bass clip:1 gain:0.8 ]", @@ -7339,12 +7339,12 @@ exports[`renders tunes > tune: randomBells 1`] = ` "[ (21/4 → 6/1) ⇝ 27/4 | gain:0.6 note:D4 velocity:0.698815540411 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 39/8 ⇜ (6/1 → 51/8) | gain:0.6 note:D5 velocity:0.875811336562 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 21/4 ⇜ (6/1 → 27/4) | gain:0.6 note:D4 velocity:0.698815540411 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", - "[ 6/1 → 15/2 | note:D2 s:bass clip:1 gain:0.8 ]", + "[ 6/1 → 15/2 | note:A2 s:bass clip:1 gain:0.8 ]", "[ 6/1 → 33/4 | gain:0.6 note:G4 velocity:0.75977108255 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 51/8 → 69/8 | gain:0.6 note:G4 velocity:0.774316444062 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 27/4 → 9/1 | gain:0.6 note:C5 velocity:0.836244757287 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", - "[ 15/2 → 33/4 | note:D2 s:bass clip:1 gain:0.8 ]", - "[ 33/4 → 9/1 | note:D2 s:bass clip:1 gain:0.8 ]", + "[ 15/2 → 33/4 | note:A2 s:bass clip:1 gain:0.8 ]", + "[ 33/4 → 9/1 | note:A2 s:bass clip:1 gain:0.8 ]", "[ 33/4 → 21/2 | gain:0.6 note:A3 velocity:0.59140187595 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 69/8 → 87/8 | gain:0.6 note:G4 velocity:0.754063542932 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 9/1 → 21/2 | note:A2 s:bass clip:1 gain:0.8 ]", @@ -7356,12 +7356,12 @@ exports[`renders tunes > tune: randomBells 1`] = ` "[ (45/4 → 12/1) ⇝ 51/4 | gain:0.6 note:A4 velocity:0.797278589569 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 87/8 ⇜ (12/1 → 99/8) | gain:0.6 note:F4 velocity:0.734787144698 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 45/4 ⇜ (12/1 → 51/4) | gain:0.6 note:A4 velocity:0.797278589569 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", - "[ 12/1 → 27/2 | note:A2 s:bass clip:1 gain:0.8 ]", + "[ 12/1 → 27/2 | note:G2 s:bass clip:1 gain:0.8 ]", "[ 12/1 → 57/4 | gain:0.6 note:G5 velocity:0.979763559997 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 99/8 → 117/8 | gain:0.6 note:C4 velocity:0.666239210404 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 51/4 → 15/1 | gain:0.6 note:F5 velocity:0.95169514697 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", - "[ 27/2 → 57/4 | note:A2 s:bass clip:1 gain:0.8 ]", - "[ 57/4 → 15/1 | note:A2 s:bass clip:1 gain:0.8 ]", + "[ 27/2 → 57/4 | note:G2 s:bass clip:1 gain:0.8 ]", + "[ 57/4 → 15/1 | note:G2 s:bass clip:1 gain:0.8 ]", "[ 57/4 → 33/2 | gain:0.6 note:F5 velocity:0.918253320269 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 117/8 → 135/8 | gain:0.6 note:G3 velocity:0.571157105267 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 15/1 → 33/2 | note:G2 s:bass clip:1 gain:0.8 ]", @@ -7373,12 +7373,12 @@ exports[`renders tunes > tune: randomBells 1`] = ` "[ (69/4 → 18/1) ⇝ 75/4 | gain:0.6 note:F3 velocity:0.508127085865 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 135/8 ⇜ (18/1 → 147/8) | gain:0.6 note:F5 velocity:0.945647025481 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 69/4 ⇜ (18/1 → 75/4) | gain:0.6 note:F3 velocity:0.508127085865 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", - "[ 18/1 → 39/2 | note:G2 s:bass clip:1 gain:0.8 ]", + "[ 18/1 → 39/2 | note:F2 s:bass clip:1 gain:0.8 ]", "[ 18/1 → 81/4 | gain:0.6 note:A3 velocity:0.608644555323 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 147/8 → 165/8 | gain:0.6 note:F3 velocity:0.506259439513 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 75/4 → 21/1 | gain:0.6 note:D4 velocity:0.671621939167 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", - "[ 39/2 → 81/4 | note:G2 s:bass clip:1 gain:0.8 ]", - "[ 81/4 → 21/1 | note:G2 s:bass clip:1 gain:0.8 ]", + "[ 39/2 → 81/4 | note:F2 s:bass clip:1 gain:0.8 ]", + "[ 81/4 → 21/1 | note:F2 s:bass clip:1 gain:0.8 ]", "[ 81/4 → 45/2 | gain:0.6 note:D4 velocity:0.704345900565 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 165/8 → 183/8 | gain:0.6 note:D5 velocity:0.887838857248 s:bell delay:0.2 delaytime:0.333333333333 delayfeedback:0.8 ]", "[ 21/1 → 45/2 | note:F2 s:bass clip:1 gain:0.8 ]",