Make pattern a function of state, not just time.
This commit is contained in:
parent
d3dce4c662
commit
a9a4cd2f5c
2 changed files with 62 additions and 36 deletions
87
strudel.mjs
87
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 {
|
class Pattern {
|
||||||
// the following functions will get patternFactories as nested functions:
|
// the following functions will get patternFactories as nested functions:
|
||||||
constructor(query) {
|
constructor(query) {
|
||||||
|
|
@ -244,25 +265,27 @@ class Pattern {
|
||||||
// easier to express, as all events are then constrained to happen within
|
// easier to express, as all events are then constrained to happen within
|
||||||
// a cycle.
|
// a cycle.
|
||||||
const pat = this
|
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)
|
return new Pattern(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
withQuerySpan(func) {
|
withQuerySpan(func) {
|
||||||
return new Pattern(span => this.query(func(span)))
|
return new Pattern(state => this.query(state.withSpan(func)))
|
||||||
}
|
}
|
||||||
|
|
||||||
withQueryTime(func) {
|
withQueryTime(func) {
|
||||||
// Returns a new pattern, with the function applied to both the begin
|
// Returns a new pattern, with the function applied to both the begin
|
||||||
// and end of the the query timespan
|
// 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) {
|
withEventSpan(func) {
|
||||||
// Returns a new pattern, with the function applied to each event
|
// Returns a new pattern, with the function applied to each event
|
||||||
// timespan.
|
// 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) {
|
withEventTime(func) {
|
||||||
|
|
@ -272,13 +295,13 @@ class Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
_withEvents(func) {
|
_withEvents(func) {
|
||||||
return new Pattern(span => func(this.query(span)))
|
return new Pattern(state => func(this.query(state)))
|
||||||
}
|
}
|
||||||
|
|
||||||
withValue(func) {
|
withValue(func) {
|
||||||
// Returns a new pattern, with the function applied to the value of
|
// Returns a new pattern, with the function applied to the value of
|
||||||
// each event. It has the alias 'fmap'.
|
// 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
|
// alias
|
||||||
|
|
@ -287,11 +310,11 @@ class Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
_filterEvents(event_test) {
|
_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) {
|
_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() {
|
_removeUndefineds() {
|
||||||
|
|
@ -310,9 +333,9 @@ class Pattern {
|
||||||
// resolve wholes, applies a given pattern of values to that
|
// resolve wholes, applies a given pattern of values to that
|
||||||
// pattern of functions.
|
// pattern of functions.
|
||||||
const pat_func = this
|
const pat_func = this
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const event_funcs = pat_func.query(span)
|
const event_funcs = pat_func.query(state)
|
||||||
const event_vals = pat_val.query(span)
|
const event_vals = pat_val.query(state)
|
||||||
const apply = function(event_func, event_val) {
|
const apply = function(event_func, event_val) {
|
||||||
const s = event_func.part.intersection(event_val.part)
|
const s = event_func.part.intersection(event_val.part)
|
||||||
if (s == undefined) {
|
if (s == undefined) {
|
||||||
|
|
@ -339,10 +362,10 @@ class Pattern {
|
||||||
appLeft(pat_val) {
|
appLeft(pat_val) {
|
||||||
const pat_func = this
|
const pat_func = this
|
||||||
|
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const haps = []
|
const haps = []
|
||||||
for (const hap_func of pat_func.query(span)) {
|
for (const hap_func of pat_func.query(state)) {
|
||||||
const event_vals = pat_val.query(hap_func.part)
|
const event_vals = pat_val.query(state.setSpan(hap_func.part))
|
||||||
for (const hap_val of event_vals) {
|
for (const hap_val of event_vals) {
|
||||||
const new_whole = hap_func.whole
|
const new_whole = hap_func.whole
|
||||||
const new_part = hap_func.part.intersection_e(hap_val.part)
|
const new_part = hap_func.part.intersection_e(hap_val.part)
|
||||||
|
|
@ -359,10 +382,10 @@ class Pattern {
|
||||||
appRight(pat_val) {
|
appRight(pat_val) {
|
||||||
const pat_func = this
|
const pat_func = this
|
||||||
|
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const haps = []
|
const haps = []
|
||||||
for (const hap_val of pat_val.query(span)) {
|
for (const hap_val of pat_val.query(state)) {
|
||||||
const hap_funcs = pat_func.query(hap_val.part)
|
const hap_funcs = pat_func.query(state.setSpan(hap_val.part))
|
||||||
for (const hap_func of hap_funcs) {
|
for (const hap_func of hap_funcs) {
|
||||||
const new_whole = hap_val.whole
|
const new_whole = hap_val.whole
|
||||||
const new_part = hap_func.part.intersection_e(hap_val.part)
|
const new_part = hap_func.part.intersection_e(hap_val.part)
|
||||||
|
|
@ -377,7 +400,7 @@ class Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
get firstCycle() {
|
get firstCycle() {
|
||||||
return this.query(new TimeSpan(Fraction(0), Fraction(1)))
|
return this.query(new State(new TimeSpan(Fraction(0), Fraction(1))))
|
||||||
}
|
}
|
||||||
|
|
||||||
_sortEventsByPart() {
|
_sortEventsByPart() {
|
||||||
|
|
@ -402,16 +425,16 @@ class Pattern {
|
||||||
|
|
||||||
_bindWhole(choose_whole, func) {
|
_bindWhole(choose_whole, func) {
|
||||||
const pat_val = this
|
const pat_val = this
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const withWhole = function(a, b) {
|
const withWhole = function(a, b) {
|
||||||
return new Hap(choose_whole(a.whole, b.whole), b.part,
|
return new Hap(choose_whole(a.whole, b.whole), b.part,
|
||||||
b.value
|
b.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const match = function (a) {
|
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)
|
return new Pattern(query)
|
||||||
}
|
}
|
||||||
|
|
@ -559,7 +582,8 @@ class Pattern {
|
||||||
|
|
||||||
rev() {
|
rev() {
|
||||||
const pat = this
|
const pat = this
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
|
const span = state.span
|
||||||
const cycle = span.begin.sam()
|
const cycle = span.begin.sam()
|
||||||
const next_cycle = span.begin.nextSam()
|
const next_cycle = span.begin.nextSam()
|
||||||
const reflect = function(to_reflect) {
|
const reflect = function(to_reflect) {
|
||||||
|
|
@ -570,7 +594,7 @@ class Pattern {
|
||||||
reflected.end = tmp
|
reflected.end = tmp
|
||||||
return reflected
|
return reflected
|
||||||
}
|
}
|
||||||
const haps = pat.query(reflect(span))
|
const haps = pat.query(state.setSpan(reflect(span)))
|
||||||
return haps.map(hap => hap.withSpan(reflect))
|
return haps.map(hap => hap.withSpan(reflect))
|
||||||
}
|
}
|
||||||
return new Pattern(query)._splitQueries()
|
return new Pattern(query)._splitQueries()
|
||||||
|
|
@ -645,8 +669,8 @@ const silence = new Pattern(_ => [])
|
||||||
|
|
||||||
function pure(value) {
|
function pure(value) {
|
||||||
// A discrete value that repeats once per cycle
|
// A discrete value that repeats once per cycle
|
||||||
function query(span) {
|
function query(state) {
|
||||||
return span.spanCycles.map(subspan => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value))
|
return state.span.spanCycles.map(subspan => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value))
|
||||||
}
|
}
|
||||||
return new Pattern(query)
|
return new Pattern(query)
|
||||||
}
|
}
|
||||||
|
|
@ -668,7 +692,7 @@ function reify(thing) {
|
||||||
|
|
||||||
function stack(...pats) {
|
function stack(...pats) {
|
||||||
const reified = pats.map(pat => reify(pat))
|
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)
|
return new Pattern(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -676,7 +700,8 @@ function slowcat(...pats) {
|
||||||
// Concatenation: combines a list of patterns, switching between them
|
// Concatenation: combines a list of patterns, switching between them
|
||||||
// successively, one per cycle.
|
// successively, one per cycle.
|
||||||
pats = pats.map(reify)
|
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_n = Math.floor(span.begin) % pats.length;
|
||||||
const pat = pats[pat_n]
|
const pat = pats[pat_n]
|
||||||
if (!pat) {
|
if (!pat) {
|
||||||
|
|
@ -687,7 +712,7 @@ function slowcat(...pats) {
|
||||||
// For example if three patterns are slowcat-ed, the fourth cycle of the result should
|
// 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.
|
// be the second (rather than fourth) cycle from the first pattern.
|
||||||
const offset = span.begin.floor().sub(span.begin.div(pats.length).floor())
|
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()
|
return new Pattern(query)._splitQueries()
|
||||||
}
|
}
|
||||||
|
|
@ -696,10 +721,10 @@ function slowcatPrime(...pats) {
|
||||||
// Concatenation: combines a list of patterns, switching between them
|
// Concatenation: combines a list of patterns, switching between them
|
||||||
// successively, one per cycle. Unlike slowcat, this version will skip cycles.
|
// successively, one per cycle. Unlike slowcat, this version will skip cycles.
|
||||||
pats = pats.map(reify)
|
pats = pats.map(reify)
|
||||||
const query = function(span) {
|
const query = function(state) {
|
||||||
const pat_n = Math.floor(span.begin) % pats.length
|
const pat_n = Math.floor(state.span.begin) % pats.length
|
||||||
const pat = pats[pat_n]
|
const pat = pats[pat_n]
|
||||||
return pat.query(span)
|
return pat.query(state)
|
||||||
}
|
}
|
||||||
return new Pattern(query)._splitQueries()
|
return new Pattern(query)._splitQueries()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import Fraction from 'fraction.js'
|
||||||
|
|
||||||
import { strict as assert } from 'assert';
|
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 { Time } from 'tone';
|
||||||
import pkg from 'tone';
|
import pkg from 'tone';
|
||||||
const { Time } = pkg;
|
const { Time } = pkg;
|
||||||
|
|
||||||
|
const st = (begin, end) => new State(ts(begin, end))
|
||||||
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
|
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
|
||||||
const hap = (whole, part, value) => new Hap(whole, part, value)
|
const hap = (whole, part, value) => new Hap(whole, part, value)
|
||||||
|
|
||||||
|
|
@ -62,7 +63,7 @@ describe('Hap', function() {
|
||||||
describe('Pattern', function() {
|
describe('Pattern', function() {
|
||||||
describe('pure', function () {
|
describe('pure', function () {
|
||||||
it('Can make a pattern', 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 () {
|
describe('fmap()', function () {
|
||||||
|
|
@ -72,12 +73,12 @@ describe('Pattern', function() {
|
||||||
})
|
})
|
||||||
describe('add()', function () {
|
describe('add()', function () {
|
||||||
it('Can add things', 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 () {
|
describe('sub()', function () {
|
||||||
it('Can subtract things', 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 () {
|
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
|
const pat = sequence(pure('c3'), pure('eb3')._slow(2)); // => try mini('c3 eb3/2') in repl
|
||||||
assert.deepStrictEqual(
|
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")
|
hap(ts(0.5,1.5), ts(1/2,1), "eb3")
|
||||||
)
|
)
|
||||||
// the following test fails
|
// the following test fails
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue