mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-25 20:48:27 +00:00
Merge pull request #88 from tidalcycles/reset
Reset, Restart and other composers
This commit is contained in:
commit
cf79083edb
@ -86,6 +86,10 @@ export class Hap {
|
|||||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showWhole() {
|
||||||
|
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
combineContext(b) {
|
combineContext(b) {
|
||||||
const a = this;
|
const a = this;
|
||||||
return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) };
|
return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) };
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import TimeSpan from './timespan.mjs';
|
|||||||
import Fraction from './fraction.mjs';
|
import Fraction from './fraction.mjs';
|
||||||
import Hap from './hap.mjs';
|
import Hap from './hap.mjs';
|
||||||
import State from './state.mjs';
|
import State from './state.mjs';
|
||||||
|
import { unionWithObj } from './value.mjs';
|
||||||
|
|
||||||
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod } from './util.mjs';
|
import { isNote, toMidi, compose, removeUndefineds, flatten, id, listRange, curry, mod } from './util.mjs';
|
||||||
import drawLine from './drawLine.mjs';
|
import drawLine from './drawLine.mjs';
|
||||||
@ -142,6 +143,11 @@ export class Pattern {
|
|||||||
return this._filterEvents((hap) => hap.hasOnset());
|
return this._filterEvents((hap) => hap.hasOnset());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
discreteOnly() {
|
||||||
|
// removes continuous events that don't have a 'whole' timespan
|
||||||
|
return this._filterEvents((hap) => hap.whole);
|
||||||
|
}
|
||||||
|
|
||||||
_appWhole(whole_func, pat_val) {
|
_appWhole(whole_func, pat_val) {
|
||||||
// Assumes 'this' is a pattern of functions, and given a function to
|
// Assumes 'this' is a pattern of functions, and given a function to
|
||||||
// resolve wholes, applies a given pattern of values to that
|
// resolve wholes, applies a given pattern of values to that
|
||||||
@ -254,26 +260,34 @@ export class Pattern {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_opLeft(other, func) {
|
_opIn(other, func) {
|
||||||
return this.fmap(func).appLeft(reify(other));
|
return this.fmap(func).appLeft(reify(other));
|
||||||
}
|
}
|
||||||
_opRight(other, func) {
|
_opOut(other, func) {
|
||||||
return this.fmap(func).appRight(reify(other));
|
return this.fmap(func).appRight(reify(other));
|
||||||
}
|
}
|
||||||
_opBoth(other, func) {
|
_opMix(other, func) {
|
||||||
return this.fmap(func).appBoth(reify(other));
|
return this.fmap(func).appBoth(reify(other));
|
||||||
}
|
}
|
||||||
_opSqueeze(other, func) {
|
_opSqueeze(other, func) {
|
||||||
const otherPat = reify(other);
|
const otherPat = reify(other);
|
||||||
return this.fmap((a) => otherPat.fmap((b) => func(a)(b)))._squeezeJoin();
|
return this.fmap((a) => otherPat.fmap((b) => func(a)(b)))._squeezeJoin();
|
||||||
}
|
}
|
||||||
_opSqueezeFlip(other, func) {
|
_opSqueezeOut(other, func) {
|
||||||
const thisPat = this;
|
const thisPat = this;
|
||||||
const otherPat = reify(other);
|
const otherPat = reify(other);
|
||||||
return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a)))._squeezeJoin();
|
return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a)))._squeezeJoin();
|
||||||
}
|
}
|
||||||
|
_opTrig(other, func) {
|
||||||
|
const otherPat = reify(other);
|
||||||
|
return otherPat.fmap((b) => this.fmap((a) => func(a)(b)))._trigJoin();
|
||||||
|
}
|
||||||
|
_opTrigzero(other, func) {
|
||||||
|
const otherPat = reify(other);
|
||||||
|
return otherPat.fmap((b) => this.fmap((a) => func(a)(b)))._TrigzeroJoin();
|
||||||
|
}
|
||||||
|
|
||||||
_asNumber(silent = false) {
|
_asNumber(dropfails = false, softfail = false) {
|
||||||
return this._withEvent((event) => {
|
return this._withEvent((event) => {
|
||||||
const asNumber = Number(event.value);
|
const asNumber = Number(event.value);
|
||||||
if (!isNaN(asNumber)) {
|
if (!isNaN(asNumber)) {
|
||||||
@ -290,11 +304,21 @@ export class Pattern {
|
|||||||
// set context type to midi to let the player know its meant as midi number and not as frequency
|
// set context type to midi to let the player know its meant as midi number and not as frequency
|
||||||
return new Hap(event.whole, event.part, toMidi(event.value), { ...event.context, type: 'midi' });
|
return new Hap(event.whole, event.part, toMidi(event.value), { ...event.context, type: 'midi' });
|
||||||
}
|
}
|
||||||
if (!silent) {
|
if (dropfail) {
|
||||||
throw new Error('cannot parse as number: "' + event.value + '"');
|
// return 'nothing'
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
return event.withValue(() => undefined); // silent error
|
if (softfail) {
|
||||||
})._removeUndefineds();
|
// return original hap
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
throw new Error('cannot parse as number: "' + event.value + '"');
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
if (dropfail) {
|
||||||
|
return result._removeUndefineds();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
round() {
|
round() {
|
||||||
@ -389,10 +413,47 @@ export class Pattern {
|
|||||||
return this.innerBind(id);
|
return this.innerBind(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flatterns patterns of patterns, by retriggering/resetting inner patterns at onsets of outer pattern events
|
||||||
|
_trigJoin(cycleZero = false) {
|
||||||
|
const pat_of_pats = this;
|
||||||
|
return new Pattern((state) => {
|
||||||
|
return (
|
||||||
|
pat_of_pats
|
||||||
|
// drop continuous events from the outer pattern.
|
||||||
|
.discreteOnly()
|
||||||
|
.query(state)
|
||||||
|
.map((outer_hap) => {
|
||||||
|
return (
|
||||||
|
outer_hap.value
|
||||||
|
// trig = align the inner pattern cycle start to outer pattern events
|
||||||
|
// Trigzero = align the inner pattern cycle zero to outer pattern events
|
||||||
|
.late(cycleZero ? outer_hap.whole.begin : outer_hap.whole.begin.cyclePos())
|
||||||
|
.query(state)
|
||||||
|
.map((inner_hap) =>
|
||||||
|
new Hap(
|
||||||
|
// Supports continuous events in the inner pattern
|
||||||
|
inner_hap.whole ? inner_hap.whole.intersection(outer_hap.whole) : undefined,
|
||||||
|
inner_hap.part.intersection(outer_hap.part),
|
||||||
|
inner_hap.value,
|
||||||
|
).setContext(outer_hap.combineContext(inner_hap)),
|
||||||
|
)
|
||||||
|
// Drop events that didn't intersect
|
||||||
|
.filter((hap) => hap.part)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_TrigzeroJoin() {
|
||||||
|
return this._trigJoin(true);
|
||||||
|
}
|
||||||
|
|
||||||
_squeezeJoin() {
|
_squeezeJoin() {
|
||||||
const pat_of_pats = this;
|
const pat_of_pats = this;
|
||||||
function query(state) {
|
function query(state) {
|
||||||
const haps = pat_of_pats.query(state);
|
const haps = pat_of_pats.discreteOnly().query(state);
|
||||||
function flatHap(outerHap) {
|
function flatHap(outerHap) {
|
||||||
const pat = outerHap.value._compressSpan(outerHap.wholeOrPart().cycleArc());
|
const pat = outerHap.value._compressSpan(outerHap.wholeOrPart().cycleArc());
|
||||||
const innerHaps = pat.query(state.setSpan(outerHap.part));
|
const innerHaps = pat.query(state.setSpan(outerHap.part));
|
||||||
@ -548,23 +609,23 @@ export class Pattern {
|
|||||||
return this._zoom(0, t)._slow(t);
|
return this._zoom(0, t)._slow(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct(...binary_pats) {
|
// struct(...binary_pats) {
|
||||||
// Re structure the pattern according to a binary pattern (false values are dropped)
|
// // Re structure the pattern according to a binary pattern (false values are dropped)
|
||||||
const binary_pat = sequence(binary_pats);
|
// const binary_pat = sequence(binary_pats);
|
||||||
return binary_pat
|
// return binary_pat
|
||||||
.fmap((b) => (val) => b ? val : undefined)
|
// .fmap((b) => (val) => b ? val : undefined)
|
||||||
.appLeft(this)
|
// .appLeft(this)
|
||||||
._removeUndefineds();
|
// ._removeUndefineds();
|
||||||
}
|
// }
|
||||||
|
|
||||||
mask(...binary_pats) {
|
// mask(...binary_pats) {
|
||||||
// Only let through parts of pattern corresponding to true values in the given binary pattern
|
// // Only let through parts of pattern corresponding to true values in the given binary pattern
|
||||||
const binary_pat = sequence(binary_pats);
|
// const binary_pat = sequence(binary_pats);
|
||||||
return binary_pat
|
// return binary_pat
|
||||||
.fmap((b) => (val) => b ? val : undefined)
|
// .fmap((b) => (val) => b ? val : undefined)
|
||||||
.appRight(this)
|
// .appRight(this)
|
||||||
._removeUndefineds();
|
// ._removeUndefineds();
|
||||||
}
|
// }
|
||||||
|
|
||||||
_color(color) {
|
_color(color) {
|
||||||
return this._withContext((context) => ({ ...context, color }));
|
return this._withContext((context) => ({ ...context, color }));
|
||||||
@ -754,49 +815,107 @@ export class Pattern {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pattern composers
|
// TODO - adopt value.mjs fully..
|
||||||
const composers = {
|
function _composeOp(a, b, func) {
|
||||||
set: [
|
function _nonFunctionObject(x) {
|
||||||
(a) => (b) => {
|
return x instanceof Object && !(x instanceof Function);
|
||||||
// If an object is involved, do a union, discarding matching keys from a.
|
}
|
||||||
// Otherwise, just return b.
|
if (_nonFunctionObject(a) || _nonFunctionObject(b)) {
|
||||||
if (a instanceof Object || b instanceof Object) {
|
if (!_nonFunctionObject(a)) {
|
||||||
if (!a instanceof Object) {
|
a = { value: a };
|
||||||
a = { value: a };
|
}
|
||||||
}
|
if (!_nonFunctionObject(b)) {
|
||||||
if (!b instanceof Object) {
|
b = { value: b };
|
||||||
b = { value: b };
|
}
|
||||||
}
|
return unionWithObj(a, b, func);
|
||||||
return Object.assign({}, a, b);
|
}
|
||||||
}
|
return func(a, b);
|
||||||
return b;
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
],
|
|
||||||
add: [(a) => (b) => a + b, (x) => x._asNumber()],
|
|
||||||
sub: [(a) => (b) => a - b, (x) => x._asNumber()],
|
|
||||||
mul: [(a) => (b) => a * b, (x) => x._asNumber()],
|
|
||||||
div: [(a) => (b) => a / b, (x) => x._asNumber()],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [name, op] of Object.entries(composers)) {
|
|
||||||
Pattern.prototype[name] = function (...other) {
|
|
||||||
return op[1](this)._opLeft(sequence(other), op[0]);
|
|
||||||
};
|
|
||||||
Pattern.prototype[name + 'Flip'] = function (...other) {
|
|
||||||
return op[1](this)._opRight(sequence(other), op[0]);
|
|
||||||
};
|
|
||||||
Pattern.prototype[name + 'Sect'] = function (...other) {
|
|
||||||
return op[1](this)._opBoth(sequence(other), op[0]);
|
|
||||||
};
|
|
||||||
Pattern.prototype[name + 'Squeeze'] = function (...other) {
|
|
||||||
return op[1](this)._opSqueeze(sequence(other), op[0]);
|
|
||||||
};
|
|
||||||
Pattern.prototype[name + 'SqueezeFlip'] = function (...other) {
|
|
||||||
return op[1](this)._opSqueezeFlip(sequence(other), op[0]);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make composers
|
||||||
|
(function() {
|
||||||
|
const num = (pat) => pat._asNumber();
|
||||||
|
const numOrString = (pat) => pat._asNumber(false, true);
|
||||||
|
|
||||||
|
// pattern composers
|
||||||
|
const composers = {
|
||||||
|
set: [(a, b) => b],
|
||||||
|
keep: [(a, b) => a],
|
||||||
|
keepif: [(a, b) => (b ? a : undefined)],
|
||||||
|
|
||||||
|
// numerical functions
|
||||||
|
add: [(a, b) => a + b, numOrString], // support string concatenation
|
||||||
|
sub: [(a, b) => a - b, num],
|
||||||
|
mul: [(a, b) => a * b, num],
|
||||||
|
div: [(a, b) => a / b, num],
|
||||||
|
mod: [mod, num],
|
||||||
|
pow: [Math.pow, num],
|
||||||
|
_and: [(a, b) => a & b, num],
|
||||||
|
_or: [(a, b) => a | b, num],
|
||||||
|
_xor: [(a, b) => a ^ b, num],
|
||||||
|
_lshift: [(a, b) => a << b, num],
|
||||||
|
_rshift: [(a, b) => a >> b, num],
|
||||||
|
|
||||||
|
// TODO - force numerical comparison if both look like numbers?
|
||||||
|
lt: [(a, b) => a < b],
|
||||||
|
gt: [(a, b) => a > b],
|
||||||
|
lte: [(a, b) => a <= b],
|
||||||
|
gte: [(a, b) => a >= b],
|
||||||
|
eq: [(a, b) => a == b],
|
||||||
|
eqt: [(a, b) => a === b],
|
||||||
|
ne: [(a, b) => a != b],
|
||||||
|
net: [(a, b) => a !== b],
|
||||||
|
and: [(a, b) => a && b],
|
||||||
|
or: [(a, b) => a || b],
|
||||||
|
|
||||||
|
// bitwise ops
|
||||||
|
func: [(a, b) => b(a)],
|
||||||
|
};
|
||||||
|
|
||||||
|
// generate methods to do what and how
|
||||||
|
for (const [what, [op, preprocess]] of Object.entries(composers)) {
|
||||||
|
for (const how of ['In', 'Out', 'Mix', 'Squeeze', 'SqueezeOut', 'Trig', 'Trigzero']) {
|
||||||
|
Pattern.prototype[what + how] = function (...other) {
|
||||||
|
var pat = this;
|
||||||
|
other = sequence(other);
|
||||||
|
if (preprocess) {
|
||||||
|
pat = preprocess(pat);
|
||||||
|
other = preprocess(other);
|
||||||
|
}
|
||||||
|
var result = pat['_op' + how](other, (a) => (b) => _composeOp(a, b, op));
|
||||||
|
// hack to remove undefs when doing 'keepif'
|
||||||
|
if (what === 'keepif') {
|
||||||
|
result = result._removeUndefineds();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
if (how === 'Squeeze') {
|
||||||
|
// support 'squeezeIn' longhand
|
||||||
|
Pattern.prototype[what + 'SqueezeIn'] = Pattern.prototype[what + how];
|
||||||
|
}
|
||||||
|
if (how === 'In') {
|
||||||
|
// default how to 'in', e.g. add == addIn
|
||||||
|
Pattern.prototype[what] = Pattern.prototype[what + how];
|
||||||
|
} else {
|
||||||
|
// default what to 'set', e.g. squeeze = setSqueeze
|
||||||
|
if (what === 'set') {
|
||||||
|
Pattern.prototype[how.toLowerCase()] = Pattern.prototype[what + how];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// binary composers
|
||||||
|
Pattern.prototype.struct = Pattern.prototype.keepifOut;
|
||||||
|
Pattern.prototype.structAll = Pattern.prototype.keepOut;
|
||||||
|
Pattern.prototype.mask = Pattern.prototype.keepifIn;
|
||||||
|
Pattern.prototype.maskAll = Pattern.prototype.keepIn;
|
||||||
|
Pattern.prototype.reset = Pattern.prototype.keepifTrig;
|
||||||
|
Pattern.prototype.resetAll = Pattern.prototype.keepTrig;
|
||||||
|
Pattern.prototype.restart = Pattern.prototype.keepifTrigzero;
|
||||||
|
Pattern.prototype.restartAll = Pattern.prototype.keepTrigzero;
|
||||||
|
})();
|
||||||
|
|
||||||
// methods of Pattern that get callable factories
|
// methods of Pattern that get callable factories
|
||||||
Pattern.prototype.patternified = [
|
Pattern.prototype.patternified = [
|
||||||
'apply',
|
'apply',
|
||||||
|
|||||||
@ -152,17 +152,32 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('add()', function () {
|
describe('add()', function () {
|
||||||
it('Can add things', function () {
|
it('can structure In()', function () {
|
||||||
assert.equal(pure(3).add(pure(4)).query(st(0, 1))[0].value, 7);
|
assert.equal(pure(3).add(pure(4)).query(st(0, 1))[0].value, 7);
|
||||||
|
assert.equal(pure(3).addIn(pure(4)).query(st(0, 1))[0].value, 7);
|
||||||
});
|
});
|
||||||
});
|
it('can structure Out()', () => {
|
||||||
describe('addFlip()', () => {
|
sameFirst(sequence(1, 2).addOut(4), sequence(5, 6).struct(true));
|
||||||
it('Can add things with structure from second pattern', () => {
|
|
||||||
sameFirst(sequence(1, 2).addFlip(4), sequence(5, 6).struct(true));
|
|
||||||
});
|
});
|
||||||
});
|
it('can Mix() structure', () => {
|
||||||
describe('addSqueeze()', () => {
|
assert.deepStrictEqual(sequence(1, 2).addMix(silence, 5, silence).firstCycle(), [
|
||||||
it('Can add while squeezing the second pattern inside the events of the first', () => {
|
hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 6),
|
||||||
|
hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 7),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('can Trig() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).addTrig(20, 30).early(2),
|
||||||
|
sequence(26, 27, 36, 37),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('can Trigzero() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).addTrigzero(20, 30).early(2),
|
||||||
|
sequence(21, 22, 31, 32),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('can Squeeze() structure', () => {
|
||||||
sameFirst(
|
sameFirst(
|
||||||
sequence(1, [2, 3]).addSqueeze(sequence(10, 20, 30)),
|
sequence(1, [2, 3]).addSqueeze(sequence(10, 20, 30)),
|
||||||
sequence(
|
sequence(
|
||||||
@ -174,15 +189,100 @@ describe('Pattern', function () {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
it('can SqueezeOut() structure', () => {
|
||||||
describe('addSqueezeFlip()', () => {
|
|
||||||
it('Can add while squeezing the first pattern inside the events of the second', () => {
|
|
||||||
sameFirst(
|
sameFirst(
|
||||||
sequence(1, [2, 3]).addSqueezeFlip(10, 20, 30),
|
sequence(1, [2, 3]).addSqueezeOut(10, 20, 30),
|
||||||
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
|
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('keep()', function () {
|
||||||
|
it('can structure In()', function () {
|
||||||
|
assert.equal(pure(3).keep(pure(4)).query(st(0, 1))[0].value, 3);
|
||||||
|
assert.equal(pure(3).keepIn(pure(4)).query(st(0, 1))[0].value, 3);
|
||||||
|
});
|
||||||
|
it('can structure Out()', () => {
|
||||||
|
sameFirst(sequence(1, 2).keepOut(4), sequence(1, 2).struct(true));
|
||||||
|
});
|
||||||
|
it('can Mix() structure', () => {
|
||||||
|
assert.deepStrictEqual(sequence(1, 2).keepMix(silence, 5, silence).firstCycle(), [
|
||||||
|
hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1),
|
||||||
|
hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('can Trig() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepTrig(20, 30).early(2),
|
||||||
|
sequence(6, 7, 6, 7),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('can Trigzero() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepTrigzero(20, 30).early(2),
|
||||||
|
sequence(1, 2, 1, 2),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('can Squeeze() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
sequence(1, [2, 3]).keepSqueeze(sequence(10, 20, 30)),
|
||||||
|
sequence(
|
||||||
|
[1, 1, 1],
|
||||||
|
[
|
||||||
|
[2, 2, 2],
|
||||||
|
[3, 3, 3],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('can SqueezeOut() structure', () => {
|
||||||
|
sameFirst(sequence(1, [2, 3]).keepSqueezeOut(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('keepif()', function () {
|
||||||
|
it('can structure In()', function () {
|
||||||
|
sameFirst(sequence(3, 4).keepif(true, false), sequence(3, silence));
|
||||||
|
sameFirst(sequence(3, 4).keepifIn(true, false), sequence(3, silence));
|
||||||
|
});
|
||||||
|
it('can structure Out()', () => {
|
||||||
|
sameFirst(pure(1).keepifOut(true, false), sequence(1, silence));
|
||||||
|
});
|
||||||
|
it('can Mix() structure', () => {
|
||||||
|
assert.deepStrictEqual(sequence(1, 2).keepifMix(false, true, false).firstCycle(), [
|
||||||
|
hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1),
|
||||||
|
hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('can Trig() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepifTrig(false, true).early(2),
|
||||||
|
sequence(silence, silence, 6, 7),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('can Trigzero() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepifTrigzero(false, true).early(2),
|
||||||
|
sequence(silence, silence, 1, 2),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('can Squeeze() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
sequence(1, [2, 3]).keepifSqueeze(sequence(true, true, false)),
|
||||||
|
sequence(
|
||||||
|
[1, 1, silence],
|
||||||
|
[
|
||||||
|
[2, 2, silence],
|
||||||
|
[3, 3, silence],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('can SqueezeOut() structure', () => {
|
||||||
|
sameFirst(
|
||||||
|
sequence(1, [2, 3]).keepifSqueezeOut(true, true, false),
|
||||||
|
sequence([1, [2, 3]], [1, [2, 3]], silence),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('sub()', function () {
|
describe('sub()', function () {
|
||||||
it('Can subtract things', function () {
|
it('Can subtract things', function () {
|
||||||
assert.equal(pure(3).sub(pure(4)).query(st(0, 1))[0].value, -1);
|
assert.equal(pure(3).sub(pure(4)).query(st(0, 1))[0].value, -1);
|
||||||
@ -214,9 +314,9 @@ describe('Pattern', function () {
|
|||||||
it('Can set things with plain values', function () {
|
it('Can set things with plain values', function () {
|
||||||
sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3));
|
sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3));
|
||||||
});
|
});
|
||||||
describe('setFlip()', () => {
|
describe('setOut()', () => {
|
||||||
it('Can set things with structure from second pattern', () => {
|
it('Can set things with structure from second pattern', () => {
|
||||||
sameFirst(sequence(1, 2).setFlip(4), pure(4).mask(true, true));
|
sameFirst(sequence(1, 2).setOut(4), pure(4).mask(true, true));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('setSqueeze()', () => {
|
describe('setSqueeze()', () => {
|
||||||
@ -254,10 +354,7 @@ describe('Pattern', function () {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('Can stack subpatterns', function () {
|
it('Can stack subpatterns', function () {
|
||||||
sameFirst(
|
sameFirst(stack('a', ['b', 'c']), stack('a', sequence('b', 'c')));
|
||||||
stack('a', ['b','c']),
|
|
||||||
stack('a', sequence('b', 'c')),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('_fast()', function () {
|
describe('_fast()', function () {
|
||||||
@ -388,11 +485,7 @@ describe('Pattern', function () {
|
|||||||
});
|
});
|
||||||
describe('fastcat()', function () {
|
describe('fastcat()', function () {
|
||||||
it('Can go into negative time', function () {
|
it('Can go into negative time', function () {
|
||||||
sameFirst(
|
sameFirst(fastcat('a', 'b', 'c').late(1000000), fastcat('a', 'b', 'c'));
|
||||||
fastcat('a','b','c')
|
|
||||||
.late(1000000),
|
|
||||||
fastcat('a','b','c'),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('slowcat()', function () {
|
describe('slowcat()', function () {
|
||||||
@ -425,12 +518,9 @@ describe('Pattern', function () {
|
|||||||
['c'],
|
['c'],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it ('Can cat subpatterns', () => {
|
it('Can cat subpatterns', () => {
|
||||||
sameFirst(
|
sameFirst(slowcat('a', ['b', 'c']).fast(4), sequence('a', ['b', 'c']).fast(2));
|
||||||
slowcat('a', ['b','c']).fast(4),
|
});
|
||||||
sequence('a', ['b', 'c']).fast(2)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
describe('rev()', function () {
|
describe('rev()', function () {
|
||||||
it('Can reverse things', function () {
|
it('Can reverse things', function () {
|
||||||
|
|||||||
@ -34,12 +34,16 @@ export class TimeSpan {
|
|||||||
return spans;
|
return spans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return this.end.sub(this.begin);
|
||||||
|
}
|
||||||
|
|
||||||
cycleArc() {
|
cycleArc() {
|
||||||
// Shifts a timespan to one of equal duration that starts within cycle zero.
|
// Shifts a timespan to one of equal duration that starts within cycle zero.
|
||||||
// (Note that the output timespan probably does not start *at* Time 0 --
|
// (Note that the output timespan probably does not start *at* Time 0 --
|
||||||
// that only happens when the input Arc starts at an integral Time.)
|
// that only happens when the input Arc starts at an integral Time.)
|
||||||
const b = this.begin.cyclePos();
|
const b = this.begin.cyclePos();
|
||||||
const e = b.add(this.end.sub(this.begin));
|
const e = b.add(this.duration);
|
||||||
return new TimeSpan(b, e);
|
return new TimeSpan(b, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +97,7 @@ export class TimeSpan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
midpoint() {
|
midpoint() {
|
||||||
return this.begin.add(this.end.sub(this.begin).div(Fraction(2)));
|
return this.begin.add(this.duration.div(Fraction(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other) {
|
equals(other) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
|
|
||||||
import { curry } from './util.mjs';
|
import { curry } from './util.mjs';
|
||||||
|
|
||||||
function unionWithObj(a, b, func) {
|
export function unionWithObj(a, b, func) {
|
||||||
const common = Object.keys(a).filter((k) => Object.keys(b).includes(k));
|
const common = Object.keys(a).filter((k) => Object.keys(b).includes(k));
|
||||||
return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])])));
|
return Object.assign({}, a, b, Object.fromEntries(common.map((k) => [k, func(a[k], b[k])])));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -809,3 +809,10 @@ export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
|
|||||||
.slow(2)
|
.slow(2)
|
||||||
.tone((await piano()).toDestination())
|
.tone((await piano()).toDestination())
|
||||||
//.pianoroll({maxMidi:160})`;
|
//.pianoroll({maxMidi:160})`;
|
||||||
|
|
||||||
|
export const bossa = `
|
||||||
|
const scales = sequence('C minor', ['D locrian', 'G phrygian'], 'Bb2 minor', ['C locrian','F phrygian']).slow(4)
|
||||||
|
stack(
|
||||||
|
"<Cm7 [Dm7b5 G7b9] Bbm7 [Cm7b5 F7b9]>".fast(2).struct("x ~ x@3 x ~ x ~ ~ ~ x ~ x@3".late(1/8)).early(1/8).slow(2).voicings(),
|
||||||
|
"[~ [0 ~]] 0 [~ [4 ~]] 4".sub(7).restart(scales).scale(scales).early(.25)
|
||||||
|
).tone((await piano()).toDestination()).slow(2)`;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user