From 0a3694fb825c2dfc08fa77502eb0c59db8729002 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Sun, 21 Apr 2024 21:17:07 +0100 Subject: [PATCH] Stepwise functions from Tidal (#1060) * rename new stepwise functions to match tidal, adding s_expand and s_contract * created a `stepJoin` for stepwise patternification --- packages/core/fraction.mjs | 4 + packages/core/pattern.mjs | 200 +++++++++++++++------- packages/core/test/pattern.test.mjs | 38 ++-- packages/core/util.mjs | 30 ++++ test/__snapshots__/examples.test.mjs.snap | 150 ++++++++++++++++ 5 files changed, 344 insertions(+), 78 deletions(-) diff --git a/packages/core/fraction.mjs b/packages/core/fraction.mjs index 43dc84ef7..ce614246d 100644 --- a/packages/core/fraction.mjs +++ b/packages/core/fraction.mjs @@ -47,6 +47,10 @@ Fraction.prototype.eq = function (other) { return this.compare(other) == 0; }; +Fraction.prototype.ne = function (other) { + return this.compare(other) != 0; +}; + Fraction.prototype.max = function (other) { return this.gt(other) ? this : other; }; diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index 4762ab85d..8f2c4362e 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -10,7 +10,18 @@ import Hap from './hap.mjs'; import State from './state.mjs'; import { unionWithObj } from './value.mjs'; -import { compose, removeUndefineds, flatten, id, listRange, curry, _mod, numeralArgs, parseNumeral } from './util.mjs'; +import { + uniqsortr, + removeUndefineds, + flatten, + id, + listRange, + curry, + _mod, + numeralArgs, + parseNumeral, + pairs, +} from './util.mjs'; import drawLine from './drawLine.mjs'; import { logger } from './logger.mjs'; @@ -48,6 +59,10 @@ export class Pattern { return this; } + withTactus(f) { + return new Pattern(this.query, this.tactus === undefined ? undefined : f(this.tactus)); + } + ////////////////////////////////////////////////////////////////////// // Haskell-style functor, applicative and monadic operations @@ -1137,24 +1152,24 @@ function _composeOp(a, b, func) { export const polyrhythm = stack; export const pr = stack; -export const pm = polymeter; +export const pm = s_polymeter; // methods that create patterns, which are added to patternified Pattern methods // TODO: remove? this is only used in old transpiler (shapeshifter) -Pattern.prototype.factories = { - pure, - stack, - slowcat, - fastcat, - cat, - timecat, - sequence, - seq, - polymeter, - pm, - polyrhythm, - pr, -}; +// Pattern.prototype.factories = { +// pure, +// stack, +// slowcat, +// fastcat, +// cat, +// timecat, +// sequence, +// seq, +// polymeter, +// pm, +// polyrhythm, +// pr, +// }; // the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts) // Elemental patterns @@ -1254,14 +1269,14 @@ function _stackWith(func, pats) { export function stackLeft(...pats) { return _stackWith( - (tactus, pats) => pats.map((pat) => (pat.tactus.eq(tactus) ? pat : timecat(pat, gap(tactus.sub(pat.tactus))))), + (tactus, pats) => pats.map((pat) => (pat.tactus.eq(tactus) ? pat : s_cat(pat, gap(tactus.sub(pat.tactus))))), pats, ); } export function stackRight(...pats) { return _stackWith( - (tactus, pats) => pats.map((pat) => (pat.tactus.eq(tactus) ? pat : timecat(gap(tactus.sub(pat.tactus)), pat))), + (tactus, pats) => pats.map((pat) => (pat.tactus.eq(tactus) ? pat : s_cat(gap(tactus.sub(pat.tactus)), pat))), pats, ); } @@ -1274,7 +1289,7 @@ export function stackCentre(...pats) { return pat; } const g = gap(tactus.sub(pat.tactus).div(2)); - return timecat(g, pat, g); + return s_cat(g, pat, g); }), pats, ); @@ -1288,7 +1303,7 @@ export function stackBy(by, ...pats) { left: stackLeft, right: stackRight, expand: stack, - repeat: (...args) => polymeterSteps(tactus, ...args), + repeat: (...args) => s_polymeterSteps(tactus, ...args), }; return by .inhabit(lookup) @@ -1374,7 +1389,7 @@ export function cat(...pats) { export function arrange(...sections) { const total = sections.reduce((sum, [cycles]) => sum + cycles, 0); sections = sections.map(([cycles, section]) => [cycles, section.fast(cycles)]); - return timecat(...sections).slow(total); + return s_cat(...sections).slow(total); } export function fastcat(...pats) { @@ -1454,7 +1469,7 @@ export const func = curry((a, b) => reify(b).func(a)); * @noAutocomplete * */ -export function register(name, func, patternify = true, preserveTactus = false) { +export function register(name, func, patternify = true, preserveTactus = false, join = (x) => x.innerJoin()) { if (Array.isArray(name)) { const result = {}; for (const name_item of name) { @@ -1491,7 +1506,7 @@ export function register(name, func, patternify = true, preserveTactus = false) return func(...args, pat); }; mapFn = curry(mapFn, null, arity - 1); - result = right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn)).innerJoin(); + result = join(right.reduce((acc, p) => acc.appLeft(p), left.fmap(mapFn))); } } if (preserveTactus) { @@ -1535,6 +1550,11 @@ export function register(name, func, patternify = true, preserveTactus = false) return curry(pfunc, null, arity); } +// Like register, but defaults to stepJoin +function stepRegister(name, func, patternify = true, preserveTactus = false, join = (x) => x.stepJoin()) { + return register(name, func, patternify, preserveTactus, join); +} + ////////////////////////////////////////////////////////////////////// // Numerical transformations @@ -2375,13 +2395,62 @@ Pattern.prototype.tag = function (tag) { // Tactus-related functions, i.e. ones that do stepwise // transformations +Pattern.prototype.stepJoin = function () { + const pp = this; + const first_t = s_cat(..._retime(_slices(pp.queryArc(0, 1)))).tactus; + const q = function (state) { + const shifted = pp.early(state.span.begin.sam()); + const haps = shifted.query(state.setSpan(new TimeSpan(Fraction(0), Fraction(1)))); + const pat = s_cat(..._retime(_slices(haps))); + return pat.query(state); + }; + return new Pattern(q, first_t); +}; + +export function _retime(timedHaps) { + const occupied_perc = timedHaps.filter((t, pat) => pat.tactus != undefined).reduce((a, b) => a.add(b), Fraction(0)); + const occupied_tactus = removeUndefineds(timedHaps.map((t, pat) => pat.tactus)).reduce( + (a, b) => a.add(b), + Fraction(0), + ); + const total_tactus = occupied_perc.eq(0) ? 0 : occupied_tactus.div(occupied_perc); + function adjust(dur, pat) { + if (pat.tactus === undefined) { + return [dur.mul(total_tactus), pat]; + } + return [pat.tactus, pat]; + } + return timedHaps.map((x) => adjust(...x)); +} + +export function _slices(haps) { + // slices evs = map (\s -> ((snd s - fst s), stack $ map value $ fit s evs)) + // $ pairs $ sort $ nubOrd $ 0:1:concatMap (\ev -> start (part ev):stop (part ev):[]) evs + const breakpoints = flatten(haps.map((hap) => [hap.part.begin, hap.part.end])); + const unique = uniqsortr([Fraction(0), Fraction(1), ...breakpoints]); + const slicespans = pairs(unique); + return slicespans.map((s) => [s[1].sub(s[0]), stack(..._fitslice(new TimeSpan(...s), haps).map((x) => x.value))]); +} + +export function _fitslice(span, haps) { + return removeUndefineds(haps.map((hap) => _match(span, hap))); +} + +export function _match(span, hap_p) { + const subspan = span.intersection(hap_p.part); + if (subspan == undefined) { + return undefined; + } + return new Hap(hap_p.whole, subspan, hap_p.value, hap_p.context); +} + /** - * *EXPERIMENTAL* - Speeds a pattern up or down, to fit to the given metrical 'tactus'. + * *EXPERIMENTAL* - Speeds a pattern up or down, to fit to the given number of steps per cycle (aka tactus). * @example - * s("bd sd cp").toTactus(4) + * s("bd sd cp").steps(4) * // The same as s("{bd sd cp}%4") */ -export const toTactus = register('toTactus', function (targetTactus, pat) { +export const steps = register('steps', function (targetTactus, pat) { if (pat.tactus.eq(0)) { // avoid divide by zero.. return nothing; @@ -2415,14 +2484,14 @@ export function _polymeterListSteps(steps, ...args) { * 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 + * @name s_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") + * s_polymeterSteps(4, "c d", "e f g") */ -export function polymeterSteps(steps, ...args) { +export function s_polymeterSteps(steps, ...args) { if (args.length == 0) { return silence; } @@ -2431,7 +2500,7 @@ export function polymeterSteps(steps, ...args) { return _polymeterListSteps(steps, ...args); } - return polymeter(...args).toTactus(steps); + return s_polymeter(...args).steps(steps); } /** @@ -2439,10 +2508,10 @@ export function polymeterSteps(steps, ...args) { * @synonyms pm * @example * // The same as "{c eb g, c2 g2}" - * polymeter("c eb g", "c2 g2") + * s_polymeter("c eb g", "c2 g2") * */ -export function polymeter(...args) { +export function s_polymeter(...args) { if (Array.isArray(args[0])) { // Support old behaviour return _polymeterListSteps(0, ...args); @@ -2461,16 +2530,16 @@ export function polymeter(...args) { /** 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. + * the pattern's 'tactus', generally inferred by the mininotation. Has the alias `timecat`. * @return {Pattern} * @example - * timecat([3,"e3"],[1, "g3"]).note() + * s_cat([3,"e3"],[1, "g3"]).note() * // the same as "e3@3 g3".note() * @example - * timecat("bd sd cp","hh hh").sound() + * s_cat("bd sd cp","hh hh").sound() * // the same as "bd sd cp hh hh".sound() */ -export function timecat(...timepats) { +export function s_cat(...timepats) { const findtactus = (x) => (Array.isArray(x) ? x : [x.tactus, x]); timepats = timepats.map(findtactus); if (timepats.length == 1) { @@ -2495,18 +2564,19 @@ export function timecat(...timepats) { return result; } -/** Deprecated alias for `timecat` */ -export const timeCat = timecat; +/** Aliases for `s_cat` */ +export const timecat = s_cat; +export const timeCat = s_cat; /** * *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. + * Similar to `s_cat`, but if an argument is a list, the whole pattern will alternate between the elements in the list. * * @return {Pattern} * @example - * stepcat(["bd cp", "mt"], "bd").sound() + * s_alt(["bd cp", "mt"], "bd").sound() */ -export function stepcat(...groups) { +export function s_alt(...groups) { groups = groups.map((a) => (Array.isArray(a) ? a.map(reify) : [reify(a)])); const cycles = lcm(...groups.map((x) => Fraction(x.length))); @@ -2517,7 +2587,7 @@ export function stepcat(...groups) { } result = result.filter((x) => x.tactus > 0); const tactus = result.reduce((a, b) => a.add(b.tactus), Fraction(0)); - result = timecat(...result); + result = s_cat(...result); result.tactus = tactus; return result; } @@ -2525,7 +2595,7 @@ export function stepcat(...groups) { /** * *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) { +export const s_add = stepRegister('s_add', function (i, pat) { if (pat.tactus.lte(0)) { return nothing; } @@ -2553,18 +2623,26 @@ export const stepwax = register('stepwax', function (i, pat) { /** * *EXPERIMENTAL* - Removes the given number of steps from a pattern, according to its 'tactus'. */ -export const stepwane = register('stepwane', function (i, pat) { +export const s_sub = stepRegister('s_sub', function (i, pat) { i = Fraction(i); if (i.lt(0)) { - return pat.stepwax(Fraction(0).sub(pat.tactus.add(i))); + return pat.s_add(Fraction(0).sub(pat.tactus.add(i))); } - return pat.stepwax(pat.tactus.sub(i)); + return pat.s_add(pat.tactus.sub(i)); +}); + +export const s_expand = stepRegister('s_expand', function (factor, pat) { + return pat.withTactus((t) => t.mul(Fraction(factor))); +}); + +export const s_contract = stepRegister('s_contract', function (factor, pat) { + return pat.withTactus((t) => t.div(Fraction(factor))); }); /** * *EXPERIMENTAL* */ -Pattern.prototype.taperlist = function (amount, times) { +Pattern.prototype.s_taperlist = function (amount, times) { const pat = this; times = times - 1; @@ -2586,23 +2664,29 @@ Pattern.prototype.taperlist = function (amount, times) { } return list; }; -export const taperlist = (amount, times, pat) => pat.taperlist(amount, times); +export const s_taperlist = (amount, times, pat) => pat.s_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; -}); +export const s_taper = register( + 's_taper', + function (amount, times, pat) { + const list = pat.s_taperlist(amount, times); + const result = s_cat(...list); + result.tactus = list.reduce((a, b) => a.add(b.tactus), Fraction(0)); + return result; + }, + true, + false, + (x) => x.stepJoin(), +); /** * *EXPERIMENTAL* */ -Pattern.prototype.steptour = function (...many) { - return stepcat( +Pattern.prototype.s_tour = function (...many) { + return s_cat( ...[].concat( ...many.map((x, i) => [...many.slice(0, many.length - i), this, ...many.slice(many.length - i)]), this, @@ -2611,8 +2695,8 @@ Pattern.prototype.steptour = function (...many) { ); }; -export const steptour = function (pat, ...many) { - return pat.steptour(...many); +export const s_tour = function (pat, ...many) { + return pat.s_tour(...many); }; ////////////////////////////////////////////////////////////////////// diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 1ef90f730..b9848885a 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -21,8 +21,8 @@ import { cat, sequence, palindrome, - polymeter, - polymeterSteps, + s_polymeter, + s_polymeterSteps, polyrhythm, silence, fast, @@ -603,18 +603,18 @@ describe('Pattern', () => { ); }); }); - describe('polymeter()', () => { + describe('s_polymeter()', () => { it('Can layer up cycles, stepwise, with lists', () => { - expect(polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual( + expect(s_polymeterSteps(3, ['d', 'e']).firstCycle()).toStrictEqual( fastcat(pure('d'), pure('e'), pure('d')).firstCycle(), ); - expect(polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual( + expect(s_polymeter(['a', 'b', 'c'], ['d', 'e']).fast(2).firstCycle()).toStrictEqual( stack(sequence('a', 'b', 'c', 'a', 'b', 'c'), sequence('d', 'e', 'd', 'e', 'd', 'e')).firstCycle(), ); }); it('Can layer up cycles, stepwise, with weighted patterns', () => { - sameFirst(polymeterSteps(3, sequence('a', 'b')).fast(2), sequence('a', 'b', 'a', 'b', 'a', 'b')); + sameFirst(s_polymeterSteps(3, sequence('a', 'b')).fast(2), sequence('a', 'b', 'a', 'b', 'a', 'b')); }); }); @@ -1138,28 +1138,26 @@ describe('Pattern', () => { ); }); }); - describe('steptaper', () => { + describe('s_taper', () => { 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))); + expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(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)), - ); + expect(sameFirst(sequence(0, 1, 2, 3, 4).s_taper(-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))); + describe('s_add and s_sub, left', () => { + it('can add from the left', () => { + expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(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 sub to the left', () => { + expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(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 add from the right', () => { + expect(sameFirst(sequence(0, 1, 2, 3, 4).s_add(-2), sequence(3, 4))); }); - it('can wane to the right', () => { - expect(sameFirst(sequence(0, 1, 2, 3, 4).stepwane(-2), sequence(2, 3, 4))); + it('can sub to the right', () => { + expect(sameFirst(sequence(0, 1, 2, 3, 4).s_sub(-2), sequence(2, 3, 4))); }); }); }); diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 2c7b3d712..ff59cc90c 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -231,6 +231,14 @@ export const splitAt = function (index, value) { export const zipWith = (f, xs, ys) => xs.map((n, i) => f(n, ys[i])); +export const pairs = function (xs) { + const result = []; + for (let i = 0; i < xs.length - 1; ++i) { + result.push([xs[i], xs[i + 1]]); + } + return result; +}; + export const clamp = (num, min, max) => Math.min(Math.max(num, min), max); /* solmization, not used yet */ @@ -289,6 +297,28 @@ export const sol2note = (n, notation = 'letters') => { return note + oct; }; +// Remove duplicates from list +export function uniq(a) { + var seen = {}; + return a.filter(function (item) { + return seen.hasOwn(item) ? false : (seen[item] = true); + }); +} + +// Remove duplicates from list, sorting in the process. Mutates argument! +export function uniqsort(a) { + return a.sort().filter(function (item, pos, ary) { + return !pos || item != ary[pos - 1]; + }); +} + +// rational version +export function uniqsortr(a) { + return a.sort().filter(function (item, pos, ary) { + return !pos || item.ne(ary[pos - 1]); + }); +} + // code hashing helpers export function unicodeToBase64(text) { diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 94c390b62..30db05a20 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -5972,6 +5972,135 @@ exports[`runs examples > example "s" example index 1 1`] = ` ] `; +exports[`runs examples > example "s_alt" example index 0 1`] = ` +[ + "[ 0/1 → 1/5 | s:bd ]", + "[ 1/5 → 2/5 | s:cp ]", + "[ 2/5 → 3/5 | s:bd ]", + "[ 3/5 → 4/5 | s:mt ]", + "[ 4/5 → 1/1 | s:bd ]", + "[ 1/1 → 6/5 | s:bd ]", + "[ 6/5 → 7/5 | s:cp ]", + "[ 7/5 → 8/5 | s:bd ]", + "[ 8/5 → 9/5 | s:mt ]", + "[ 9/5 → 2/1 | s:bd ]", + "[ 2/1 → 11/5 | s:bd ]", + "[ 11/5 → 12/5 | s:cp ]", + "[ 12/5 → 13/5 | s:bd ]", + "[ 13/5 → 14/5 | s:mt ]", + "[ 14/5 → 3/1 | s:bd ]", + "[ 3/1 → 16/5 | s:bd ]", + "[ 16/5 → 17/5 | s:cp ]", + "[ 17/5 → 18/5 | s:bd ]", + "[ 18/5 → 19/5 | s:mt ]", + "[ 19/5 → 4/1 | s:bd ]", +] +`; + +exports[`runs examples > example "s_cat" example index 0 1`] = ` +[ + "[ 0/1 → 3/4 | note:e3 ]", + "[ 3/4 → 1/1 | note:g3 ]", + "[ 1/1 → 7/4 | note:e3 ]", + "[ 7/4 → 2/1 | note:g3 ]", + "[ 2/1 → 11/4 | note:e3 ]", + "[ 11/4 → 3/1 | note:g3 ]", + "[ 3/1 → 15/4 | note:e3 ]", + "[ 15/4 → 4/1 | note:g3 ]", +] +`; + +exports[`runs examples > example "s_cat" example index 1 1`] = ` +[ + "[ 0/1 → 1/5 | s:bd ]", + "[ 1/5 → 2/5 | s:sd ]", + "[ 2/5 → 3/5 | s:cp ]", + "[ 3/5 → 4/5 | s:hh ]", + "[ 4/5 → 1/1 | s:hh ]", + "[ 1/1 → 6/5 | s:bd ]", + "[ 6/5 → 7/5 | s:sd ]", + "[ 7/5 → 8/5 | s:cp ]", + "[ 8/5 → 9/5 | s:hh ]", + "[ 9/5 → 2/1 | s:hh ]", + "[ 2/1 → 11/5 | s:bd ]", + "[ 11/5 → 12/5 | s:sd ]", + "[ 12/5 → 13/5 | s:cp ]", + "[ 13/5 → 14/5 | s:hh ]", + "[ 14/5 → 3/1 | s:hh ]", + "[ 3/1 → 16/5 | s:bd ]", + "[ 16/5 → 17/5 | s:sd ]", + "[ 17/5 → 18/5 | s:cp ]", + "[ 18/5 → 19/5 | s:hh ]", + "[ 19/5 → 4/1 | s:hh ]", +] +`; + +exports[`runs examples > example "s_polymeter" example index 0 1`] = ` +[ + "[ 0/1 → 1/3 | c ]", + "[ 0/1 → 1/3 | c2 ]", + "[ 1/3 → 2/3 | eb ]", + "[ 1/3 → 2/3 | g2 ]", + "[ 2/3 → 1/1 | g ]", + "[ 2/3 → 1/1 | c2 ]", + "[ 1/1 → 4/3 | c ]", + "[ 1/1 → 4/3 | g2 ]", + "[ 4/3 → 5/3 | eb ]", + "[ 4/3 → 5/3 | c2 ]", + "[ 5/3 → 2/1 | g ]", + "[ 5/3 → 2/1 | g2 ]", + "[ 2/1 → 7/3 | c ]", + "[ 2/1 → 7/3 | c2 ]", + "[ 7/3 → 8/3 | eb ]", + "[ 7/3 → 8/3 | g2 ]", + "[ 8/3 → 3/1 | g ]", + "[ 8/3 → 3/1 | c2 ]", + "[ 3/1 → 10/3 | c ]", + "[ 3/1 → 10/3 | g2 ]", + "[ 10/3 → 11/3 | eb ]", + "[ 10/3 → 11/3 | c2 ]", + "[ 11/3 → 4/1 | g ]", + "[ 11/3 → 4/1 | g2 ]", +] +`; + +exports[`runs examples > example "s_polymeterSteps" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | c ]", + "[ 0/1 → 1/4 | e ]", + "[ 1/4 → 1/2 | d ]", + "[ 1/4 → 1/2 | f ]", + "[ 1/2 → 3/4 | c ]", + "[ 1/2 → 3/4 | g ]", + "[ 3/4 → 1/1 | d ]", + "[ 3/4 → 1/1 | e ]", + "[ 1/1 → 5/4 | c ]", + "[ 1/1 → 5/4 | f ]", + "[ 5/4 → 3/2 | d ]", + "[ 5/4 → 3/2 | g ]", + "[ 3/2 → 7/4 | c ]", + "[ 3/2 → 7/4 | e ]", + "[ 7/4 → 2/1 | d ]", + "[ 7/4 → 2/1 | f ]", + "[ 2/1 → 9/4 | c ]", + "[ 2/1 → 9/4 | g ]", + "[ 9/4 → 5/2 | d ]", + "[ 9/4 → 5/2 | e ]", + "[ 5/2 → 11/4 | c ]", + "[ 5/2 → 11/4 | f ]", + "[ 11/4 → 3/1 | d ]", + "[ 11/4 → 3/1 | g ]", + "[ 3/1 → 13/4 | c ]", + "[ 3/1 → 13/4 | e ]", + "[ 13/4 → 7/2 | d ]", + "[ 13/4 → 7/2 | f ]", + "[ 7/2 → 15/4 | c ]", + "[ 7/2 → 15/4 | g ]", + "[ 15/4 → 4/1 | d ]", + "[ 15/4 → 4/1 | e ]", +] +`; + exports[`runs examples > example "samples" example index 0 1`] = ` [ "[ 0/1 → 1/4 | s:bd ]", @@ -7112,6 +7241,27 @@ exports[`runs examples > example "stepcat" example index 0 1`] = ` ] `; +exports[`runs examples > example "steps" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd ]", + "[ 1/4 → 1/2 | s:sd ]", + "[ 1/2 → 3/4 | s:cp ]", + "[ 3/4 → 1/1 | s:bd ]", + "[ 1/1 → 5/4 | s:sd ]", + "[ 5/4 → 3/2 | s:cp ]", + "[ 3/2 → 7/4 | s:bd ]", + "[ 7/4 → 2/1 | s:sd ]", + "[ 2/1 → 9/4 | s:cp ]", + "[ 9/4 → 5/2 | s:bd ]", + "[ 5/2 → 11/4 | s:sd ]", + "[ 11/4 → 3/1 | s:cp ]", + "[ 3/1 → 13/4 | s:bd ]", + "[ 13/4 → 7/2 | s:sd ]", + "[ 7/2 → 15/4 | s:cp ]", + "[ 15/4 → 4/1 | s:bd ]", +] +`; + exports[`runs examples > example "striate" example index 0 1`] = ` [ "[ 0/1 → 1/6 | s:numbers n:0 begin:0 end:0.16666666666666666 ]",