Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Felix Roos 2022-02-05 21:36:36 +01:00
commit 74355e86da
3 changed files with 85 additions and 42 deletions

View File

@ -1,12 +1,12 @@
export const tetris = `stack(sequence( export const tetris = `stack(sequence(
'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'), 'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'),
'a4', sequence('a4', 'c5'), 'e5', sequence('d5', 'c5'), 'a4', sequence('a4', 'c5'), 'e5', sequence('d5', 'c5'),
'b4', sequence(silence(), 'c5'), 'd5', 'e5', 'b4', sequence(silence, 'c5'), 'd5', 'e5',
'c5', 'a4', 'a4', silence(), 'c5', 'a4', 'a4', silence,
sequence(silence(), 'd5'), sequence(silence(), 'f5'), 'a5', sequence('g5', 'f5'), sequence(silence, 'd5'), sequence(silence, 'f5'), 'a5', sequence('g5', 'f5'),
'e5', sequence(silence(), 'c5'), 'e5', sequence('d5', 'c5'), 'e5', sequence(silence, 'c5'), 'e5', sequence('d5', 'c5'),
'b4', sequence('b4', 'c5'), 'd5', 'e5', 'b4', sequence('b4', 'c5'), 'd5', 'e5',
'c5', 'a4', 'a4', silence()), 'c5', 'a4', 'a4', silence),
sequence( sequence(
'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3', 'e2', 'e3',
'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3', 'a2', 'a3',

View File

@ -11,6 +11,19 @@ function flatten(arr) {
var id = a => a var id = a => a
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
}
else {
return function(...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
}
// Returns the start of the cycle. // Returns the start of the cycle.
Fraction.prototype.sam = function() { Fraction.prototype.sam = function() {
return Fraction(Math.floor(this)) return Fraction(Math.floor(this))
@ -42,6 +55,10 @@ Fraction.prototype.gte = function(other) {
return this.compare(other) >= 0 return this.compare(other) >= 0
} }
Fraction.prototype.eq = function(other) {
return this.compare(other) == 0
}
Fraction.prototype.max = function(other) { Fraction.prototype.max = function(other) {
return this.gt(other) ? this : other return this.gt(other) ? this : other
} }
@ -51,7 +68,11 @@ Fraction.prototype.min = function(other) {
} }
Fraction.prototype.show = function () { Fraction.prototype.show = function () {
return this.n + "/" + this.d return this.n + "/" + this.d
}
Fraction.prototype.or = function(other) {
return this.eq(0) ? other : this
} }
class TimeSpan { class TimeSpan {
@ -225,6 +246,10 @@ class Pattern {
return this.withEventSpan(span => span.withTime(func)) return this.withEventSpan(span => span.withTime(func))
} }
_withEvents(func) {
return new Pattern(span => func(this.query(span)))
}
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'.
@ -326,6 +351,10 @@ class Pattern {
return this.query(new TimeSpan(Fraction(0), Fraction(1))) return this.query(new TimeSpan(Fraction(0), Fraction(1)))
} }
_sortEventsByPart() {
return this._withEvents(events => events.sort((a,b) => a.part.begin.sub(b.part.begin).or(a.part.end.sub(b.part.end)).or(a.whole.begin.sub(b.whole.begin).or(a.whole.end.sub(b.whole.end)))))
}
_opleft(other, func) { _opleft(other, func) {
return this.fmap(func).appLeft(reify(other)) return this.fmap(func).appLeft(reify(other))
} }
@ -353,7 +382,7 @@ class Pattern {
var match = function (a) { var match = function (a) {
return func(a.value).query(a.part).map(b => withWhole(a, b)) return func(a.value).query(a.part).map(b => withWhole(a, b))
} }
return flatten(pat_val.query(span).map(match)) return flatten(pat_val.query(span).map(a => match(a)))
} }
return new Pattern(query) return new Pattern(query)
} }
@ -430,19 +459,15 @@ class Pattern {
var false_pat = binary_pat._filterValues(val => !val) var false_pat = binary_pat._filterValues(val => !val)
var with_pat = true_pat.fmap(_ => y => y).appRight(func(this)) var with_pat = true_pat.fmap(_ => y => y).appRight(func(this))
var without_pat = false_pat.fmap(_ => y => y).appRight(this) var without_pat = false_pat.fmap(_ => y => y).appRight(this)
return stack([with_pat, without_pat]) return stack(with_pat, without_pat)
} }
off(time_pat, func) { off(time_pat, func) {
return stack([this, func(this._early(time_pat))]) return stack([this, func(this._early(time_pat))])
} }
off(time_pat, func) {
return stack(this, func(this.early(time_pat)))
}
every(n, func) { every(n, func) {
const pats = Array(n-1).fill(this) var pats = Array(n-1).fill(this)
pats.unshift(this) pats.unshift(this)
return slowcat(...pats) return slowcat(...pats)
} }
@ -485,12 +510,7 @@ class Pattern {
} }
} }
function reify(thing) { const silence = new Pattern(_ => [])
if (thing.constructor.name == "Pattern") {
return thing
}
return pure(thing)
}
function pure(value) { function pure(value) {
// Returns a pattern that repeats the given value once per cycle // Returns a pattern that repeats the given value once per cycle
@ -504,8 +524,15 @@ function steady(value) {
return new Pattern(span => Hap(undefined, span, value)) return new Pattern(span => Hap(undefined, span, value))
} }
function reify(thing) {
if (thing.constructor.name == "Pattern") {
return thing
}
return pure(thing)
}
function stack(...pats) { function stack(...pats) {
pats = pats.map(reify) var pats = pats.map(pat => reify(pat))
var query = function(span) { var query = function(span) {
return flatten(pats.map(pat => pat.query(span))) return flatten(pats.map(pat => pat.query(span)))
} }
@ -537,7 +564,7 @@ function cat(...pats) {
function _sequenceCount(x) { function _sequenceCount(x) {
if(Array.isArray(x)) { if(Array.isArray(x)) {
if (x.length == 0) { if (x.length == 0) {
return [silence(),0] return [silence,0]
} }
if (x.length == 1) { if (x.length == 1) {
return _sequenceCount(x[0]) return _sequenceCount(x[0])
@ -552,9 +579,9 @@ function sequence(...xs) {
} }
function polymeter(steps=0, ...args) { function polymeter(steps=0, ...args) {
var seqs = args.map(_sequenceCount) var seqs = args.map(a => _sequenceCount(a))
if (seqs.length == 0) { if (seqs.length == 0) {
return silence() return silence
} }
if (steps == 0) { if (steps == 0) {
steps = seqs[0][1] steps = seqs[0][1]
@ -574,25 +601,25 @@ function polymeter(steps=0, ...args) {
return stack(pats) return stack(pats)
} }
function silence() { // alias
return new Pattern(_ => []) function pm(args) {
polymeter(args)
} }
// # alias function polyrhythm(...xs) {
// pm = polymeter var seqs = xs.map(a => sequence(a))
// def polyrhythm(*xs): if (seqs.length == 0) {
// seqs = [sequence(x) for x in xs] return silence
}
// if len(seqs) == 0: return stack(...seqs)
// return silence() }
// return stack(seqs)
// # alias
// pr = polyrhythm
// alias
function pr(args) {
polyrhythm(args)
}
export {Fraction, TimeSpan, Hap, Pattern, export {Fraction, TimeSpan, Hap, Pattern,
pure, reify, stack, slowcat, fastcat, cat, sequence, polymeter, silence} pure, stack, slowcat, fastcat, cat, sequence, polymeter, pm, polyrhythm, pr, reify, silence}

View File

@ -2,7 +2,7 @@ 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} from "../strudel.mjs"; import {TimeSpan, Hap, Pattern, pure, stack, fastcat, slowcat, cat, sequence, polyrhythm} from "../strudel.mjs";
describe('TimeSpan', function() { describe('TimeSpan', function() {
describe('equals()', function() { describe('equals()', function() {
@ -78,7 +78,7 @@ describe('Pattern', function() {
}) })
describe('stack()', function () { describe('stack()', function () {
it('Can stack things', function () { it('Can stack things', function () {
assert.deepStrictEqual(stack([pure("a"), pure("b"), pure("c")]).firstCycle.map(h => h.value), ["a", "b", "c"]) assert.deepStrictEqual(stack(pure("a"), pure("b"), pure("c")).firstCycle.map(h => h.value), ["a", "b", "c"])
}) })
}) })
describe('_fast()', function () { describe('_fast()', function () {
@ -120,9 +120,25 @@ describe('Pattern', function() {
assert.deepStrictEqual(fastcat([pure("a"), pure("b"), pure("c")]).rev().firstCycle.sort((a,b) => a.part.begin.sub(b.part.begin)).map(a => a.value), ["c", "b","a"]) assert.deepStrictEqual(fastcat([pure("a"), pure("b"), pure("c")]).rev().firstCycle.sort((a,b) => a.part.begin.sub(b.part.begin)).map(a => a.value), ["c", "b","a"])
}) })
}) })
describe('sequence()', function () { describe('sequence()', () => {
it('Can work like fastcat', function () { it('Can work like fastcat', () => {
assert.deepStrictEqual(sequence(1,2,3).firstCycle, fastcat([pure(1), pure(2), pure(3)]).firstCycle) assert.deepStrictEqual(sequence(1,2,3).firstCycle, fastcat([pure(1), pure(2), pure(3)]).firstCycle)
}) })
}) })
describe('polyrhythm()', () => {
it('Can layer up cycles', () => {
assert.deepStrictEqual(
polyrhythm(["a","b"],["c"])._sortEventsByPart().firstCycle,
stack([fastcat(pure("a"),pure("b")),pure("c")])._sortEventsByPart().firstCycle
)
})
})
describe('every()', () => {
it('Can apply a function every 3rd time', () => {
assert.deepStrictEqual(
pure("a").every(3, x => x._fast(2)._fast(3)).firstCycle,
sequence(sequence("a", "a"), "a", "a").firstCycle
)
})
})
}) })