Feature: tactus marking (#1021)

* rename `beat` option to `stackBy` to `repeat`
* fix parse error reporting
* rename `weight` to `tactus` (it might in the end be pulse, step, or tap)
* tactus marking with ^
* and add some tests
This commit is contained in:
Alex McLean 2024-03-27 12:06:05 +00:00 committed by GitHub
parent 2e8ae38d33
commit 2fd2bdba60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 205 additions and 134 deletions

View file

@ -6,6 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import * as krill from './krill-parser.js';
import * as strudel from '@strudel/core';
import Fraction, { lcm } from '@strudel/core/fraction.mjs';
const randOffset = 0.0003;
@ -88,45 +89,75 @@ export function patternifyAST(ast, code, onEnter, offset = 0) {
resolveReplications(ast);
const children = ast.source_.map((child) => enter(child)).map(applyOptions(ast, enter));
const alignment = ast.arguments_.alignment;
if (alignment === 'stack') {
return strudel.stack(...children);
}
if (alignment === 'polymeter_slowcat') {
const aligned = children.map((child) => child._slow(child.weight));
return strudel.stack(...aligned);
}
if (alignment === 'polymeter') {
// polymeter
const stepsPerCycle = ast.arguments_.stepsPerCycle
? enter(ast.arguments_.stepsPerCycle).fmap((x) => strudel.Fraction(x))
: strudel.pure(strudel.Fraction(children.length > 0 ? children[0].weight : 1));
const with_tactus = children.filter((child) => child.__tactus_source);
let pat;
switch (alignment) {
case 'stack': {
pat = strudel.stack(...children);
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
case 'polymeter_slowcat': {
pat = strudel.stack(...children.map((child) => child._slow(child.__weight)));
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
case 'polymeter': {
// polymeter
const stepsPerCycle = ast.arguments_.stepsPerCycle
? enter(ast.arguments_.stepsPerCycle).fmap((x) => strudel.Fraction(x))
: strudel.pure(strudel.Fraction(children.length > 0 ? children[0].__weight : 1));
const aligned = children.map((child) => child.fast(stepsPerCycle.fmap((x) => x.div(child.weight))));
return strudel.stack(...aligned);
const aligned = children.map((child) => child.fast(stepsPerCycle.fmap((x) => x.div(child.__weight))));
pat = strudel.stack(...aligned);
break;
}
case 'rand': {
pat = strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
if (with_tactus.length) {
pat.tactus = lcm(...with_tactus.map((x) => Fraction(x.tactus)));
}
break;
}
case 'feet': {
pat = strudel.fastcat(...children);
break;
}
default: {
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (weightedChildren) {
const weightSum = ast.source_.reduce(
(sum, child) => sum.add(child.options_?.weight || strudel.Fraction(1)),
strudel.Fraction(0),
);
pat = strudel.timeCat(
...ast.source_.map((child, i) => [child.options_?.weight || strudel.Fraction(1), children[i]]),
);
pat.__weight = weightSum; // for polymeter
pat.tactus = weightSum;
if (with_tactus.length) {
pat.tactus = pat.tactus.mul(lcm(...with_tactus.map((x) => Fraction(x.tactus))));
}
} else {
pat = strudel.sequence(...children);
pat.tactus = children.length;
}
if (ast.arguments_.tactus) {
pat.__tactus_source = true;
}
}
}
if (alignment === 'rand') {
return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children);
if (with_tactus.length) {
pat.__tactus_source = true;
}
if (alignment === 'feet') {
return strudel.fastcat(...children);
}
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (weightedChildren) {
const weightSum = ast.source_.reduce(
(sum, child) => sum.add(child.options_?.weight || strudel.Fraction(1)),
strudel.Fraction(0),
);
const pat = strudel.timeCat(
...ast.source_.map((child, i) => [child.options_?.weight || strudel.Fraction(1), children[i]]),
);
pat.weight = weightSum;
return pat;
}
const pat = strudel.sequence(...children);
pat.weight = children.length;
return pat;
}
case 'element': {
1;
return enter(ast.source_);
}
case 'atom': {
@ -166,7 +197,7 @@ export const getLeafLocation = (code, leaf, globalOffset = 0) => {
};
// takes quoted mini string, returns ast
export const mini2ast = (code, start, userCode) => {
export const mini2ast = (code, start = 0, userCode = code) => {
try {
return krill.parse(code);
} catch (error) {