Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Felix Roos 2022-02-06 12:41:40 +01:00
commit 4437f7c89e
10 changed files with 136 additions and 64 deletions

View File

@ -1 +1,11 @@
# strudel
# strudel
## Local development
Run the REPL locally:
```bash
cd repl
npm install
npm run start
```

1
docs/CNAME Normal file
View File

@ -0,0 +1 @@
strudel.tidalcycles.org

View File

@ -6,6 +6,17 @@ function flatten(arr) {
return [].concat(...arr);
}
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));
};
}
};
}
Fraction.prototype.sam = function() {
return Fraction(Math.floor(this));
};
@ -27,6 +38,9 @@ Fraction.prototype.lte = function(other) {
Fraction.prototype.gte = function(other) {
return this.compare(other) >= 0;
};
Fraction.prototype.eq = function(other) {
return this.compare(other) == 0;
};
Fraction.prototype.max = function(other) {
return this.gt(other) ? this : other;
};
@ -36,6 +50,9 @@ Fraction.prototype.min = function(other) {
Fraction.prototype.show = function() {
return this.n + "/" + this.d;
};
Fraction.prototype.or = function(other) {
return this.eq(0) ? other : this;
};
class TimeSpan {
constructor(begin, end) {
this.begin = Fraction(begin);
@ -141,6 +158,9 @@ class Pattern {
withEventTime(func) {
return this.withEventSpan((span) => span.withTime(func));
}
_withEvents(func) {
return new Pattern((span) => func(this.query(span)));
}
withValue(func) {
return new Pattern((span) => this.query(span).map((hap) => hap.withValue(func)));
}
@ -220,6 +240,9 @@ class Pattern {
get firstCycle() {
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) {
return this.fmap(func).appLeft(reify(other));
}
@ -241,7 +264,7 @@ class Pattern {
var match = function(a) {
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(query2);
}
@ -288,16 +311,13 @@ class Pattern {
var false_pat = binary_pat._filterValues((val) => !val);
var with_pat = true_pat.fmap((_) => (y) => y).appRight(func(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) {
return stack([this, func(this._early(time_pat))]);
}
off(time_pat, func) {
return stack(this, func(this.early(time_pat)));
}
every(n, func) {
const pats = Array(n - 1).fill(this);
var pats = Array(n - 1).fill(this);
pats.unshift(this);
return slowcat(...pats);
}
@ -334,12 +354,7 @@ class Pattern {
return stack([left, func(right)]);
}
}
function reify(thing) {
if (thing.constructor.name == "Pattern") {
return thing;
}
return pure(thing);
}
const silence = new Pattern((_) => []);
function pure(value) {
function query2(span) {
return span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
@ -349,8 +364,14 @@ function pure(value) {
function steady(value) {
return new Pattern((span) => Hap(void 0, span, value));
}
function reify(thing) {
if (thing.constructor.name == "Pattern") {
return thing;
}
return pure(thing);
}
function stack(...pats) {
pats = pats.map(reify);
var pats = pats.map((pat) => reify(pat));
var query2 = function(span) {
return flatten(pats.map((pat) => pat.query(span)));
};
@ -373,7 +394,7 @@ function cat(...pats) {
function _sequenceCount(x) {
if (Array.isArray(x)) {
if (x.length == 0) {
return [silence(), 0];
return [silence, 0];
}
if (x.length == 1) {
return _sequenceCount(x[0]);
@ -386,9 +407,9 @@ function sequence(...xs) {
return _sequenceCount(xs)[0];
}
function polymeter(steps = 0, ...args) {
var seqs = args.map(_sequenceCount);
var seqs = args.map((a) => _sequenceCount(a));
if (seqs.length == 0) {
return silence();
return silence;
}
if (steps == 0) {
steps = seqs[0][1];
@ -406,8 +427,18 @@ function polymeter(steps = 0, ...args) {
}
return stack(pats);
}
function silence() {
return new Pattern((_) => []);
function pm(args) {
polymeter(args);
}
function polyrhythm(...xs) {
var seqs = xs.map((a) => sequence(a));
if (seqs.length == 0) {
return silence;
}
return stack(...seqs);
}
function pr(args) {
polyrhythm(args);
}
export {
Fraction,
@ -415,12 +446,15 @@ export {
Hap,
Pattern,
pure,
reify,
stack,
slowcat,
fastcat,
cat,
sequence,
polymeter,
pm,
polyrhythm,
pr,
reify,
silence
};

View File

@ -1 +1 @@
export default "/strudel/dist/logo.svg";
export default "/dist/logo.svg";

10
docs/dist/tunes.js vendored
View File

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

View File

@ -2,8 +2,8 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/strudel/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/strudel/global.css" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/global.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Strudel REPL" />
<title>Strudel REPL</title>
@ -11,6 +11,6 @@
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/strudel/dist/index.js"></script>
<script type="module" src="/dist/index.js"></script>
</body>
</html>

1
repl/public/CNAME Normal file
View File

@ -0,0 +1 @@
strudel.tidalcycles.org

View File

@ -35,6 +35,6 @@ export default {
buildOptions: {
/* ... */
out: '../docs',
baseUrl: '/strudel',
baseUrl: '/',
},
};

View File

@ -423,35 +423,51 @@ class Pattern {
return this.outerBind(id)
}
// def _patternify(method):
// def patterned(self, *args):
// pat_arg = sequence(*args)
// return pat_arg.fmap(lambda arg: method(self,arg)).outer_join()
// return patterned
_patternify(func) {
const pat = this
const patterned = function (...args) {
const pat_arg = sequence(...args)
return pat_arg.fmap(arg => func.call(pat,arg)).outerJoin()
}
return patterned
}
_fast(factor) {
var fastQuery = this.withQueryTime(t => t.mul(factor))
return fastQuery.withEventTime(t => t.div(factor))
}
// fast = _patternify(_fast)
fast(factor) {
return this._patternify(Pattern.prototype._fast)(factor)
}
_slow(factor) {
return this._fast(1/factor)
}
// slow = _patternify(_slow)
slow(factor) {
return this._patternify(Pattern.prototype._slow)(factor)
}
_early(offset) {
// Equivalent of Tidal's <~ operator
offset = Fraction(offset)
return this.withQueryTime(t => t.add(offset)).withEventTime(t => t.sub(offset))
}
// early = _patternify(_early)
early(factor) {
return this._patternify(Pattern.prototype._early)(factor)
}
_late(offset) {
// Equivalent of Tidal's ~> operator
return this._early(0-offset)
}
// late = _patternify(_late)
late(factor) {
return this._patternify(Pattern.prototype._late)(factor)
}
when(binary_pat, func) {
//binary_pat = sequence(binary_pat)

View File

@ -86,6 +86,16 @@ describe('Pattern', function() {
assert.equal(pure("a")._fast(2).firstCycle.length, 2)
})
})
describe('fast()', function () {
it('Makes things faster', function () {
assert.equal(pure("a").fast(2).firstCycle.length, 2)
})
it('Makes things faster, with a pattern of factors', function () {
assert.equal(pure("a").fast(sequence(1,4)).firstCycle.length, 3)
// not working..
// assert.deepStrictEqual(pure("a").fast(sequence(1,4)).firstCycle, sequence("a",sequence("a","a")).firstCycle)
})
})
describe('_slow()', function () {
it('Makes things slower', function () {
assert.deepStrictEqual(pure("a")._slow(2).firstCycle[0], new Hap(new TimeSpan(Fraction(0),Fraction(2)), new TimeSpan(Fraction(0), Fraction(1)), "a"))
@ -106,39 +116,39 @@ describe('Pattern', function() {
})
describe('fastcat()', function () {
it('Can concatenate two things', function () {
assert.deepStrictEqual(fastcat([pure("a"), pure("b")]).firstCycle.map(x => x.value), ["a", "b"])
assert.deepStrictEqual(fastcat(pure("a"), pure("b")).firstCycle.map(x => x.value), ["a", "b"])
})
})
describe('slowcat()', function () {
it('Can concatenate things slowly', function () {
assert.deepStrictEqual(slowcat([pure("a"), pure("b")]).firstCycle.map(x => x.value), ["a"])
assert.deepStrictEqual(slowcat([pure("a"), pure("b")])._early(1).firstCycle.map(x => x.value), ["b"])
assert.deepStrictEqual(slowcat(pure("a"), pure("b")).firstCycle.map(x => x.value), ["a"])
assert.deepStrictEqual(slowcat(pure("a"), pure("b"))._early(1).firstCycle.map(x => x.value), ["b"])
})
})
describe('rev()', function () {
it('Can reverse things', 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"])
})
})
describe('sequence()', () => {
it('Can work like fastcat', () => {
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
)
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()', () => {
// it('Can work like fastcat', () => {
// 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
// )
// })
// })
})