From 7c367eb1e8f253fa9a7a1918ca5edb9bd88ea07e Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Wed, 22 Feb 2023 11:51:31 +0000 Subject: [PATCH] slice and splice (#466) Implements `slice` and `splice` from tidal, intended for beat slicing --- packages/core/pattern.mjs | 112 +++++++++++++++++----- packages/core/test/pattern.test.mjs | 27 ++++++ test/__snapshots__/examples.test.mjs.snap | 63 ++++++++++++ 3 files changed, 178 insertions(+), 24 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 1274e760..6268ffb5 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1595,11 +1595,11 @@ export const trigzero = methodToFunction('trigzero'); * @noAutocomplete * */ -export function register(name, func) { +export function register(name, func, patternify = true) { if (Array.isArray(name)) { const result = {}; for (const name_item of name) { - result[name_item] = register(name_item, func); + result[name_item] = register(name_item, func, patternify); } return result; } @@ -1607,29 +1607,40 @@ export function register(name, func) { registerMethod(name); - const pfunc = function (...args) { - args = args.map(reify); - const pat = args[args.length - 1]; - if (arity === 1) { - return func(pat); - } - const [left, ...right] = args.slice(0, -1); - let mapFn = (...args) => { - // make sure to call func with the correct argument count - // args.length is expected to be <= arity-1 - // so we set undefined args explicitly undefined - Array(arity - 1) - .fill() - .map((_, i) => args[i] ?? undefined); - return func(...args, pat); + var pfunc; + + if (patternify) { + pfunc = function (...args) { + args = args.map(reify); + const pat = args[args.length - 1]; + if (arity === 1) { + return func(pat); + } + const [left, ...right] = args.slice(0, -1); + let mapFn = (...args) => { + // make sure to call func with the correct argument count + // args.length is expected to be <= arity-1 + // so we set undefined args explicitly undefined + Array(arity - 1) + .fill() + .map((_, i) => args[i] ?? undefined); + return func(...args, pat); + }; + mapFn = curryPattern(mapFn, arity - 1); + + const app = function (acc, p, i) { + return acc.appLeft(p); + }; + const start = left.fmap(mapFn); + + return right.reduce(app, start).innerJoin(); }; - mapFn = curryPattern(mapFn, arity - 1); - - const app = (acc, p) => acc.appLeft(p); - const start = left.fmap(mapFn); - - return right.reduce(app, start).innerJoin(); - }; + } else { + pfunc = function (...args) { + args = args.map(reify); + return func(...args); + }; + } Pattern.prototype[name] = function (...args) { args = args.map(reify); @@ -2419,6 +2430,59 @@ const _loopAt = function (factor, pat, cps = 1) { .slow(factor); }; +/** + * Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers. + * @name slice + * @memberof Pattern + * @returns Pattern + * @example + * await samples('github:tidalcycles/Dirt-Samples/master') + * s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5) + */ +const slice = register( + 'slice', + function (npat, ipat, opat) { + return npat.innerBind((n) => + ipat.outerBind((i) => + opat.outerBind((o) => { + // If it's not an object, assume it's a string and make it a 's' control parameter + o = o instanceof Object ? o : { s: o }; + // Remember we must stay pure and avoid editing the object directly + const toAdd = { begin: i / n, end: (i + 1) / n, _slices: n }; + return pure({ ...toAdd, ...o }); + }), + ), + ); + }, + false, // turns off auto-patternification +); + +/** + * Works the same as slice, but changes the playback speed of each slice to match the duration of its step. + * @name splice + * @memberof Pattern + * @returns Pattern + * @example + * await samples('github:tidalcycles/Dirt-Samples/master') + * s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65) + */ +const splice = register( + 'splice', + function (npat, ipat, opat) { + const sliced = slice(npat, ipat, opat); + return sliced.withHap(function (hap) { + return hap.withValue((v) => ({ + ...{ + speed: (1 / v._slices / hap.whole.duration) * (v.speed || 1), + unit: 'c', + }, + ...v, + })); + }); + }, + false, // turns off auto-patternification +); + const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) { return _loopAt(factor, pat, 1); }); diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 3d71f529..3e95efca 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -47,6 +47,7 @@ import { run, hitch, set, + begin, } from '../index.mjs'; import { steady } from '../signal.mjs'; @@ -986,4 +987,30 @@ describe('Pattern', () => { sameFirst(n(0, 1).weave(2, s('bd', silence), s(silence, 'sd')), sequence(s('bd').n(0), s('sd').n(1))); }); }); + describe('slice', () => { + it('Can slice a sample', () => { + sameFirst( + s('break').slice(4, sequence(0, 1, 2, 3)), + sequence( + { begin: 0, end: 0.25, s: 'break', _slices: 4 }, + { begin: 0.25, end: 0.5, s: 'break', _slices: 4 }, + { begin: 0.5, end: 0.75, s: 'break', _slices: 4 }, + { begin: 0.75, end: 1, s: 'break', _slices: 4 }, + ), + ); + }); + }); + describe('splice', () => { + it('Can splice a sample', () => { + sameFirst( + s('break').splice(4, sequence(0, 1, 2, 3)), + sequence( + { begin: 0, end: 0.25, s: 'break', _slices: 4, unit: 'c', speed: 1 }, + { begin: 0.25, end: 0.5, s: 'break', _slices: 4, unit: 'c', speed: 1 }, + { begin: 0.5, end: 0.75, s: 'break', _slices: 4, unit: 'c', speed: 1 }, + { begin: 0.75, end: 1, s: 'break', _slices: 4, unit: 'c', speed: 1 }, + ), + ); + }); + }); }); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index e53cf9fa..95ae2b41 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3690,6 +3690,39 @@ exports[`runs examples > example "sine" example index 0 1`] = ` ] `; +exports[`runs examples > example "slice" example index 0 1`] = ` +[ + "[ (15/16 → 1/1) ⇝ 9/8 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 3/4 → 15/16 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 21/32 → 3/4 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]", + "[ 9/16 → 21/32 | begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 3/8 → 9/16 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]", + "[ 3/16 → 3/8 | begin:0.75 end:0.875 _slices:8 s:breaks165 ]", + "[ 0/1 → 3/16 | begin:0.875 end:1 _slices:8 s:breaks165 ]", + "[ 21/16 → 3/2 | begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 9/8 → 21/16 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ 15/16 ⇜ (1/1 → 9/8) | begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 3/2 → 27/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 27/16 → 15/8 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ 15/8 → 63/32 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ (63/32 → 2/1) ⇝ 33/16 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 63/32 ⇜ (2/1 → 33/16) | begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 33/16 → 9/4 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 9/4 → 75/32 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]", + "[ 75/32 → 39/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 39/16 → 21/8 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]", + "[ 21/8 → 45/16 | begin:0.75 end:0.875 _slices:8 s:breaks165 ]", + "[ 45/16 → 3/1 | begin:0.875 end:1 _slices:8 s:breaks165 ]", + "[ 3/1 → 51/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 51/16 → 27/8 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ 27/8 → 57/16 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 57/16 → 15/4 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 15/4 → 123/32 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]", + "[ 123/32 → 63/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ (63/16 → 4/1) ⇝ 33/8 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]", +] +`; + exports[`runs examples > example "slow" example index 0 1`] = ` [ "[ 0/1 → 1/1 | s:bd ]", @@ -3815,6 +3848,36 @@ exports[`runs examples > example "speed" example index 1 1`] = ` ] `; +exports[`runs examples > example "splice" example index 0 1`] = ` +[ + "[ 0/1 → 5/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 5/26 → 5/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ 5/13 → 20/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 20/39 → 25/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 25/39 → 10/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 10/13 → 25/26 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ (25/26 → 1/1) ⇝ 35/26 | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 25/26 ⇜ (1/1 → 35/26) | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 35/26 → 20/13 | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]", + "[ 20/13 → 45/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 45/26 → 25/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ (25/13 → 2/1) ⇝ 80/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 25/13 ⇜ (2/1 → 80/39) | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 80/39 → 85/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 85/39 → 30/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 30/13 → 5/2 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 5/2 → 75/26 | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ (75/26 → 3/1) ⇝ 40/13 | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]", + "[ 75/26 ⇜ (3/1 → 40/13) | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]", + "[ 40/13 → 85/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ 85/26 → 45/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]", + "[ 45/13 → 140/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]", + "[ 140/39 → 145/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", + "[ 145/39 → 50/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]", + "[ (50/13 → 4/1) ⇝ 105/26 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]", +] +`; + exports[`runs examples > example "square" example index 0 1`] = ` [ "[ 0/1 → 1/2 | note:C3 ]",