Stepwise functions from Tidal (#1060)

* rename new stepwise functions to match tidal, adding s_expand and s_contract
* created a `stepJoin` for stepwise patternification
This commit is contained in:
Alex McLean 2024-04-21 21:17:07 +01:00 committed by GitHub
parent a194180130
commit 0a3694fb82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 344 additions and 78 deletions

View File

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

View File

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

View File

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

View File

@ -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) {

View File

@ -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 ]",