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:
parent
2e8ae38d33
commit
2fd2bdba60
8 changed files with 205 additions and 134 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue