Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for multiple mininotation operators #350

Merged
merged 5 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 39 additions & 30 deletions packages/mini/krill-parser.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 21 additions & 16 deletions packages/mini/krill.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -119,32 +119,37 @@ 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_replicate / slice_degrade
slice_op = op_weight / op_bjorklund / op_slow / op_fast / op_replicate / op_degrade

slice_weight = "@" a:number
{ return { weight: a} }
op_weight = "@" a:number
{ return x => x.options_['weight'] = a }

slice_replicate = "!"a:number
{ return { replicate: a } }
op_replicate = "!"a:number
{ return x => x.options_['reps'] = a }

slice_bjorklund = "(" ws p:slice_with_modifier ws comma ws s:slice_with_modifier ws comma? ws r:slice_with_modifier? ws ")"
{ return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r } } } }
op_bjorklund = "(" ws p:slice_with_ops ws comma ws s:slice_with_ops ws comma? ws r:slice_with_ops? ws ")"
{ return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }

slice_slow = "/"a:slice
{ return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'slow' } } } }
op_slow = "/"a:slice
{ return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) }

slice_fast = "*"a:slice
{ return { operator : { type_: "stretch", arguments_ :{ amount:a, type: 'fast' } } } }
op_fast = "*"a:slice
{ return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) }

slice_degrade = "?"a:number?
{ return { operator : { type_: "degradeBy", arguments_ :{ amount:a } } } }
op_degrade = "?"a:number?
{ return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a } }) }

// a slice with an modifier applied i.e [bd@4 sd@3]@2 hh]
slice_with_modifier = s:slice o:slice_modifier?
{ return new ElementStub(s, o);}
slice_with_ops = s:slice ops:slice_op*
{ const result = new ElementStub(s, {ops: [], weight: 1, reps: 1});
for (const op of ops) {
op(result);
}
return result;
}

// a sequence is a combination of one or more successive slices (as an array)
sequence = s:(slice_with_modifier)+
sequence = s:(slice_with_ops)+
{ return new PatternStub(s, 'fastcat'); }

// a stack is a series of vertically aligned sequence, separated by a comma
Expand Down
96 changes: 46 additions & 50 deletions packages/mini/mini.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,78 +17,74 @@ function _nextSeed() {
const applyOptions = (parent, code) => (pat, i) => {
const ast = parent.source_[i];
const options = ast.options_;
const operator = options?.operator;
if (operator) {
switch (operator.type_) {
case 'stretch': {
const legalTypes = ['fast', 'slow'];
const { type, amount } = operator.arguments_;
if (!legalTypes.includes(type)) {
throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`);
const ops = options?.ops;
if (ops) {
for (const op of ops) {
switch (op.type_) {
case 'stretch': {
const legalTypes = ['fast', 'slow'];
const { type, amount } = op.arguments_;
if (!legalTypes.includes(type)) {
throw new Error(`mini: stretch: type must be one of ${legalTypes.join('|')} but got ${type}`);
}
pat = strudel.reify(pat)[type](patternifyAST(amount, code));
break;
}
return strudel.reify(pat)[type](patternifyAST(amount, code));
}
case 'bjorklund':
if (operator.arguments_.rotation) {
return pat.euclidRot(
patternifyAST(operator.arguments_.pulse, code),
patternifyAST(operator.arguments_.step, code),
patternifyAST(operator.arguments_.rotation, code),
);
} else {
return pat.euclid(
patternifyAST(operator.arguments_.pulse, code),
patternifyAST(operator.arguments_.step, code),
);
case 'bjorklund': {
if (op.arguments_.rotation) {
pat = pat.euclidRot(
patternifyAST(op.arguments_.pulse, code),
patternifyAST(op.arguments_.step, code),
patternifyAST(op.arguments_.rotation, code),
);
} else {
pat = pat.euclid(patternifyAST(op.arguments_.pulse, code), patternifyAST(op.arguments_.step, code));
}
break;
}
case 'degradeBy':
// TODO: find out what is right here
// example:
/*
case 'degradeBy': {
// TODO: find out what is right here
// example:
/*
stack(
s("hh*8").degrade(),
s("[ht*8]?")
)
*/
// above example will only be in sync when _degradeBy is used...
// it also seems that the nextSeed will create undeterministic behaviour
// as it uses a global _seedState. This is probably the reason for
// https://github.com/tidalcycles/strudel/issues/245
// above example will only be in sync when _degradeBy is used...
// it also seems that the nextSeed will create undeterministic behaviour
// as it uses a global _seedState. This is probably the reason for
// https://github.com/tidalcycles/strudel/issues/245

// this is how it was:
/*
// this is how it was:
/*
return strudel.reify(pat)._degradeByWith(
strudel.rand.early(randOffset * _nextSeed()).segment(1),
operator.arguments_.amount ?? 0.5,
op.arguments_.amount ?? 0.5,
);
*/
return strudel.reify(pat).degradeBy(operator.arguments_.amount === null ? 0.5 : operator.arguments_.amount);
pat = strudel.reify(pat).degradeBy(op.arguments_.amount === null ? 0.5 : op.arguments_.amount);
break;
}
default: {
console.warn(`operator "${op.type_}" not implemented`);
}
}
}
console.warn(`operator "${operator.type_}" not implemented`);
}
if (options?.weight) {
// weight is handled by parent
return pat;
}
// TODO: bjorklund e.g. "c3(5,8)"
const unimplemented = Object.keys(options || {}).filter((key) => key !== 'operator');
if (unimplemented.length) {
console.warn(
`option${unimplemented.length > 1 ? 's' : ''} ${unimplemented.map((o) => `"${o}"`).join(', ')} not implemented`,
);
}

return pat;
};

function resolveReplications(ast) {
ast.source_ = strudel.flatten(
ast.source_.map((child) => {
const { replicate, ...options } = child.options_ || {};
if (!replicate) {
const { reps } = child.options_ || {};
if (!reps) {
return [child];
}
delete child.options_.replicate;
return Array(replicate).fill(child);
delete child.options_.reps;
return Array(reps).fill(child);
}),
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/mini/test/mini.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import '@strudel.cycles/core/euclid.mjs';
import { describe, expect, it } from 'vitest';

describe('mini', () => {
const minV = (v) => mini(v).firstCycleValues;
const minS = (v) => mini(v).showFirstCycle;
const minV = (v) => mini(v).sortHapsByPart().firstCycleValues;
const minS = (v) => mini(v).sortHapsByPart().showFirstCycle;
it('supports single elements', () => {
expect(minV('a')).toEqual(['a']);
});
Expand Down