mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-23 03:28:33 +00:00
More experimental step functions: stepwax, stepwane, steptaper, steptour and taperlist (#1042)
* group tactus-oriented functions * add stepwax, stepwane, steptaper and steptour * taperlist
This commit is contained in:
parent
558e9e36ff
commit
ebeefc0ac4
@ -1328,7 +1328,8 @@ export function slowcat(...pats) {
|
|||||||
const offset = span.begin.floor().sub(span.begin.div(pats.length).floor());
|
const offset = span.begin.floor().sub(span.begin.div(pats.length).floor());
|
||||||
return pat.withHapTime((t) => t.add(offset)).query(state.setSpan(span.withTime((t) => t.sub(offset))));
|
return pat.withHapTime((t) => t.add(offset)).query(state.setSpan(span.withTime((t) => t.sub(offset))));
|
||||||
};
|
};
|
||||||
return new Pattern(query).splitQueries();
|
const tactus = lcm(...pats.map((x) => x.tactus));
|
||||||
|
return new Pattern(query).splitQueries().setTactus(tactus);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles.
|
/** Concatenation: combines a list of patterns, switching between them successively, one per cycle. Unlike slowcat, this version will skip cycles.
|
||||||
@ -1359,42 +1360,6 @@ export function cat(...pats) {
|
|||||||
return slowcat(...pats);
|
return slowcat(...pats);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sequences patterns like `seq`, but each pattern has a length, relative to the whole.
|
|
||||||
* This length can either be provided as a [length, pattern] pair, or inferred from
|
|
||||||
* the pattern's 'tactus', generally inferred by the mininotation.
|
|
||||||
* @return {Pattern}
|
|
||||||
* @example
|
|
||||||
* timecat([3,"e3"],[1, "g3"]).note()
|
|
||||||
* // the same as "e3@3 g3".note()
|
|
||||||
* @example
|
|
||||||
* timecat("bd sd cp","hh hh").sound()
|
|
||||||
* // the same as "bd sd cp hh hh".sound()
|
|
||||||
*/
|
|
||||||
export function timecat(...timepats) {
|
|
||||||
const findtactus = (x) => (Array.isArray(x) ? x : [x.tactus, x]);
|
|
||||||
timepats = timepats.map(findtactus);
|
|
||||||
if (timepats.length == 1) {
|
|
||||||
const result = reify(timepats[0][1]);
|
|
||||||
result.tactus = timepats[0][0];
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
|
|
||||||
let begin = Fraction(0);
|
|
||||||
const pats = [];
|
|
||||||
for (const [time, pat] of timepats) {
|
|
||||||
const end = begin.add(time);
|
|
||||||
pats.push(reify(pat)._compress(begin.div(total), end.div(total)));
|
|
||||||
begin = end;
|
|
||||||
}
|
|
||||||
const result = stack(...pats);
|
|
||||||
result.tactus = total;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Deprecated alias for `timecat` */
|
|
||||||
export const timeCat = timecat;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to arrange multiple patterns together over multiple cycles.
|
* Allows to arrange multiple patterns together over multiple cycles.
|
||||||
* Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use.
|
* Takes a variable number of arrays with two elements specifying the number of cycles and the pattern to use.
|
||||||
@ -1426,30 +1391,6 @@ export function sequence(...pats) {
|
|||||||
return fastcat(...pats);
|
return fastcat(...pats);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Concatenates patterns stepwise, according to their 'tactus'.
|
|
||||||
* Similar to `timecat`, but if an argument is a list, the whole pattern will be repeated for each element in the list.
|
|
||||||
*
|
|
||||||
* @return {Pattern}
|
|
||||||
* @example
|
|
||||||
* stepcat(["bd cp", "mt"], "bd").sound()
|
|
||||||
*/
|
|
||||||
export function stepcat(...groups) {
|
|
||||||
groups = groups.map((a) => (Array.isArray(a) ? a.map(reify) : [reify(a)]));
|
|
||||||
|
|
||||||
const cycles = lcm(...groups.map((x) => Fraction(x.length)));
|
|
||||||
|
|
||||||
let result = [];
|
|
||||||
for (let cycle = 0; cycle < cycles; ++cycle) {
|
|
||||||
result.push(...groups.map((x) => (x.length == 0 ? silence : x[cycle % x.length])));
|
|
||||||
}
|
|
||||||
result = result.filter((x) => x.tactus > 0);
|
|
||||||
const tactus = result.reduce((a, b) => a.add(b.tactus), Fraction(0));
|
|
||||||
result = timecat(...result);
|
|
||||||
result.tactus = tactus;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Like **cat**, but the items are crammed into one cycle.
|
/** Like **cat**, but the items are crammed into one cycle.
|
||||||
* @synonyms fastcat, sequence
|
* @synonyms fastcat, sequence
|
||||||
* @example
|
* @example
|
||||||
@ -1474,86 +1415,6 @@ function _sequenceCount(x) {
|
|||||||
return [reify(x), 1];
|
return [reify(x), 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Speeds a pattern up or down, to fit to the given metrical 'tactus'.
|
|
||||||
* @example
|
|
||||||
* s("bd sd cp").toTactus(4)
|
|
||||||
* // The same as s("{bd sd cp}%4")
|
|
||||||
*/
|
|
||||||
export const toTactus = register('toTactus', function (targetTactus, pat) {
|
|
||||||
return pat.fast(Fraction(targetTactus).div(pat.tactus));
|
|
||||||
});
|
|
||||||
|
|
||||||
export function _polymeterListSteps(steps, ...args) {
|
|
||||||
const seqs = args.map((a) => _sequenceCount(a));
|
|
||||||
if (seqs.length == 0) {
|
|
||||||
return silence;
|
|
||||||
}
|
|
||||||
if (steps == 0) {
|
|
||||||
steps = seqs[0][1];
|
|
||||||
}
|
|
||||||
const pats = [];
|
|
||||||
for (const seq of seqs) {
|
|
||||||
if (seq[1] == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (steps == seq[1]) {
|
|
||||||
pats.push(seq[0]);
|
|
||||||
} else {
|
|
||||||
pats.push(seq[0]._fast(Fraction(steps).div(Fraction(seq[1]))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stack(...pats);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aligns one or more given patterns to the given number of steps per cycle.
|
|
||||||
* This relies on patterns having coherent number of steps per cycle,
|
|
||||||
*
|
|
||||||
* @name polymeterSteps
|
|
||||||
* @param {number} steps how many items are placed in one cycle
|
|
||||||
* @param {any[]} patterns one or more patterns
|
|
||||||
* @example
|
|
||||||
* // the same as "{c d, e f g}%4"
|
|
||||||
* polymeterSteps(4, "c d", "e f g")
|
|
||||||
*/
|
|
||||||
export function polymeterSteps(steps, ...args) {
|
|
||||||
if (args.length == 0) {
|
|
||||||
return silence;
|
|
||||||
}
|
|
||||||
if (Array.isArray(args[0])) {
|
|
||||||
// Support old behaviour
|
|
||||||
return _polymeterListSteps(steps, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return polymeter(...args).toTactus(steps);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combines the given lists of patterns with the same pulse, creating polymeters when different sized sequences are used.
|
|
||||||
* @synonyms pm
|
|
||||||
* @example
|
|
||||||
* // The same as "{c eb g, c2 g2}"
|
|
||||||
* polymeter("c eb g", "c2 g2")
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function polymeter(...args) {
|
|
||||||
if (Array.isArray(args[0])) {
|
|
||||||
// Support old behaviour
|
|
||||||
return _polymeterListSteps(0, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length == 0) {
|
|
||||||
return silence;
|
|
||||||
}
|
|
||||||
const tactus = args[0].tactus;
|
|
||||||
const [head, ...tail] = args;
|
|
||||||
|
|
||||||
const result = stack(head, ...tail.map((pat) => pat._slow(pat.tactus.div(tactus))));
|
|
||||||
result.tactus = tactus;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const mask = curry((a, b) => reify(b).mask(a));
|
export const mask = curry((a, b) => reify(b).mask(a));
|
||||||
export const struct = curry((a, b) => reify(b).struct(a));
|
export const struct = curry((a, b) => reify(b).struct(a));
|
||||||
export const superimpose = curry((a, b) => reify(b).superimpose(...a));
|
export const superimpose = curry((a, b) => reify(b).superimpose(...a));
|
||||||
@ -2069,11 +1930,16 @@ export const late = register(
|
|||||||
export const zoom = register('zoom', function (s, e, pat) {
|
export const zoom = register('zoom', function (s, e, pat) {
|
||||||
e = Fraction(e);
|
e = Fraction(e);
|
||||||
s = Fraction(s);
|
s = Fraction(s);
|
||||||
|
if (s.gte(e)) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
const d = e.sub(s);
|
const d = e.sub(s);
|
||||||
|
const tactus = pat.tactus.mul(d);
|
||||||
return pat
|
return pat
|
||||||
.withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s)))
|
.withQuerySpan((span) => span.withCycle((t) => t.mul(d).add(s)))
|
||||||
.withHapSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
|
.withHapSpan((span) => span.withCycle((t) => t.sub(s).div(d)))
|
||||||
.splitQueries();
|
.splitQueries()
|
||||||
|
.setTactus(tactus);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a, pat) {
|
export const { zoomArc, zoomarc } = register(['zoomArc', 'zoomarc'], function (a, pat) {
|
||||||
@ -2487,6 +2353,250 @@ Pattern.prototype.tag = function (tag) {
|
|||||||
return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
|
return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// Tactus-related functions, i.e. ones that do stepwise
|
||||||
|
// transformations
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *EXPERIMENTAL* - Speeds a pattern up or down, to fit to the given metrical 'tactus'.
|
||||||
|
* @example
|
||||||
|
* s("bd sd cp").toTactus(4)
|
||||||
|
* // The same as s("{bd sd cp}%4")
|
||||||
|
*/
|
||||||
|
export const toTactus = register('toTactus', function (targetTactus, pat) {
|
||||||
|
if (pat.tactus.eq(0)) {
|
||||||
|
// avoid divide by zero..
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
return pat.fast(Fraction(targetTactus).div(pat.tactus));
|
||||||
|
});
|
||||||
|
|
||||||
|
export function _polymeterListSteps(steps, ...args) {
|
||||||
|
const seqs = args.map((a) => _sequenceCount(a));
|
||||||
|
if (seqs.length == 0) {
|
||||||
|
return silence;
|
||||||
|
}
|
||||||
|
if (steps == 0) {
|
||||||
|
steps = seqs[0][1];
|
||||||
|
}
|
||||||
|
const pats = [];
|
||||||
|
for (const seq of seqs) {
|
||||||
|
if (seq[1] == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (steps == seq[1]) {
|
||||||
|
pats.push(seq[0]);
|
||||||
|
} else {
|
||||||
|
pats.push(seq[0]._fast(Fraction(steps).div(Fraction(seq[1]))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stack(...pats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aligns one or more given patterns to the given number of steps per cycle.
|
||||||
|
* This relies on patterns having coherent number of steps per cycle,
|
||||||
|
*
|
||||||
|
* @name polymeterSteps
|
||||||
|
* @param {number} steps how many items are placed in one cycle
|
||||||
|
* @param {any[]} patterns one or more patterns
|
||||||
|
* @example
|
||||||
|
* // the same as "{c d, e f g}%4"
|
||||||
|
* polymeterSteps(4, "c d", "e f g")
|
||||||
|
*/
|
||||||
|
export function polymeterSteps(steps, ...args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
return silence;
|
||||||
|
}
|
||||||
|
if (Array.isArray(args[0])) {
|
||||||
|
// Support old behaviour
|
||||||
|
return _polymeterListSteps(steps, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return polymeter(...args).toTactus(steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *EXPERIMENTAL* - Combines the given lists of patterns with the same pulse, creating polymeters when different sized sequences are used.
|
||||||
|
* @synonyms pm
|
||||||
|
* @example
|
||||||
|
* // The same as "{c eb g, c2 g2}"
|
||||||
|
* polymeter("c eb g", "c2 g2")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function polymeter(...args) {
|
||||||
|
if (Array.isArray(args[0])) {
|
||||||
|
// Support old behaviour
|
||||||
|
return _polymeterListSteps(0, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
return silence;
|
||||||
|
}
|
||||||
|
const tactus = args[0].tactus;
|
||||||
|
const [head, ...tail] = args;
|
||||||
|
|
||||||
|
const result = stack(head, ...tail.map((pat) => pat._slow(pat.tactus.div(tactus))));
|
||||||
|
result.tactus = tactus;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sequences patterns like `seq`, but each pattern has a length, relative to the whole.
|
||||||
|
* This length can either be provided as a [length, pattern] pair, or inferred from
|
||||||
|
* the pattern's 'tactus', generally inferred by the mininotation.
|
||||||
|
* @return {Pattern}
|
||||||
|
* @example
|
||||||
|
* timecat([3,"e3"],[1, "g3"]).note()
|
||||||
|
* // the same as "e3@3 g3".note()
|
||||||
|
* @example
|
||||||
|
* timecat("bd sd cp","hh hh").sound()
|
||||||
|
* // the same as "bd sd cp hh hh".sound()
|
||||||
|
*/
|
||||||
|
export function timecat(...timepats) {
|
||||||
|
const findtactus = (x) => (Array.isArray(x) ? x : [x.tactus, x]);
|
||||||
|
timepats = timepats.map(findtactus);
|
||||||
|
if (timepats.length == 1) {
|
||||||
|
const result = reify(timepats[0][1]);
|
||||||
|
result.tactus = timepats[0][0];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = timepats.map((a) => a[0]).reduce((a, b) => a.add(b), Fraction(0));
|
||||||
|
let begin = Fraction(0);
|
||||||
|
const pats = [];
|
||||||
|
for (const [time, pat] of timepats) {
|
||||||
|
if (Fraction(time).eq(0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const end = begin.add(time);
|
||||||
|
pats.push(reify(pat)._compress(begin.div(total), end.div(total)));
|
||||||
|
begin = end;
|
||||||
|
}
|
||||||
|
const result = stack(...pats);
|
||||||
|
result.tactus = total;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deprecated alias for `timecat` */
|
||||||
|
export const timeCat = timecat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *EXPERIMENTAL* - Concatenates patterns stepwise, according to their 'tactus'.
|
||||||
|
* Similar to `timecat`, but if an argument is a list, the whole pattern will be repeated for each element in the list.
|
||||||
|
*
|
||||||
|
* @return {Pattern}
|
||||||
|
* @example
|
||||||
|
* stepcat(["bd cp", "mt"], "bd").sound()
|
||||||
|
*/
|
||||||
|
export function stepcat(...groups) {
|
||||||
|
groups = groups.map((a) => (Array.isArray(a) ? a.map(reify) : [reify(a)]));
|
||||||
|
|
||||||
|
const cycles = lcm(...groups.map((x) => Fraction(x.length)));
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
for (let cycle = 0; cycle < cycles; ++cycle) {
|
||||||
|
result.push(...groups.map((x) => (x.length == 0 ? silence : x[cycle % x.length])));
|
||||||
|
}
|
||||||
|
result = result.filter((x) => x.tactus > 0);
|
||||||
|
const tactus = result.reduce((a, b) => a.add(b.tactus), Fraction(0));
|
||||||
|
result = timecat(...result);
|
||||||
|
result.tactus = tactus;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *EXPERIMENTAL* - Retains the given number of steps in a pattern (and dropping the rest), according to its 'tactus'.
|
||||||
|
*/
|
||||||
|
export const stepwax = register('stepwax', function (i, pat) {
|
||||||
|
if (pat.tactus.lte(0)) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
i = Fraction(i);
|
||||||
|
if (i.eq(0)) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const flip = i < 0;
|
||||||
|
if (flip) {
|
||||||
|
i = i.abs();
|
||||||
|
}
|
||||||
|
const frac = i.div(pat.tactus);
|
||||||
|
if (frac.lte(0)) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
if (frac.gte(1)) {
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
if (flip) {
|
||||||
|
return pat.zoom(Fraction(1).sub(frac), 1);
|
||||||
|
}
|
||||||
|
return pat.zoom(0, frac);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *EXPERIMENTAL* - Removes the given number of steps from a pattern, according to its 'tactus'.
|
||||||
|
*/
|
||||||
|
export const stepwane = register('stepwane', function (i, pat) {
|
||||||
|
i = Fraction(i);
|
||||||
|
if (i.lt(0)) {
|
||||||
|
return pat.stepwax(Fraction(0).sub(pat.tactus.add(i)));
|
||||||
|
}
|
||||||
|
return pat.stepwax(pat.tactus.sub(i));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *EXPERIMENTAL*
|
||||||
|
*/
|
||||||
|
Pattern.prototype.taperlist = function (amount, times) {
|
||||||
|
const pat = this;
|
||||||
|
times = times - 1;
|
||||||
|
|
||||||
|
if (times === 0) {
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = [];
|
||||||
|
const reverse = amount > 0;
|
||||||
|
amount = Fraction(Math.abs(amount));
|
||||||
|
const start = pat.tactus.sub(amount.mul(Fraction(times))).max(Fraction(0));
|
||||||
|
|
||||||
|
for (let i = 0; i < times; ++i) {
|
||||||
|
list.push(pat.zoom(0, start.add(amount.mul(Fraction(i))).div(pat.tactus)));
|
||||||
|
}
|
||||||
|
list.push(pat);
|
||||||
|
if (reverse) {
|
||||||
|
list.reverse();
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
export const taperlist = (amount, times, pat) => pat.taperlist(amount, times);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *EXPERIMENTAL*
|
||||||
|
*/
|
||||||
|
export const steptaper = register('steptaper', function (amount, times, pat) {
|
||||||
|
const list = pat.taperlist(amount, times);
|
||||||
|
const result = timecat(...list);
|
||||||
|
result.tactus = list.reduce((a, b) => a.add(b.tactus), Fraction(0));
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *EXPERIMENTAL*
|
||||||
|
*/
|
||||||
|
Pattern.prototype.steptour = function (...many) {
|
||||||
|
return stepcat(
|
||||||
|
...[].concat(
|
||||||
|
...many.map((x, i) => [...many.slice(0, many.length - i), this, ...many.slice(many.length - i)]),
|
||||||
|
this,
|
||||||
|
...many,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const steptour = function (pat, ...many) {
|
||||||
|
return pat.steptour(...many);
|
||||||
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// Control-related functions, i.e. ones that manipulate patterns of
|
// Control-related functions, i.e. ones that manipulate patterns of
|
||||||
// objects
|
// objects
|
||||||
|
|||||||
@ -1138,4 +1138,28 @@ describe('Pattern', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('steptaper', () => {
|
||||||
|
it('can taper', () => {
|
||||||
|
expect(sameFirst(sequence(0, 1, 2, 3, 4).steptaper(1, 5), sequence(0, 1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 0, 1, 0)));
|
||||||
|
});
|
||||||
|
it('can taper backwards', () => {
|
||||||
|
expect(
|
||||||
|
sameFirst(sequence(0, 1, 2, 3, 4).steptaper(-1, 5), sequence(0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('wax and wane, left', () => {
|
||||||
|
it('can wax from the left', () => {
|
||||||
|
expect(sameFirst(sequence(0, 1, 2, 3, 4).stepwax(2), sequence(0, 1)));
|
||||||
|
});
|
||||||
|
it('can wane to the left', () => {
|
||||||
|
expect(sameFirst(sequence(0, 1, 2, 3, 4).stepwane(2), sequence(0, 1, 2)));
|
||||||
|
});
|
||||||
|
it('can wax from the right', () => {
|
||||||
|
expect(sameFirst(sequence(0, 1, 2, 3, 4).stepwax(-2), sequence(3, 4)));
|
||||||
|
});
|
||||||
|
it('can wane to the right', () => {
|
||||||
|
expect(sameFirst(sequence(0, 1, 2, 3, 4).stepwane(-2), sequence(2, 3, 4)));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user