From a9a4cd2f5c408818172f5412599f6111f485bb3e Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 23 Feb 2022 20:18:03 +0000 Subject: [PATCH] Make pattern a function of state, not just time. --- strudel.mjs | 87 ++++++++++++++++++++++++++++--------------- test/pattern.test.mjs | 11 +++--- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/strudel.mjs b/strudel.mjs index 25b9fd0f..839ea1c2 100644 --- a/strudel.mjs +++ b/strudel.mjs @@ -216,6 +216,27 @@ class Hap { } } +export class State { + constructor(span, controls={}) { + this.span = span + this.controls = controls + } + + // Returns new State with different span + setSpan(span) { + return new State(span, this.controls) + } + + withSpan(func) { + return this.setSpan(func(this.span)) + } + + // Returns new State with different controls + setControls(controls) { + return new State(this.span, controls) + } +} + class Pattern { // the following functions will get patternFactories as nested functions: constructor(query) { @@ -244,25 +265,27 @@ class Pattern { // easier to express, as all events are then constrained to happen within // a cycle. const pat = this - const q = span => flatten(span.spanCycles.map(subspan => pat.query(subspan))) + const q = state => { + return flatten(state.span.spanCycles.map(subspan => pat.query(state.setSpan(subspan)))) + } return new Pattern(q) } withQuerySpan(func) { - return new Pattern(span => this.query(func(span))) + return new Pattern(state => this.query(state.withSpan(func))) } withQueryTime(func) { // Returns a new pattern, with the function applied to both the begin // and end of the the query timespan - return new Pattern(span => this.query(span.withTime(func))) + return new Pattern(state => this.query(state.withSpan(span => span.withTime(func)))) } withEventSpan(func) { // Returns a new pattern, with the function applied to each event // timespan. - return new Pattern(span => this.query(span).map(hap => hap.withSpan(func))) + return new Pattern(state => this.query(state).map(hap => hap.withSpan(func))) } withEventTime(func) { @@ -272,13 +295,13 @@ class Pattern { } _withEvents(func) { - return new Pattern(span => func(this.query(span))) + return new Pattern(state => func(this.query(state))) } withValue(func) { // Returns a new pattern, with the function applied to the value of // each event. It has the alias 'fmap'. - return new Pattern(span => this.query(span).map(hap => hap.withValue(func))) + return new Pattern(state => this.query(state).map(hap => hap.withValue(func))) } // alias @@ -287,11 +310,11 @@ class Pattern { } _filterEvents(event_test) { - return new Pattern(span => this.query(span).filter(event_test)) + return new Pattern(state => this.query(state).filter(event_test)) } _filterValues(value_test) { - return new Pattern(span => this.query(span).filter(hap => value_test(hap.value))) + return new Pattern(state => this.query(state).filter(hap => value_test(hap.value))) } _removeUndefineds() { @@ -310,9 +333,9 @@ class Pattern { // resolve wholes, applies a given pattern of values to that // pattern of functions. const pat_func = this - const query = function(span) { - const event_funcs = pat_func.query(span) - const event_vals = pat_val.query(span) + const query = function(state) { + const event_funcs = pat_func.query(state) + const event_vals = pat_val.query(state) const apply = function(event_func, event_val) { const s = event_func.part.intersection(event_val.part) if (s == undefined) { @@ -339,10 +362,10 @@ class Pattern { appLeft(pat_val) { const pat_func = this - const query = function(span) { + const query = function(state) { const haps = [] - for (const hap_func of pat_func.query(span)) { - const event_vals = pat_val.query(hap_func.part) + for (const hap_func of pat_func.query(state)) { + const event_vals = pat_val.query(state.setSpan(hap_func.part)) for (const hap_val of event_vals) { const new_whole = hap_func.whole const new_part = hap_func.part.intersection_e(hap_val.part) @@ -359,10 +382,10 @@ class Pattern { appRight(pat_val) { const pat_func = this - const query = function(span) { + const query = function(state) { const haps = [] - for (const hap_val of pat_val.query(span)) { - const hap_funcs = pat_func.query(hap_val.part) + for (const hap_val of pat_val.query(state)) { + const hap_funcs = pat_func.query(state.setSpan(hap_val.part)) for (const hap_func of hap_funcs) { const new_whole = hap_val.whole const new_part = hap_func.part.intersection_e(hap_val.part) @@ -377,7 +400,7 @@ class Pattern { } get firstCycle() { - return this.query(new TimeSpan(Fraction(0), Fraction(1))) + return this.query(new State(new TimeSpan(Fraction(0), Fraction(1)))) } _sortEventsByPart() { @@ -402,16 +425,16 @@ class Pattern { _bindWhole(choose_whole, func) { const pat_val = this - const query = function(span) { + const query = function(state) { const withWhole = function(a, b) { return new Hap(choose_whole(a.whole, b.whole), b.part, b.value ) } const match = function (a) { - return func(a.value).query(a.part).map(b => withWhole(a, b)) + return func(a.value).query(state.setSpan(a.part)).map(b => withWhole(a, b)) } - return flatten(pat_val.query(span).map(a => match(a))) + return flatten(pat_val.query(state).map(a => match(a))) } return new Pattern(query) } @@ -559,7 +582,8 @@ class Pattern { rev() { const pat = this - const query = function(span) { + const query = function(state) { + const span = state.span const cycle = span.begin.sam() const next_cycle = span.begin.nextSam() const reflect = function(to_reflect) { @@ -570,7 +594,7 @@ class Pattern { reflected.end = tmp return reflected } - const haps = pat.query(reflect(span)) + const haps = pat.query(state.setSpan(reflect(span))) return haps.map(hap => hap.withSpan(reflect)) } return new Pattern(query)._splitQueries() @@ -645,8 +669,8 @@ const silence = new Pattern(_ => []) function pure(value) { // A discrete value that repeats once per cycle - function query(span) { - return span.spanCycles.map(subspan => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value)) + function query(state) { + return state.span.spanCycles.map(subspan => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value)) } return new Pattern(query) } @@ -668,7 +692,7 @@ function reify(thing) { function stack(...pats) { const reified = pats.map(pat => reify(pat)) - const query = span => flatten(reified.map(pat => pat.query(span))) + const query = state => flatten(reified.map(pat => pat.query(state))) return new Pattern(query) } @@ -676,7 +700,8 @@ function slowcat(...pats) { // Concatenation: combines a list of patterns, switching between them // successively, one per cycle. pats = pats.map(reify) - const query = function(span) { + const query = function(state) { + const span = state.span const pat_n = Math.floor(span.begin) % pats.length; const pat = pats[pat_n] if (!pat) { @@ -687,7 +712,7 @@ function slowcat(...pats) { // For example if three patterns are slowcat-ed, the fourth cycle of the result should // be the second (rather than fourth) cycle from the first pattern. const offset = span.begin.floor().sub(span.begin.div(pats.length).floor()) - return pat.withEventTime(t => t.add(offset)).query(span.withTime(t => t.sub(offset))) + return pat.withEventTime(t => t.add(offset)).query(state.setSpan(span.withTime(t => t.sub(offset)))) } return new Pattern(query)._splitQueries() } @@ -696,10 +721,10 @@ function slowcatPrime(...pats) { // Concatenation: combines a list of patterns, switching between them // successively, one per cycle. Unlike slowcat, this version will skip cycles. pats = pats.map(reify) - const query = function(span) { - const pat_n = Math.floor(span.begin) % pats.length + const query = function(state) { + const pat_n = Math.floor(state.span.begin) % pats.length const pat = pats[pat_n] - return pat.query(span) + return pat.query(state) } return new Pattern(query)._splitQueries() } diff --git a/test/pattern.test.mjs b/test/pattern.test.mjs index c6986538..73ed7957 100644 --- a/test/pattern.test.mjs +++ b/test/pattern.test.mjs @@ -2,11 +2,12 @@ import Fraction from 'fraction.js' import { strict as assert } from 'assert'; -import {TimeSpan, Hap, Pattern, pure, stack, fastcat, slowcat, cat, sequence, polyrhythm, silence, fast, timeCat,add,sub,mul,div} from "../strudel.mjs"; +import {TimeSpan, Hap, Pattern, pure, stack, fastcat, slowcat, cat, sequence, polyrhythm, silence, fast, timeCat,add,sub,mul,div, State} from "../strudel.mjs"; //import { Time } from 'tone'; import pkg from 'tone'; const { Time } = pkg; +const st = (begin, end) => new State(ts(begin, end)) const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end)); const hap = (whole, part, value) => new Hap(whole, part, value) @@ -62,7 +63,7 @@ describe('Hap', function() { describe('Pattern', function() { describe('pure', function () { it('Can make a pattern', function() { - assert.equal(pure("hello").query(new TimeSpan(Fraction(0.5), Fraction(2.5))).length, 3) + assert.equal(pure("hello").query(st(0.5, 2.5)).length, 3) }) }) describe('fmap()', function () { @@ -72,12 +73,12 @@ describe('Pattern', function() { }) describe('add()', function () { it('Can add things', function() { - assert.equal(pure(3).add(pure(4)).query(new TimeSpan(Fraction(0), Fraction(1)))[0].value, 7) + assert.equal(pure(3).add(pure(4)).query(st(0,1))[0].value, 7) }) }) describe('sub()', function () { it('Can subtract things', function() { - assert.equal(pure(3).sub(pure(4)).query(new TimeSpan(Fraction(0), Fraction(1)))[0].value, -1) + assert.equal(pure(3).sub(pure(4)).query(st(0,1))[0].value, -1) }) }) describe('union()', function () { @@ -155,7 +156,7 @@ describe('Pattern', function() { const pat = sequence(pure('c3'), pure('eb3')._slow(2)); // => try mini('c3 eb3/2') in repl assert.deepStrictEqual( - pat.query(ts(0,1))[1], + pat.query(st(0,1))[1], hap(ts(0.5,1.5), ts(1/2,1), "eb3") ) // the following test fails