diff --git a/packages/mini/krill-parser.js b/packages/mini/krill-parser.js index 34e3843c..d2c861f2 100644 --- a/packages/mini/krill-parser.js +++ b/packages/mini/krill-parser.js @@ -279,7 +279,7 @@ function peg$parse(input, options) { var peg$f8 = function(p, s, r) { return x => x.options_['ops'].push({ type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r }}) }; var peg$f9 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'slow' }}) }; var peg$f10 = function(a) { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) }; - var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a } }) }; + var peg$f11 = function(a) { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) }; var peg$f12 = function(s) { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) }; var peg$f13 = function(s, ops) { const result = new ElementStub(s, {ops: [], weight: 1, reps: 1}); for (const op of ops) { @@ -289,8 +289,8 @@ function peg$parse(input, options) { }; var peg$f14 = function(s) { return new PatternStub(s, 'fastcat'); }; var peg$f15 = function(tail) { return { alignment: 'stack', list: tail }; }; - var peg$f16 = function(tail) { return { alignment: 'rand', list: tail }; }; - var peg$f17 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment); } else { return head; } }; + var peg$f16 = function(tail) { return { alignment: 'rand', list: tail, seed: seed++ }; }; + var peg$f17 = function(head, tail) { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } }; var peg$f18 = function(head, tail) { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); }; var peg$f19 = function(sc) { return sc; }; var peg$f20 = function(s) { return { name: "struct", args: { mini:s }}}; @@ -2185,10 +2185,13 @@ function peg$parse(input, options) { this.location_ = location(); } - var PatternStub = function(source, alignment) + var PatternStub = function(source, alignment, seed) { this.type_ = "pattern"; - this.arguments_ = { alignment : alignment}; + this.arguments_ = { alignment: alignment }; + if (seed !== undefined) { + this.arguments_.seed = seed; + } this.source_ = source; } @@ -2214,6 +2217,7 @@ function peg$parse(input, options) { this.options_ = options; } + var seed = 0; peg$result = peg$startRuleFunction(); diff --git a/packages/mini/krill.pegjs b/packages/mini/krill.pegjs index f4fd7772..9ca48624 100644 --- a/packages/mini/krill.pegjs +++ b/packages/mini/krill.pegjs @@ -19,10 +19,13 @@ This program is free software: you can redistribute it and/or modify it under th this.location_ = location(); } - var PatternStub = function(source, alignment) + var PatternStub = function(source, alignment, seed) { this.type_ = "pattern"; - this.arguments_ = { alignment : alignment}; + this.arguments_ = { alignment: alignment }; + if (seed !== undefined) { + this.arguments_.seed = seed; + } this.source_ = source; } @@ -48,6 +51,7 @@ This program is free software: you can redistribute it and/or modify it under th this.options_ = options; } + var seed = 0; } start = statement @@ -137,7 +141,7 @@ op_fast = "*"a:slice { return x => x.options_['ops'].push({ type_: "stretch", arguments_ :{ amount:a, type: 'fast' }}) } op_degrade = "?"a:number? - { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a } }) } + { return x => x.options_['ops'].push({ type_: "degradeBy", arguments_ :{ amount:a, seed: seed++ } }) } op_tail = ":" s:slice { return x => x.options_['ops'].push({ type_: "tail", arguments_ :{ element:s } }) } @@ -162,12 +166,12 @@ stack_tail = tail:(comma @sequence)+ // 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 }; } + { return { alignment: 'rand', list: tail, seed: seed++ }; } // if the stack contains only one element, we don't create a stack but return the // underlying element 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; } } + { if (tail && tail.list.length > 0) { return new PatternStub([head, ...tail.list], tail.alignment, tail.seed); } else { return head; } } polymeter_stack = head:sequence tail:stack_tail? { return new PatternStub(tail ? [head, ...tail.list] : [head], 'polymeter'); } diff --git a/packages/mini/mini.mjs b/packages/mini/mini.mjs index af6549cf..648499e6 100644 --- a/packages/mini/mini.mjs +++ b/packages/mini/mini.mjs @@ -7,12 +7,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.cycles/core'; -/* var _seedState = 0; -const randOffset = 0.0002; - -function _nextSeed() { - return _seedState++; -} */ +const randOffset = 0.0003; const applyOptions = (parent, code) => (pat, i) => { const ast = parent.source_[i]; @@ -44,27 +39,9 @@ const applyOptions = (parent, code) => (pat, i) => { break; } 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 - - // this is how it was: - /* - return strudel.reify(pat)._degradeByWith( - strudel.rand.early(randOffset * _nextSeed()).segment(1), - op.arguments_.amount ?? 0.5, - ); - */ - pat = strudel.reify(pat).degradeBy(op.arguments_.amount === null ? 0.5 : op.arguments_.amount); + pat = strudel + .reify(pat) + ._degradeByWith(strudel.rand.early(randOffset * op.arguments_.seed), op.arguments_.amount ?? 0.5); break; } case 'tail': { @@ -114,9 +91,7 @@ export function patternifyAST(ast, code) { 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); + return strudel.chooseInWith(strudel.rand.early(randOffset * ast.arguments_.seed).segment(1), children); } const weightedChildren = ast.source_.some((child) => !!child.options_?.weight); if (!weightedChildren && alignment === 'slowcat') { diff --git a/packages/mini/test/mini.test.mjs b/packages/mini/test/mini.test.mjs index 1e18dd58..70577ab0 100644 --- a/packages/mini/test/mini.test.mjs +++ b/packages/mini/test/mini.test.mjs @@ -140,10 +140,22 @@ describe('mini', () => { expect(haps.length < 230).toBe(true); // 'Had too many cycles remaining after degradeBy 0.8'); }); - it('supports lists', () => { - expect(minV('a:b c:d:[e:f] g')).toEqual([['a', 'b'], ['c', 'd', ['e', 'f']], 'g']); + it('supports multiple independent uses of the random choice operator ("|")', () => { + const numCycles = 1000; + const values = mini('[a|b] [a|b]') + .queryArc(0, numCycles) + .map((e) => e.value); + const observed = { aa: 0, ab: 0, ba: 0, bb: 0 }; + for (let i = 0; i < values.length; i += 2) { + const chunk = values.slice(i, i + 2); + observed[chunk.join('')]++; + } + for (const count of Object.values(observed)) { + // Should fall within 99% confidence interval for binomial with p=0.25. + expect(215 <= count && count <= 286).toBe(true); + } }); - /*it('supports the random choice operator ("|") with nesting', () => { + it('supports the random choice operator ("|") with nesting', () => { const numCycles = 900; const haps = mini('a | [b | c] | [d | e | f]').queryArc(0, numCycles); // Should have about 1/3 a, 1/6 each of b | c, and 1/9 each of d | e | f. @@ -168,6 +180,8 @@ describe('mini', () => { // 15.086 is the chisq for 5 degrees of freedom at 99%, so for 99% of uniformly-distributed // PRNG, this test should succeed expect(chisq <= 15.086).toBe(true); - // assert(chisq <= 15.086, chisq + ' was expected to be less than 15.086 under chi-squared test'); - });*/ + }); + it('supports lists', () => { + expect(minV('a:b c:d:[e:f] g')).toEqual([['a', 'b'], ['c', 'd', ['e', 'f']], 'g']); + }); }); diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index 261255f0..0822ffbb 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -63,6 +63,7 @@ exports[`renders tunes > tune: arpoon 1`] = ` "[ 0/1 → 1/2 | s:bd bank:RolandTR909 gain:0.5 ]", "[ 1/2 → 1/1 | s:bd bank:RolandTR909 gain:0.5 ]", "[ 1/2 → 2/3 | s:hh bank:RolandTR909 gain:0.5 ]", + "[ 5/6 → 1/1 | s:hh bank:RolandTR909 gain:0.5 ]", ] `;