From 7b34f3f5247b74c45387e20e519caa13d61046ce Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 19 Feb 2022 16:54:44 +0000 Subject: [PATCH 1/3] struct() and invert()/inv() --- strudel.mjs | 25 ++++++++++++++++++++++++- test/pattern.test.mjs | 23 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/strudel.mjs b/strudel.mjs index 7c7b124f..89b99c5b 100644 --- a/strudel.mjs +++ b/strudel.mjs @@ -294,6 +294,10 @@ class Pattern { return new Pattern(span => this.query(span).filter(hap => value_test(hap.value))) } + _removeUndefineds() { + return(this._filterValues(val => val != undefined)) + } + onsetsOnly() { // Returns a new pattern that will only return events where the start // of the 'whole' timespan matches the start of the 'part' @@ -507,6 +511,17 @@ class Pattern { return this._early(0-offset) } + struct(...binary_pats) { + // Re structure the pattern according to a binary pattern (false values are dropped) + const binary_pat = sequence(binary_pats) + return binary_pat.fmap(b => val => b ? val : undefined).appRight(this)._removeUndefineds() + } + + inv() { + // Swap true/false in a binary pattern + return this.fmap(x => !x) + } + when(binary_pat, func) { //binary_pat = sequence(binary_pat) const true_pat = binary_pat._filterValues(id) @@ -612,10 +627,13 @@ Pattern.prototype.patternified = ['fast', 'slow', 'early', 'late']; Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr}; // the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts) +// Elemental patterns + +// Nothing const silence = new Pattern(_ => []) function pure(value) { - // Returns a pattern that repeats the given value once per cycle + // A discrete value that repeats once per cycle function query(span) { return span.spanCycles.map(subspan => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value)) } @@ -623,16 +641,20 @@ function pure(value) { } function steady(value) { + // A continuous value return new Pattern(span => Hap(undefined, span, value)) } function reify(thing) { + // Tunrs something into a pattern, unless it's already a pattern if (thing?.constructor?.name == "Pattern") { return thing } return pure(thing) } +// Basic functions for combining patterns + function stack(...pats) { const reified = pats.map(pat => reify(pat)) const query = span => flatten(reified.map(pat => pat.query(span))) @@ -682,6 +704,7 @@ function cat(...pats) { } function timeCat(...timepats) { + // Like cat, but where each step has a temporal 'weight' const total = timepats.map(a => a[0]).reduce((a,b) => a.add(b), Fraction(0)) let begin = Fraction(0) const pats = [] diff --git a/test/pattern.test.mjs b/test/pattern.test.mjs index 7fa8bdfe..3646bd22 100644 --- a/test/pattern.test.mjs +++ b/test/pattern.test.mjs @@ -10,6 +10,9 @@ const { Time } = pkg; const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end)); const hap = (whole, part, value) => new Hap(whole, part, value) +const third = Fraction(1,3) +const twothirds = Fraction(2,3) + describe('TimeSpan', function() { describe('equals()', function() { it('Should be equal to the same value', function() { @@ -244,4 +247,24 @@ describe('Pattern', function() { ) }) }) + describe('struct()', function() { + it('Can restructure a pattern', function() { + assert.deepStrictEqual( + sequence("a", "b").struct(sequence(true, true, true)).firstCycle, + [hap(ts(0, 0.5), ts(0,third), "a"), + hap(ts(0, 0.5), ts(third, 0.5), "a"), + hap(ts(0.5, 1), ts(0.5, twothirds), "b"), + hap(ts(0.5, 1), ts(twothirds, 1), "b") + ] + ) + }) + }) + describe('inv()', function() { + it('Can invert a binary pattern', function() { + assert.deepStrictEqual( + sequence(true, false, [true, false]).inv().firstCycle, + sequence(false, true, [false, true]).firstCycle + ) + }) + }) }) From 0039b600205bd7e2df333f50d5a6eb010b070de9 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 19 Feb 2022 17:14:11 +0000 Subject: [PATCH 2/3] Fix struct() and add mask() --- strudel.mjs | 24 ++++++++++++++++++++---- test/pattern.test.mjs | 26 ++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/strudel.mjs b/strudel.mjs index 89b99c5b..25b9fd0f 100644 --- a/strudel.mjs +++ b/strudel.mjs @@ -514,12 +514,23 @@ class Pattern { struct(...binary_pats) { // Re structure the pattern according to a binary pattern (false values are dropped) const binary_pat = sequence(binary_pats) - return binary_pat.fmap(b => val => b ? val : undefined).appRight(this)._removeUndefineds() + return binary_pat.fmap(b => val => b ? val : undefined).appLeft(this)._removeUndefineds() + } + + mask(...binary_pats) { + // Only let through parts of pattern corresponding to true values in the given binary pattern + const binary_pat = sequence(binary_pats) + return binary_pat.fmap(b => val => b ? val : undefined).appRight(this)._removeUndefineds() + } + + invert() { + // Swap true/false in a binary pattern + return this.fmap(x => !x) } inv() { - // Swap true/false in a binary pattern - return this.fmap(x => !x) + // alias for invert() + return this.invert() } when(binary_pat, func) { @@ -791,6 +802,10 @@ const off = curry((t, f, pat) => pat.off(t,f)) const jux = curry((f, pat) => pat.jux(f)) const append = curry((a, pat) => pat.append(a)) const superimpose = curry((array, pat) => pat.superimpose(...array)) +const struct = curry((a, pat) => pat.struct(a)) +const mask = curry((a, pat) => pat.mask(a)) +const invert = pat => pat.invert() +const inv = pat => pat.inv() // problem: curried functions with spread arguments must have pat at the beginning // with this, we cannot keep the pattern open at the end.. solution for now: use array to keep using pat as last arg @@ -845,6 +860,7 @@ Pattern.prototype.bootstrap = () => { export {Fraction, TimeSpan, Hap, Pattern, pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence, fast, slow, early, late, rev, - add, sub, mul, div, union, every, when, off, jux, append, superimpose + add, sub, mul, div, union, every, when, off, jux, append, superimpose, + struct, mask, invert, inv } diff --git a/test/pattern.test.mjs b/test/pattern.test.mjs index 3646bd22..2e148e0e 100644 --- a/test/pattern.test.mjs +++ b/test/pattern.test.mjs @@ -251,6 +251,18 @@ describe('Pattern', function() { it('Can restructure a pattern', function() { assert.deepStrictEqual( sequence("a", "b").struct(sequence(true, true, true)).firstCycle, + [hap(ts(0,third), ts(0,third), "a"), + hap(ts(third, twothirds), ts(third, 0.5), "a"), + hap(ts(third, twothirds), ts(0.5, twothirds), "b"), + hap(ts(twothirds, 1), ts(twothirds, 1), "b") + ] + ) + }) + }) + describe('mask()', function() { + it('Can fragment a pattern', function() { + assert.deepStrictEqual( + sequence("a", "b").mask(sequence(true, true, true)).firstCycle, [hap(ts(0, 0.5), ts(0,third), "a"), hap(ts(0, 0.5), ts(third, 0.5), "a"), hap(ts(0.5, 1), ts(0.5, twothirds), "b"), @@ -258,11 +270,21 @@ describe('Pattern', function() { ] ) }) + it('Can mask off parts of a pattern', function() { + assert.deepStrictEqual( + sequence(["a", "b"], "c").mask(sequence(true, false)).firstCycle, + sequence(["a","b"], silence).firstCycle + ) + assert.deepStrictEqual( + sequence("a").mask(sequence(true, false)).firstCycle, + [hap(ts(0,1),ts(0,0.5), "a")] + ) + }) }) - describe('inv()', function() { + describe('invert()', function() { it('Can invert a binary pattern', function() { assert.deepStrictEqual( - sequence(true, false, [true, false]).inv().firstCycle, + sequence(true, false, [true, false]).invert().firstCycle, sequence(false, true, [false, true]).firstCycle ) }) From ca9fef50d95cee5c08bd3d23530ed1f3c8d2c65d Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 19 Feb 2022 17:20:02 +0000 Subject: [PATCH 3/3] More struct tests --- test/pattern.test.mjs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/pattern.test.mjs b/test/pattern.test.mjs index 2e148e0e..a4a26515 100644 --- a/test/pattern.test.mjs +++ b/test/pattern.test.mjs @@ -257,6 +257,18 @@ describe('Pattern', function() { hap(ts(twothirds, 1), ts(twothirds, 1), "b") ] ) + assert.deepStrictEqual( + pure("a").struct(sequence(true, [true,false], true)).firstCycle, + sequence("a", ["a", silence], "a").firstCycle, + ) + assert.deepStrictEqual( + pure("a").struct(sequence(true, [true,false], true).invert()).firstCycle, + sequence(silence, [silence, "a"], silence).firstCycle, + ) + assert.deepStrictEqual( + pure("a").struct(sequence(true, [true,silence], true)).firstCycle, + sequence("a", ["a", silence], "a").firstCycle, + ) }) }) describe('mask()', function() {