mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 05:38:35 +00:00
Another attempt at composable functions - WIP (#390)
Summary of changes:
- Made unary functions composable, including controls. So e.g. s("bd sd").every(3,fast(2).iter(4).n(4)) works the same as s("bd sd").every(3,x => x.fast(2).iter(4).n(4))
- Made operators/alignments composable too, so s("bd sd").every(3, set.squeeze.n(3, 4)) works
- Patterns are not treated as functions, so s("bd sd").every(3, n(5)) is an annoying runtime error. s("bd sd").every(3, set.n(5)) does work though.
Other minor changes:
- standardised alignment 'squeezeOut' as lowercase 'squeezeout'
- made firstCycleValues turn haps sorted in order of 'part'
This commit is contained in:
parent
924e8a764a
commit
cbae355896
@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern, sequence } from './pattern.mjs';
|
||||
import { Pattern, sequence, registerControl } from './pattern.mjs';
|
||||
|
||||
const controls = {};
|
||||
const generic_params = [
|
||||
@ -828,26 +828,26 @@ const generic_params = [
|
||||
|
||||
// TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13
|
||||
|
||||
const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
|
||||
|
||||
const _setter = (func, name) =>
|
||||
function (...pats) {
|
||||
const makeControl = function (name) {
|
||||
const func = (...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));
|
||||
const setter = function (...pats) {
|
||||
if (!pats.length) {
|
||||
return this.fmap((value) => ({ [name]: value }));
|
||||
}
|
||||
return this.set(func(...pats));
|
||||
};
|
||||
Pattern.prototype[name] = setter;
|
||||
registerControl(name, func);
|
||||
return func;
|
||||
};
|
||||
|
||||
generic_params.forEach(([type, name, description]) => {
|
||||
controls[name] = (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = _setter(controls[name], name);
|
||||
controls[name] = makeControl(name);
|
||||
});
|
||||
|
||||
// create custom param
|
||||
controls.createParam = (name) => {
|
||||
const func = (...pats) => _name(name, ...pats);
|
||||
Pattern.prototype[name] = _setter(func, name);
|
||||
return (...pats) => _name(name, ...pats);
|
||||
return makeControl(name);
|
||||
};
|
||||
|
||||
controls.createParams = (...names) =>
|
||||
|
||||
@ -21,6 +21,141 @@ let stringParser;
|
||||
// intended to use with mini to automatically interpret all strings as mini notation
|
||||
export const setStringParser = (parser) => (stringParser = parser);
|
||||
|
||||
const alignments = ['in', 'out', 'mix', 'squeeze', 'squeezeout', 'trig', 'trigzero'];
|
||||
|
||||
const methodRegistry = [];
|
||||
const getterRegistry = [];
|
||||
const controlRegistry = [];
|
||||
const controlSubscribers = [];
|
||||
const composifiedRegistry = [];
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Magic for supporting higher order composition of method chains
|
||||
|
||||
// Dresses the given (unary) function with methods for composition chaining, so e.g.
|
||||
// `fast(2).iter(4)` composes to pattern functions into a new one.
|
||||
function composify(func) {
|
||||
if (!func.__composified) {
|
||||
for (const [name, method] of methodRegistry) {
|
||||
func[name] = method;
|
||||
}
|
||||
for (const [name, getter] of getterRegistry) {
|
||||
Object.defineProperty(func, name, getter);
|
||||
}
|
||||
func.__composified = true;
|
||||
composifiedRegistry.push(func);
|
||||
} else {
|
||||
console.log('Warning: attempt at composifying a function more than once');
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
export function registerMethod(name, addAlignments = false, addControls = false) {
|
||||
if (addAlignments || addControls) {
|
||||
// This method needs to make its 'this' object available to chained alignments and/or
|
||||
// control parameters, so it has to be implemented as a getter
|
||||
const getter = {
|
||||
get: function () {
|
||||
const func = this;
|
||||
const wrapped = function (...args) {
|
||||
const composed = (pat) => func(pat)[name](...args);
|
||||
return composify(composed);
|
||||
};
|
||||
|
||||
if (addAlignments) {
|
||||
for (const alignment of alignments) {
|
||||
wrapped[alignment] = function (...args) {
|
||||
const composed = (pat) => func(pat)[name][alignment](...args);
|
||||
return composify(composed);
|
||||
};
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
wrapped[alignment][controlname] = function (...args) {
|
||||
const composed = (pat) => func(pat)[name][alignment](controlfunc(...args));
|
||||
return composify(composed);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (addControls) {
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
wrapped[controlname] = function (...args) {
|
||||
const composed = (pat) => func(pat)[name](controlfunc(...args));
|
||||
return composify(composed);
|
||||
};
|
||||
}
|
||||
}
|
||||
return wrapped;
|
||||
},
|
||||
};
|
||||
|
||||
getterRegistry.push([name, getter]);
|
||||
|
||||
// Add to functions already 'composified'
|
||||
for (const composified of composifiedRegistry) {
|
||||
Object.defineProperty(composified, name, getter);
|
||||
}
|
||||
} else {
|
||||
// No chained alignments/controls needed, so we can just add as a plain method,
|
||||
// probably more efficient this way?
|
||||
const method = function (...args) {
|
||||
const func = this;
|
||||
const composed = (pat) => func(pat)[name](...args);
|
||||
return composify(composed);
|
||||
};
|
||||
|
||||
methodRegistry.push([name, method]);
|
||||
|
||||
// Add to functions already 'composified'
|
||||
for (const composified of composifiedRegistry) {
|
||||
composified[name] = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function registerControl(controlname, controlfunc) {
|
||||
registerMethod(controlname);
|
||||
controlRegistry.push([controlname, controlfunc]);
|
||||
for (const subscriber of controlSubscribers) {
|
||||
subscriber(controlname, controlfunc);
|
||||
}
|
||||
}
|
||||
|
||||
export function withControls(func) {
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
func(controlname, controlfunc);
|
||||
}
|
||||
controlSubscribers.push(func);
|
||||
}
|
||||
|
||||
export function addToPrototype(name, func) {
|
||||
Pattern.prototype[name] = func;
|
||||
registerMethod(name);
|
||||
}
|
||||
|
||||
export function curryPattern(func, arity = func.length) {
|
||||
const fn = function curried(...args) {
|
||||
if (args.length >= arity) {
|
||||
return func.apply(this, args);
|
||||
}
|
||||
|
||||
const partial = function (...args2) {
|
||||
return curried.apply(this, args.concat(args2));
|
||||
};
|
||||
if (args.length == arity - 1) {
|
||||
return composify(partial);
|
||||
}
|
||||
|
||||
return partial;
|
||||
};
|
||||
if (arity == 1) {
|
||||
composify(fn);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The core Pattern class
|
||||
|
||||
/** @class Class representing a pattern. */
|
||||
export class Pattern {
|
||||
/**
|
||||
@ -643,7 +778,9 @@ export class Pattern {
|
||||
* @noAutocomplete
|
||||
*/
|
||||
get firstCycleValues() {
|
||||
return this.firstCycle().map((hap) => hap.value);
|
||||
return this.sortHapsByPart()
|
||||
.firstCycle()
|
||||
.map((hap) => hap.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -693,7 +830,7 @@ export class Pattern {
|
||||
const otherPat = reify(other);
|
||||
return this.fmap((a) => otherPat.fmap((b) => func(a)(b))).squeezeJoin();
|
||||
}
|
||||
_opSqueezeOut(other, func) {
|
||||
_opSqueezeout(other, func) {
|
||||
const thisPat = this;
|
||||
const otherPat = reify(other);
|
||||
return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a))).squeezeJoin();
|
||||
@ -860,11 +997,11 @@ function groupHapsBy(eq, haps) {
|
||||
const congruent = (a, b) => a.spanEquals(b);
|
||||
// Pattern<Hap<T>> -> Pattern<Hap<T[]>>
|
||||
// returned pattern contains arrays of congruent haps
|
||||
Pattern.prototype.collect = function () {
|
||||
addToPrototype('collect', function () {
|
||||
return this.withHaps((haps) =>
|
||||
groupHapsBy(congruent, haps).map((_haps) => new Hap(_haps[0].whole, _haps[0].part, _haps, {})),
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Selects indices in in stacked notes.
|
||||
@ -872,12 +1009,12 @@ Pattern.prototype.collect = function () {
|
||||
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
|
||||
* .arpWith(haps => haps[2])
|
||||
* */
|
||||
Pattern.prototype.arpWith = function (func) {
|
||||
addToPrototype('arpWith', function (func) {
|
||||
return this.collect()
|
||||
.fmap((v) => reify(func(v)))
|
||||
.innerJoin()
|
||||
.withHap((h) => new Hap(h.whole, h.part, h.value.value, h.combineContext(h.value)));
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Selects indices in in stacked notes.
|
||||
@ -885,9 +1022,9 @@ Pattern.prototype.arpWith = function (func) {
|
||||
* note("<[c,eb,g]!2 [c,f,ab] [d,f,ab]>")
|
||||
* .arp("0 [0,2] 1 [0,2]").slow(2)
|
||||
* */
|
||||
Pattern.prototype.arp = function (pat) {
|
||||
addToPrototype('arp', function (pat) {
|
||||
return this.arpWith((haps) => pat.fmap((i) => haps[i % haps.length]));
|
||||
};
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// compose matrix functions
|
||||
@ -985,15 +1122,15 @@ function _composeOp(a, b, func) {
|
||||
func: [(a, b) => b(a)],
|
||||
};
|
||||
|
||||
const hows = ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Trig', 'Trigzero'];
|
||||
const hows = alignments.map((x) => x.charAt(0).toUpperCase() + x.slice(1));
|
||||
|
||||
// generate methods to do what and how
|
||||
for (const [what, [op, preprocess]] of Object.entries(composers)) {
|
||||
// make plain version, e.g. pat._add(value) adds that plain value
|
||||
// to all the values in pat
|
||||
Pattern.prototype['_' + what] = function (value) {
|
||||
addToPrototype('_' + what, function (value) {
|
||||
return this.fmap((x) => op(x, value));
|
||||
};
|
||||
});
|
||||
|
||||
// make patternified monster version
|
||||
Object.defineProperty(Pattern.prototype, what, {
|
||||
@ -1007,7 +1144,7 @@ function _composeOp(a, b, func) {
|
||||
|
||||
// add methods to that function for each behaviour
|
||||
for (const how of hows) {
|
||||
wrapper[how.toLowerCase()] = function (...other) {
|
||||
const howfunc = function (...other) {
|
||||
var howpat = pat;
|
||||
other = sequence(other);
|
||||
if (preprocess) {
|
||||
@ -1025,19 +1162,41 @@ function _composeOp(a, b, func) {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
howfunc[controlname] = (...args) => howfunc(controlfunc(...args));
|
||||
}
|
||||
wrapper[how.toLowerCase()] = howfunc;
|
||||
}
|
||||
wrapper.squeezein = wrapper.squeeze;
|
||||
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
wrapper[controlname] = (...args) => wrapper.in(controlfunc(...args));
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
},
|
||||
});
|
||||
|
||||
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
|
||||
for (const how of hows) {
|
||||
Pattern.prototype[how.toLowerCase()] = function (...args) {
|
||||
return this.set[how.toLowerCase()](args);
|
||||
};
|
||||
}
|
||||
registerMethod(what, true, true);
|
||||
}
|
||||
|
||||
// Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2)
|
||||
for (const howLower of alignments) {
|
||||
// Using a 'get'ted function so that all the controls are added
|
||||
Object.defineProperty(Pattern.prototype, howLower, {
|
||||
get: function () {
|
||||
const pat = this;
|
||||
const howfunc = function (...args) {
|
||||
return pat.set[howLower](args);
|
||||
};
|
||||
for (const [controlname, controlfunc] of controlRegistry) {
|
||||
howfunc[controlname] = (...args) => howfunc(controlfunc(...args));
|
||||
}
|
||||
return howfunc;
|
||||
},
|
||||
});
|
||||
registerMethod(howLower, false, true);
|
||||
}
|
||||
|
||||
// binary composers
|
||||
@ -1049,36 +1208,36 @@ function _composeOp(a, b, func) {
|
||||
* .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~")
|
||||
* .slow(4)
|
||||
*/
|
||||
Pattern.prototype.struct = function (...args) {
|
||||
addToPrototype('struct', function (...args) {
|
||||
return this.keepif.out(...args);
|
||||
};
|
||||
Pattern.prototype.structAll = function (...args) {
|
||||
});
|
||||
addToPrototype('structAll', function (...args) {
|
||||
return this.keep.out(...args);
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Returns silence when mask is 0 or "~"
|
||||
*
|
||||
* @example
|
||||
* note("c [eb,g] d [eb,g]").mask("<1 [0 1]>").slow(2)
|
||||
*/
|
||||
Pattern.prototype.mask = function (...args) {
|
||||
addToPrototype('mask', function (...args) {
|
||||
return this.keepif.in(...args);
|
||||
};
|
||||
Pattern.prototype.maskAll = function (...args) {
|
||||
});
|
||||
addToPrototype('maskAll', function (...args) {
|
||||
return this.keep.in(...args);
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Resets the pattern to the start of the cycle for each onset of the reset pattern.
|
||||
*
|
||||
* @example
|
||||
* s("<bd lt> sd, hh*4").reset("<x@3 x(3,8)>")
|
||||
*/
|
||||
Pattern.prototype.reset = function (...args) {
|
||||
addToPrototype('reset', function (...args) {
|
||||
return this.keepif.trig(...args);
|
||||
};
|
||||
Pattern.prototype.resetAll = function (...args) {
|
||||
});
|
||||
addToPrototype('resetAll', function (...args) {
|
||||
return this.keep.trig(...args);
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Restarts the pattern for each onset of the restart pattern.
|
||||
* While reset will only reset the current cycle, restart will start from cycle 0.
|
||||
@ -1086,12 +1245,12 @@ function _composeOp(a, b, func) {
|
||||
* @example
|
||||
* s("<bd lt> sd, hh*4").restart("<x@3 x(3,8)>")
|
||||
*/
|
||||
Pattern.prototype.restart = function (...args) {
|
||||
addToPrototype('restart', function (...args) {
|
||||
return this.keepif.trigzero(...args);
|
||||
};
|
||||
Pattern.prototype.restartAll = function (...args) {
|
||||
});
|
||||
addToPrototype('restartAll', function (...args) {
|
||||
return this.keep.trigzero(...args);
|
||||
};
|
||||
});
|
||||
})();
|
||||
|
||||
// aliases
|
||||
@ -1336,36 +1495,68 @@ export function pm(...args) {
|
||||
polymeter(...args);
|
||||
}
|
||||
|
||||
export const mask = curry((a, b) => reify(b).mask(a));
|
||||
export const struct = curry((a, b) => reify(b).struct(a));
|
||||
export const superimpose = curry((a, b) => reify(b).superimpose(...a));
|
||||
export const mask = curryPattern((a, b) => reify(b).mask(a));
|
||||
export const struct = curryPattern((a, b) => reify(b).struct(a));
|
||||
export const superimpose = curryPattern((a, b) => reify(b).superimpose(...a));
|
||||
|
||||
const methodToFunction = function (name, addAlignments = false) {
|
||||
const func = curryPattern((a, b) => reify(b)[name](a));
|
||||
|
||||
withControls((controlname, controlfunc) => {
|
||||
func[controlname] = function (...pats) {
|
||||
return func(controlfunc(...pats));
|
||||
};
|
||||
});
|
||||
|
||||
if (addAlignments) {
|
||||
for (const alignment of alignments) {
|
||||
func[alignment] = curryPattern((a, b) => reify(b)[name][alignment](a));
|
||||
withControls((controlname, controlfunc) => {
|
||||
func[alignment][controlname] = function (...pats) {
|
||||
return func[alignment](controlfunc(...pats));
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return func;
|
||||
};
|
||||
|
||||
// operators
|
||||
export const set = curry((a, b) => reify(b).set(a));
|
||||
export const keep = curry((a, b) => reify(b).keep(a));
|
||||
export const keepif = curry((a, b) => reify(b).keepif(a));
|
||||
export const add = curry((a, b) => reify(b).add(a));
|
||||
export const sub = curry((a, b) => reify(b).sub(a));
|
||||
export const mul = curry((a, b) => reify(b).mul(a));
|
||||
export const div = curry((a, b) => reify(b).div(a));
|
||||
export const mod = curry((a, b) => reify(b).mod(a));
|
||||
export const pow = curry((a, b) => reify(b).pow(a));
|
||||
export const band = curry((a, b) => reify(b).band(a));
|
||||
export const bor = curry((a, b) => reify(b).bor(a));
|
||||
export const bxor = curry((a, b) => reify(b).bxor(a));
|
||||
export const blshift = curry((a, b) => reify(b).blshift(a));
|
||||
export const brshift = curry((a, b) => reify(b).brshift(a));
|
||||
export const lt = curry((a, b) => reify(b).lt(a));
|
||||
export const gt = curry((a, b) => reify(b).gt(a));
|
||||
export const lte = curry((a, b) => reify(b).lte(a));
|
||||
export const gte = curry((a, b) => reify(b).gte(a));
|
||||
export const eq = curry((a, b) => reify(b).eq(a));
|
||||
export const eqt = curry((a, b) => reify(b).eqt(a));
|
||||
export const ne = curry((a, b) => reify(b).ne(a));
|
||||
export const net = curry((a, b) => reify(b).net(a));
|
||||
export const and = curry((a, b) => reify(b).and(a));
|
||||
export const or = curry((a, b) => reify(b).or(a));
|
||||
export const func = curry((a, b) => reify(b).func(a));
|
||||
export const set = methodToFunction('set', true);
|
||||
export const keep = methodToFunction('keep', true);
|
||||
export const keepif = methodToFunction('keepif', true);
|
||||
export const add = methodToFunction('add', true);
|
||||
export const sub = methodToFunction('sub', true);
|
||||
export const mul = methodToFunction('mul', true);
|
||||
export const div = methodToFunction('div', true);
|
||||
export const mod = methodToFunction('mod', true);
|
||||
export const pow = methodToFunction('pow', true);
|
||||
export const band = methodToFunction('band', true);
|
||||
export const bor = methodToFunction('bor', true);
|
||||
export const bxor = methodToFunction('bxor', true);
|
||||
export const blshift = methodToFunction('blshift', true);
|
||||
export const brshift = methodToFunction('brshift', true);
|
||||
export const lt = methodToFunction('lt', true);
|
||||
export const gt = methodToFunction('gt', true);
|
||||
export const lte = methodToFunction('lte', true);
|
||||
export const gte = methodToFunction('gte', true);
|
||||
export const eq = methodToFunction('eq', true);
|
||||
export const eqt = methodToFunction('eqt', true);
|
||||
export const ne = methodToFunction('ne', true);
|
||||
export const net = methodToFunction('net', true);
|
||||
export const and = methodToFunction('and', true);
|
||||
export const or = methodToFunction('or', true);
|
||||
export const func = methodToFunction('func', true);
|
||||
|
||||
// alignments
|
||||
// export const in = methodToFunction('in'); // reserved word :(
|
||||
export const out = methodToFunction('out');
|
||||
export const mix = methodToFunction('mix');
|
||||
export const squeeze = methodToFunction('squeeze');
|
||||
export const squeezeout = methodToFunction('squeezeout');
|
||||
export const trig = methodToFunction('trig');
|
||||
export const trigzero = methodToFunction('trigzero');
|
||||
|
||||
/**
|
||||
* Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
|
||||
@ -1384,9 +1575,10 @@ export function register(name, func) {
|
||||
return result;
|
||||
}
|
||||
const arity = func.length;
|
||||
var pfunc; // the patternified function
|
||||
|
||||
pfunc = function (...args) {
|
||||
registerMethod(name);
|
||||
|
||||
const pfunc = function (...args) {
|
||||
args = args.map(reify);
|
||||
const pat = args[args.length - 1];
|
||||
if (arity === 1) {
|
||||
@ -1402,8 +1594,12 @@ export function register(name, func) {
|
||||
.map((_, i) => args[i] ?? undefined);
|
||||
return func(...args, pat);
|
||||
};
|
||||
mapFn = curry(mapFn, null, arity - 1);
|
||||
return right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)).innerJoin();
|
||||
mapFn = curryPattern(mapFn, arity - 1);
|
||||
|
||||
const app = (acc, p) => acc.appLeft(p);
|
||||
const start = left.fmap(mapFn);
|
||||
|
||||
return right.reduce(app, start).innerJoin();
|
||||
};
|
||||
|
||||
Pattern.prototype[name] = function (...args) {
|
||||
@ -1428,7 +1624,7 @@ export function register(name, func) {
|
||||
|
||||
// toplevel functions get curried as well as patternified
|
||||
// because pfunc uses spread args, we need to state the arity explicitly!
|
||||
return curry(pfunc, null, arity);
|
||||
return curryPattern(pfunc, arity);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -45,6 +45,8 @@ import {
|
||||
rev,
|
||||
time,
|
||||
run,
|
||||
hitch,
|
||||
set,
|
||||
} from '../index.mjs';
|
||||
|
||||
import { steady } from '../signal.mjs';
|
||||
@ -204,7 +206,7 @@ describe('Pattern', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can SqueezeOut() structure', () => {
|
||||
it('can squeezeout() structure', () => {
|
||||
sameFirst(
|
||||
sequence(1, [2, 3]).add.squeezeout(10, 20, 30),
|
||||
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
|
||||
@ -252,7 +254,7 @@ describe('Pattern', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can SqueezeOut() structure', () => {
|
||||
it('can squeezeout() structure', () => {
|
||||
sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]]));
|
||||
});
|
||||
});
|
||||
@ -294,7 +296,7 @@ describe('Pattern', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can SqueezeOut() structure', () => {
|
||||
it('can squeezeout() structure', () => {
|
||||
sameFirst(sequence(1, [2, 3]).keepif.squeezeout(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence));
|
||||
});
|
||||
});
|
||||
@ -929,6 +931,14 @@ describe('Pattern', () => {
|
||||
});
|
||||
});
|
||||
describe('alignments', () => {
|
||||
it('Can combine controls', () => {
|
||||
sameFirst(s('bd').set.in.n(3), s('bd').n(3));
|
||||
sameFirst(s('bd').set.squeeze.n(3, 4), sequence(s('bd').n(3), s('bd').n(4)));
|
||||
});
|
||||
it('Can combine functions with alignmed controls', () => {
|
||||
sameFirst(s('bd').apply(fast(2).set(n(3))), s('bd').fast(2).set.in.n(3));
|
||||
sameFirst(s('bd').apply(fast(2).set.in.n(3)), s('bd').fast(2).set.in.n(3));
|
||||
});
|
||||
it('Can squeeze arguments', () => {
|
||||
expect(sequence(1, 2).add.squeeze(4, 5).firstCycle()).toStrictEqual(sequence(5, 6, 6, 7).firstCycle());
|
||||
});
|
||||
@ -959,4 +969,16 @@ describe('Pattern', () => {
|
||||
sameFirst(s('a', 'b').hurry(2), s('a', 'b').fast(2).speed(2));
|
||||
});
|
||||
});
|
||||
describe('composable functions', () => {
|
||||
it('Can compose functions', () => {
|
||||
sameFirst(sequence(3, 4).fast(2).rev().fast(2), fast(2).rev().fast(2)(sequence(3, 4)));
|
||||
});
|
||||
it('Can compose by method chaining operators with controls', () => {
|
||||
sameFirst(s('bd').apply(set.n(3).fast(2)), s('bd').set.n(3).fast(2));
|
||||
});
|
||||
it('Can compose by method chaining operators and alignments with controls', () => {
|
||||
sameFirst(s('bd').apply(set.in.n(3).fast(2)), s('bd').set.n(3).fast(2));
|
||||
// sameFirst(s('bd').apply(set.squeeze.n(3).fast(2)), s('bd').set.squeeze.n(3).fast(2));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -139,7 +139,7 @@ export const removeUndefineds = (xs) => xs.filter((x) => x != undefined);
|
||||
export const flatten = (arr) => [].concat(...arr);
|
||||
|
||||
export const id = (a) => a;
|
||||
export const constant = (a, b) => a;
|
||||
export const constant = curry((a, b) => a);
|
||||
|
||||
export const listRange = (min, max) => Array.from({ length: max - min + 1 }, (_, i) => i + min);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user