slice and splice (#466)

Implements `slice` and `splice` from tidal, intended for beat slicing
This commit is contained in:
Alex McLean 2023-02-22 11:51:31 +00:00 committed by GitHub
parent fdb76867a7
commit 7c367eb1e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 178 additions and 24 deletions

View file

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