mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 05:38:35 +00:00
Tidy parser, implement polymeters (#336)
* rename parser stuff to be more explicit and fit with tidal concepts. qualify all strudel function calls
* Add {,}%n polymeter support, with a few tests
This commit is contained in:
parent
c0a7173ca4
commit
8bb460701f
File diff suppressed because it is too large
Load Diff
@ -5,10 +5,10 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
// Some terminology:
|
||||
// a sequence = a serie of elements placed between quotes
|
||||
// a stack = a serie of vertically aligned slices sharing the same overall length
|
||||
// a slice = a serie of horizontally aligned elements
|
||||
// a choose = a serie of elements, one of which is chosen at random
|
||||
// mini(notation) = a series of elements placed between quotes
|
||||
// a stack = a series of vertically aligned slices sharing the same overall length
|
||||
// a sequence = a series of horizontally aligned elements
|
||||
// a choose = a series of elements, one of which is chosen at random
|
||||
|
||||
|
||||
{
|
||||
@ -93,18 +93,26 @@ step_char = [0-9a-zA-Z~] / "-" / "#" / "." / "^" / "_" / ":"
|
||||
step = ws chars:step_char+ ws { return chars.join("") }
|
||||
|
||||
// define a sub cycle e.g. [1 2, 3 [4]]
|
||||
sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s}
|
||||
sub_cycle = ws "[" ws s:stack_or_choose ws "]" ws { return s }
|
||||
|
||||
// define a timeline e.g <1 3 [3 5]>. We simply defer to a stack and change the alignement
|
||||
timeline = ws "<" ws sc:single_cycle ws ">" ws
|
||||
{ sc.arguments_.alignment = "t"; return sc;}
|
||||
// define a polymeter e.g. {1 2, 3 4 5}
|
||||
polymeter = ws "{" ws s:polymeter_stack ws "}" stepsPerCycle:polymeter_steps? ws
|
||||
{ s.arguments_.stepsPerCycle = stepsPerCycle ; return s; }
|
||||
|
||||
polymeter_steps = "%"a:number
|
||||
{ return a }
|
||||
|
||||
// define a step-per-cycle timeline e.g <1 3 [3 5]>. We simply defer to a sequence and
|
||||
// change the alignment to slowcat
|
||||
slow_sequence = ws "<" ws s:sequence ws ">" ws
|
||||
{ s.arguments_.alignment = 'slowcat'; return s; }
|
||||
|
||||
// a slice is either a single step or a sub cycle
|
||||
slice = step / sub_cycle / timeline
|
||||
slice = step / sub_cycle / polymeter / slow_sequence
|
||||
|
||||
// slice modifier affects the timing/size of a slice (e.g. [a b c]@3)
|
||||
// at this point, we assume we can represent them as regular sequence operators
|
||||
slice_modifier = slice_weight / slice_bjorklund / slice_slow / slice_fast / slice_fixed_step / slice_replicate / slice_degrade
|
||||
slice_modifier = slice_weight / slice_bjorklund / slice_slow / slice_fast / slice_replicate / slice_degrade
|
||||
|
||||
slice_weight = "@" a:number
|
||||
{ return { weight: a} }
|
||||
@ -121,9 +129,6 @@ slice_slow = "/"a:number
|
||||
slice_fast = "*"a:number
|
||||
{ return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'fast' } } } }
|
||||
|
||||
slice_fixed_step = "%"a:number
|
||||
{ return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } }
|
||||
|
||||
slice_degrade = "?"a:number?
|
||||
{ return { operator : { type_: "degradeBy", arguments_ :{ amount:(a? a : 0.5) } } } }
|
||||
|
||||
@ -131,35 +136,42 @@ slice_degrade = "?"a:number?
|
||||
slice_with_modifier = s:slice o:slice_modifier?
|
||||
{ return new ElementStub(s, o);}
|
||||
|
||||
// a single cycle is a combination of one or more successive slices (as an array). If we
|
||||
// have only one element, we skip the array and return the element itself
|
||||
single_cycle = s:(slice_with_modifier)+
|
||||
{ return new PatternStub(s,"h"); }
|
||||
// a sequence is a combination of one or more successive slices (as an array)
|
||||
sequence = s:(slice_with_modifier)+
|
||||
{ return new PatternStub(s, 'fastcat'); }
|
||||
|
||||
// a stack is a serie of vertically aligned single cycles, separated by a comma
|
||||
stack_tail = tail:(comma @single_cycle)+
|
||||
{ return { alignment: 'v', list: tail }; }
|
||||
// a stack is a series of vertically aligned sequence, separated by a comma
|
||||
stack_tail = tail:(comma @sequence)+
|
||||
{ return { alignment: 'stack', list: tail }; }
|
||||
|
||||
// a choose is a serie of pipe-separated single cycles, one of which is chosen
|
||||
// at random each time through the pattern
|
||||
choose_tail = tail:(pipe @single_cycle)+
|
||||
{ return { alignment: 'r', list: tail }; }
|
||||
// a choose is a series of pipe-separated sequence, one of which is
|
||||
// chosen at random, each cycle
|
||||
choose_tail = tail:(pipe @sequence)+
|
||||
{ return { alignment: 'rand', list: tail }; }
|
||||
|
||||
// if the stack contains only one element, we don't create a stack but return the
|
||||
// underlying element
|
||||
stack_or_choose = head:single_cycle tail:(stack_tail / choose_tail)?
|
||||
stack_or_choose = head:sequence tail:(stack_tail / choose_tail)?
|
||||
{ if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment); } else { return head; } }
|
||||
|
||||
// a sequence is a quoted stack
|
||||
sequence = ws quote sc:stack_or_choose quote
|
||||
polymeter_stack = head:sequence tail:stack_tail?
|
||||
{ return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); }
|
||||
|
||||
|
||||
// Mini-notation innards ends
|
||||
// ---------->8---------->8---------->8---------->8---------->8----------
|
||||
// Experimental haskellish parser begins
|
||||
|
||||
// mini-notation = a quoted stack
|
||||
mini = ws quote sc:stack_or_choose quote
|
||||
{ return sc; }
|
||||
|
||||
// ------------------ operators ---------------------------
|
||||
|
||||
operator = scale / slow / fast / target / bjorklund / struct / rotR / rotL
|
||||
|
||||
struct = "struct" ws s:sequence_or_operator
|
||||
{ return { name: "struct", args: { sequence:s }}}
|
||||
struct = "struct" ws s:mini_or_operator
|
||||
{ return { name: "struct", args: { mini:s }}}
|
||||
|
||||
target = "target" ws quote s:step quote
|
||||
{ return { name: "target", args : { name:s}}}
|
||||
@ -189,27 +201,27 @@ comment = '//' p:([^\n]*)
|
||||
group_operator = cat
|
||||
|
||||
// cat is another form of timeline
|
||||
cat = "cat" ws "[" ws s:sequence_or_operator ss:(comma v:sequence_or_operator { return v})* ws "]"
|
||||
{ ss.unshift(s); return new PatternStub(ss,"t"); }
|
||||
cat = "cat" ws "[" ws s:mini_or_operator ss:(comma v:mini_or_operator { return v})* ws "]"
|
||||
{ ss.unshift(s); return new PatternStub(ss, 'slowcat'); }
|
||||
|
||||
// ------------------ high level sequence ---------------------------
|
||||
// ------------------ high level mini ---------------------------
|
||||
|
||||
sequence_or_group =
|
||||
mini_or_group =
|
||||
group_operator /
|
||||
sequence
|
||||
mini
|
||||
|
||||
sequence_or_operator =
|
||||
sg:sequence_or_group ws (comment)*
|
||||
mini_or_operator =
|
||||
sg:mini_or_group ws (comment)*
|
||||
{return sg}
|
||||
/ o:operator ws "$" ws soc:sequence_or_operator
|
||||
/ o:operator ws "$" ws soc:mini_or_operator
|
||||
{ return new OperatorStub(o.name,o.args,soc)}
|
||||
|
||||
sequ_or_operator_or_comment =
|
||||
sc: sequence_or_operator
|
||||
sc: mini_or_operator
|
||||
{ return sc }
|
||||
/ comment
|
||||
|
||||
sequence_definition = s:sequ_or_operator_or_comment
|
||||
mini_definition = s:sequ_or_operator_or_comment
|
||||
|
||||
// ---------------------- statements ----------------------------
|
||||
|
||||
@ -227,4 +239,4 @@ hush = "hush"
|
||||
|
||||
// ---------------------- statements ----------------------------
|
||||
|
||||
statement = sequence_definition / command
|
||||
statement = mini_definition / command
|
||||
|
||||
@ -7,8 +7,6 @@ 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.cycles/core';
|
||||
|
||||
const { pure, Fraction, stack, slowcat, sequence, timeCat, silence, reify } = strudel;
|
||||
|
||||
/* var _seedState = 0;
|
||||
const randOffset = 0.0002;
|
||||
|
||||
@ -28,7 +26,7 @@ const applyOptions = (parent) => (pat, i) => {
|
||||
if (!legalTypes.includes(type)) {
|
||||
throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`);
|
||||
}
|
||||
return reify(pat)[type](amount);
|
||||
return strudel.reify(pat)[type](amount);
|
||||
}
|
||||
case 'bjorklund':
|
||||
return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation);
|
||||
@ -48,12 +46,12 @@ const applyOptions = (parent) => (pat, i) => {
|
||||
|
||||
// this is how it was:
|
||||
/*
|
||||
return reify(pat)._degradeByWith(
|
||||
return strudel.reify(pat)._degradeByWith(
|
||||
strudel.rand.early(randOffset * _nextSeed()).segment(1),
|
||||
operator.arguments_.amount ?? 0.5,
|
||||
);
|
||||
*/
|
||||
return reify(pat)._degradeBy(operator.arguments_.amount ?? 0.5);
|
||||
return strudel.reify(pat)._degradeBy(operator.arguments_.amount ?? 0.5);
|
||||
|
||||
// TODO: case 'fixed-step': "%"
|
||||
}
|
||||
@ -87,7 +85,7 @@ function resolveReplications(ast) {
|
||||
source_: {
|
||||
type_: 'pattern',
|
||||
arguments_: {
|
||||
alignment: 'h',
|
||||
alignment: 'fastcat',
|
||||
},
|
||||
source_: [
|
||||
{
|
||||
@ -113,31 +111,45 @@ export function patternifyAST(ast, code) {
|
||||
resolveReplications(ast);
|
||||
const children = ast.source_.map((child) => patternifyAST(child, code)).map(applyOptions(ast));
|
||||
const alignment = ast.arguments_.alignment;
|
||||
if (alignment === 'v') {
|
||||
return stack(...children);
|
||||
if (alignment === 'stack') {
|
||||
return strudel.stack(...children);
|
||||
}
|
||||
if (alignment === 'r') {
|
||||
if (alignment === 'polymeter') {
|
||||
// polymeter
|
||||
const stepsPerCycle = strudel.Fraction(
|
||||
ast.arguments_.stepsPerCycle
|
||||
? ast.arguments_.stepsPerCycle
|
||||
: strudel.Fraction(children.length > 0 ? children[0].__weight : 1),
|
||||
);
|
||||
|
||||
const aligned = children.map((child) => child.fast(stepsPerCycle.div(child.__weight || strudel.Fraction(1))));
|
||||
return strudel.stack(...aligned);
|
||||
}
|
||||
if (alignment === 'rand') {
|
||||
// https://github.com/tidalcycles/strudel/issues/245#issuecomment-1345406422
|
||||
// return strudel.chooseInWith(strudel.rand.early(randOffset * _nextSeed()).segment(1), children);
|
||||
return strudel.chooseCycles(...children);
|
||||
}
|
||||
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
|
||||
if (!weightedChildren && alignment === 't') {
|
||||
return slowcat(...children);
|
||||
if (!weightedChildren && alignment === 'slowcat') {
|
||||
return strudel.slowcat(...children);
|
||||
}
|
||||
if (weightedChildren) {
|
||||
const pat = timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
||||
if (alignment === 't') {
|
||||
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
||||
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
|
||||
const pat = strudel.timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
|
||||
if (alignment === 'slowcat') {
|
||||
return pat._slow(weightSum); // timecat + slow
|
||||
}
|
||||
pat.__weight = weightSum;
|
||||
return pat;
|
||||
}
|
||||
return sequence(...children);
|
||||
const pat = strudel.sequence(...children);
|
||||
pat.__weight = strudel.Fraction(children.length);
|
||||
return pat;
|
||||
}
|
||||
case 'element': {
|
||||
if (ast.source_ === '~') {
|
||||
return silence;
|
||||
return strudel.silence;
|
||||
}
|
||||
if (typeof ast.source_ !== 'object') {
|
||||
if (!ast.location_) {
|
||||
@ -153,10 +165,12 @@ export function patternifyAST(ast, code) {
|
||||
const [offsetStart = 0, offsetEnd = 0] = actual
|
||||
? actual.split(ast.source_).map((p) => p.split('').filter((c) => c === ' ').length)
|
||||
: [];
|
||||
return pure(value).withLocation(
|
||||
[start.line, start.column + offsetStart, start.offset + offsetStart],
|
||||
[start.line, end.column - offsetEnd, end.offset - offsetEnd],
|
||||
);
|
||||
return strudel
|
||||
.pure(value)
|
||||
.withLocation(
|
||||
[start.line, start.column + offsetStart, start.offset + offsetStart],
|
||||
[start.line, end.column - offsetEnd, end.offset - offsetEnd],
|
||||
);
|
||||
}
|
||||
return patternifyAST(ast.source_, code);
|
||||
}
|
||||
@ -183,10 +197,10 @@ export function patternifyAST(ast, code) {
|
||||
}); */
|
||||
/* case 'struct':
|
||||
// TODO:
|
||||
return silence; */
|
||||
return strudel.silence; */
|
||||
default:
|
||||
console.warn(`node type "${ast.type_}" not implemented -> returning silence`);
|
||||
return silence;
|
||||
return strudel.silence;
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +211,7 @@ export const mini = (...strings) => {
|
||||
const ast = krill.parse(code);
|
||||
return patternifyAST(ast, code);
|
||||
});
|
||||
return sequence(...pats);
|
||||
return strudel.sequence(...pats);
|
||||
};
|
||||
|
||||
// includes haskell style (raw krill parsing)
|
||||
@ -211,5 +225,5 @@ export function minify(thing) {
|
||||
if (typeof thing === 'string') {
|
||||
return mini(thing);
|
||||
}
|
||||
return reify(thing);
|
||||
return strudel.reify(thing);
|
||||
}
|
||||
|
||||
@ -36,6 +36,16 @@ describe('mini', () => {
|
||||
expect(minS('c3 [d3 e3]')).toEqual(['c3: 0 - 1/2', 'd3: 1/2 - 3/4', 'e3: 3/4 - 1']);
|
||||
expect(minS('c3 [d3 [e3 f3]]')).toEqual(['c3: 0 - 1/2', 'd3: 1/2 - 3/4', 'e3: 3/4 - 7/8', 'f3: 7/8 - 1']);
|
||||
});
|
||||
it('supports curly brackets', () => {
|
||||
expect(minS('{a b, c d e}*3')).toEqual(minS('[a b a b a b, c d e c d e]'));
|
||||
expect(minS('{a b, c [d e] f}*3')).toEqual(minS('[a b a b a b, c [d e] f c [d e] f]'));
|
||||
expect(minS('{a b c, d e}*2')).toEqual(minS('[a b c a b c, d e d e d e]'));
|
||||
});
|
||||
it('supports curly brackets with explicit step-per-cycle', () => {
|
||||
expect(minS('{a b, c d e}%3')).toEqual(minS('[a b a, c d e]'));
|
||||
expect(minS('{a b, c d e}%5')).toEqual(minS('[a b a b a, c d e c d]'));
|
||||
expect(minS('{a b, c d e}%6')).toEqual(minS('[a b a b a b, c d e c d e]'));
|
||||
});
|
||||
it('supports commas', () => {
|
||||
expect(minS('c3,e3,g3')).toEqual(['c3: 0 - 1', 'e3: 0 - 1', 'g3: 0 - 1']);
|
||||
expect(minS('[c3,e3,g3] f3')).toEqual(['c3: 0 - 1/2', 'e3: 0 - 1/2', 'g3: 0 - 1/2', 'f3: 1/2 - 1']);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user