mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 13:48:34 +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}`;
|
||||
}
|
||||
|
||||
showWhole() {
|
||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${this.value}`;
|
||||
}
|
||||
|
||||
combineContext(b) {
|
||||
const a = this;
|
||||
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 Hap from './hap.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 drawLine from './drawLine.mjs';
|
||||
@ -142,6 +143,11 @@ export class Pattern {
|
||||
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) {
|
||||
// Assumes 'this' is a pattern of functions, and given a function to
|
||||
// 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));
|
||||
}
|
||||
_opRight(other, func) {
|
||||
_opOut(other, func) {
|
||||
return this.fmap(func).appRight(reify(other));
|
||||
}
|
||||
_opBoth(other, func) {
|
||||
_opMix(other, func) {
|
||||
return this.fmap(func).appBoth(reify(other));
|
||||
}
|
||||
_opSqueeze(other, func) {
|
||||
const otherPat = reify(other);
|
||||
return this.fmap((a) => otherPat.fmap((b) => func(a)(b)))._squeezeJoin();
|
||||
}
|
||||
_opSqueezeFlip(other, func) {
|
||||
_opSqueezeOut(other, func) {
|
||||
const thisPat = this;
|
||||
const otherPat = reify(other);
|
||||
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) => {
|
||||
const asNumber = Number(event.value);
|
||||
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
|
||||
return new Hap(event.whole, event.part, toMidi(event.value), { ...event.context, type: 'midi' });
|
||||
}
|
||||
if (!silent) {
|
||||
throw new Error('cannot parse as number: "' + event.value + '"');
|
||||
if (dropfail) {
|
||||
// return 'nothing'
|
||||
return undefined;
|
||||
}
|
||||
return event.withValue(() => undefined); // silent error
|
||||
})._removeUndefineds();
|
||||
if (softfail) {
|
||||
// return original hap
|
||||
return event;
|
||||
}
|
||||
throw new Error('cannot parse as number: "' + event.value + '"');
|
||||
return event;
|
||||
});
|
||||
if (dropfail) {
|
||||
return result._removeUndefineds();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
round() {
|
||||
@ -389,10 +413,47 @@ export class Pattern {
|
||||
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() {
|
||||
const pat_of_pats = this;
|
||||
function query(state) {
|
||||
const haps = pat_of_pats.query(state);
|
||||
const haps = pat_of_pats.discreteOnly().query(state);
|
||||
function flatHap(outerHap) {
|
||||
const pat = outerHap.value._compressSpan(outerHap.wholeOrPart().cycleArc());
|
||||
const innerHaps = pat.query(state.setSpan(outerHap.part));
|
||||
@ -548,23 +609,23 @@ export class Pattern {
|
||||
return this._zoom(0, t)._slow(t);
|
||||
}
|
||||
|
||||
struct(...binary_pats) {
|
||||
// Re structure the pattern according to a binary pattern (false values are dropped)
|
||||
const binary_pat = sequence(binary_pats);
|
||||
return binary_pat
|
||||
.fmap((b) => (val) => b ? val : undefined)
|
||||
.appLeft(this)
|
||||
._removeUndefineds();
|
||||
}
|
||||
// struct(...binary_pats) {
|
||||
// // Re structure the pattern according to a binary pattern (false values are dropped)
|
||||
// const binary_pat = sequence(binary_pats);
|
||||
// return binary_pat
|
||||
// .fmap((b) => (val) => b ? val : undefined)
|
||||
// .appLeft(this)
|
||||
// ._removeUndefineds();
|
||||
// }
|
||||
|
||||
mask(...binary_pats) {
|
||||
// Only let through parts of pattern corresponding to true values in the given binary pattern
|
||||
const binary_pat = sequence(binary_pats);
|
||||
return binary_pat
|
||||
.fmap((b) => (val) => b ? val : undefined)
|
||||
.appRight(this)
|
||||
._removeUndefineds();
|
||||
}
|
||||
// mask(...binary_pats) {
|
||||
// // Only let through parts of pattern corresponding to true values in the given binary pattern
|
||||
// const binary_pat = sequence(binary_pats);
|
||||
// return binary_pat
|
||||
// .fmap((b) => (val) => b ? val : undefined)
|
||||
// .appRight(this)
|
||||
// ._removeUndefineds();
|
||||
// }
|
||||
|
||||
_color(color) {
|
||||
return this._withContext((context) => ({ ...context, color }));
|
||||
@ -754,49 +815,107 @@ export class Pattern {
|
||||
}
|
||||
}
|
||||
|
||||
// pattern composers
|
||||
const composers = {
|
||||
set: [
|
||||
(a) => (b) => {
|
||||
// If an object is involved, do a union, discarding matching keys from a.
|
||||
// Otherwise, just return b.
|
||||
if (a instanceof Object || b instanceof Object) {
|
||||
if (!a instanceof Object) {
|
||||
a = { value: a };
|
||||
}
|
||||
if (!b instanceof Object) {
|
||||
b = { value: b };
|
||||
}
|
||||
return Object.assign({}, 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]);
|
||||
};
|
||||
// TODO - adopt value.mjs fully..
|
||||
function _composeOp(a, b, func) {
|
||||
function _nonFunctionObject(x) {
|
||||
return x instanceof Object && !(x instanceof Function);
|
||||
}
|
||||
if (_nonFunctionObject(a) || _nonFunctionObject(b)) {
|
||||
if (!_nonFunctionObject(a)) {
|
||||
a = { value: a };
|
||||
}
|
||||
if (!_nonFunctionObject(b)) {
|
||||
b = { value: b };
|
||||
}
|
||||
return unionWithObj(a, b, func);
|
||||
}
|
||||
return func(a, b);
|
||||
}
|
||||
|
||||
// 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
|
||||
Pattern.prototype.patternified = [
|
||||
'apply',
|
||||
|
||||
@ -152,17 +152,32 @@ describe('Pattern', 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).addIn(pure(4)).query(st(0, 1))[0].value, 7);
|
||||
});
|
||||
});
|
||||
describe('addFlip()', () => {
|
||||
it('Can add things with structure from second pattern', () => {
|
||||
sameFirst(sequence(1, 2).addFlip(4), sequence(5, 6).struct(true));
|
||||
it('can structure Out()', () => {
|
||||
sameFirst(sequence(1, 2).addOut(4), sequence(5, 6).struct(true));
|
||||
});
|
||||
});
|
||||
describe('addSqueeze()', () => {
|
||||
it('Can add while squeezing the second pattern inside the events of the first', () => {
|
||||
it('can Mix() structure', () => {
|
||||
assert.deepStrictEqual(sequence(1, 2).addMix(silence, 5, silence).firstCycle(), [
|
||||
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(
|
||||
sequence(1, [2, 3]).addSqueeze(sequence(10, 20, 30)),
|
||||
sequence(
|
||||
@ -174,15 +189,100 @@ describe('Pattern', function () {
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('addSqueezeFlip()', () => {
|
||||
it('Can add while squeezing the first pattern inside the events of the second', () => {
|
||||
it('can SqueezeOut() structure', () => {
|
||||
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]]),
|
||||
);
|
||||
});
|
||||
});
|
||||
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 () {
|
||||
it('Can subtract things', function () {
|
||||
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 () {
|
||||
sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3));
|
||||
});
|
||||
describe('setFlip()', () => {
|
||||
describe('setOut()', () => {
|
||||
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()', () => {
|
||||
@ -254,10 +354,7 @@ describe('Pattern', function () {
|
||||
);
|
||||
});
|
||||
it('Can stack subpatterns', function () {
|
||||
sameFirst(
|
||||
stack('a', ['b','c']),
|
||||
stack('a', sequence('b', 'c')),
|
||||
);
|
||||
sameFirst(stack('a', ['b', 'c']), stack('a', sequence('b', 'c')));
|
||||
});
|
||||
});
|
||||
describe('_fast()', function () {
|
||||
@ -388,11 +485,7 @@ describe('Pattern', function () {
|
||||
});
|
||||
describe('fastcat()', function () {
|
||||
it('Can go into negative time', function () {
|
||||
sameFirst(
|
||||
fastcat('a','b','c')
|
||||
.late(1000000),
|
||||
fastcat('a','b','c'),
|
||||
);
|
||||
sameFirst(fastcat('a', 'b', 'c').late(1000000), fastcat('a', 'b', 'c'));
|
||||
});
|
||||
});
|
||||
describe('slowcat()', function () {
|
||||
@ -425,12 +518,9 @@ describe('Pattern', function () {
|
||||
['c'],
|
||||
);
|
||||
});
|
||||
it ('Can cat subpatterns', () => {
|
||||
sameFirst(
|
||||
slowcat('a', ['b','c']).fast(4),
|
||||
sequence('a', ['b', 'c']).fast(2)
|
||||
)
|
||||
})
|
||||
it('Can cat subpatterns', () => {
|
||||
sameFirst(slowcat('a', ['b', 'c']).fast(4), sequence('a', ['b', 'c']).fast(2));
|
||||
});
|
||||
});
|
||||
describe('rev()', function () {
|
||||
it('Can reverse things', function () {
|
||||
|
||||
@ -34,12 +34,16 @@ export class TimeSpan {
|
||||
return spans;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this.end.sub(this.begin);
|
||||
}
|
||||
|
||||
cycleArc() {
|
||||
// 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 --
|
||||
// that only happens when the input Arc starts at an integral Time.)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -93,7 +97,7 @@ export class TimeSpan {
|
||||
}
|
||||
|
||||
midpoint() {
|
||||
return this.begin.add(this.end.sub(this.begin).div(Fraction(2)));
|
||||
return this.begin.add(this.duration.div(Fraction(2)));
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
function unionWithObj(a, b, func) {
|
||||
export function unionWithObj(a, b, func) {
|
||||
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])])));
|
||||
}
|
||||
|
||||
@ -809,3 +809,10 @@ export const festivalOfFingers3 = `"[-7*3],0,2,6,[8 7]"
|
||||
.slow(2)
|
||||
.tone((await piano()).toDestination())
|
||||
//.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