mirror of
https://github.com/Tonejs/Tone.js
synced 2024-12-27 03:53:07 +00:00
d4d7c8a78b
plays the correct number of iterations
442 lines
11 KiB
TypeScript
442 lines
11 KiB
TypeScript
import { expect } from "chai";
|
|
import { BasicTests } from "test/helper/Basic";
|
|
import { atTime, Offline } from "test/helper/Offline";
|
|
import { Time } from "Tone/core/type/Time";
|
|
import { noOp } from "Tone/core/util/Interface";
|
|
import { Sequence } from "./Sequence";
|
|
|
|
describe("Sequence", () => {
|
|
|
|
BasicTests(Sequence);
|
|
|
|
context("Constructor", () => {
|
|
|
|
it("takes a callback and a sequence of values", () => {
|
|
return Offline(() => {
|
|
const callback = noOp;
|
|
const seq = new Sequence(callback, [0, 1, 2]);
|
|
expect(seq.callback).to.equal(callback);
|
|
expect(seq.length).to.equal(3);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("takes a callback and a sequence of values and a subdivision", () => {
|
|
return Offline(() => {
|
|
const callback = noOp;
|
|
const seq = new Sequence(callback, [0, 1, 2], "2n");
|
|
expect(seq.callback).to.equal(callback);
|
|
expect(seq.subdivision).to.equal(Time("2n").valueOf());
|
|
expect(seq.length).to.equal(3);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("can be constructed with no arguments", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence();
|
|
expect(seq.length).to.equal(0);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("can pass in arguments in options object", () => {
|
|
return Offline(() => {
|
|
const callback = noOp;
|
|
const seq = new Sequence({
|
|
callback,
|
|
events : [0, 1, 2],
|
|
humanize : true,
|
|
loop : true,
|
|
loopEnd : 2,
|
|
probability : 0.3,
|
|
});
|
|
expect(seq.callback).to.equal(callback);
|
|
expect(seq.length).to.equal(3);
|
|
expect(seq.loop).to.be.true;
|
|
expect(seq.loopEnd).to.equal(2);
|
|
expect(seq.probability).to.equal(0.3);
|
|
expect(seq.humanize).to.be.true;
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it.skip("loops by default with the loopEnd as the duration of the loop", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence(noOp, [0, 1, 2, 3], "8n");
|
|
expect(seq.loop).to.be.true;
|
|
expect(seq.length).to.equal(4);
|
|
expect(seq.loopEnd).to.equal(4);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
});
|
|
|
|
context("Adding / Removing / Getting Events", () => {
|
|
|
|
it("can add an event using the index", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence();
|
|
seq.events[0] = 0;
|
|
expect(seq.length).to.equal(1);
|
|
seq.events[1] = 1;
|
|
expect(seq.length).to.equal(2);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("can add a subsequence", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence();
|
|
seq.events = [[0, 1, 2]];
|
|
expect(seq.length).to.equal(3);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("can retrieve an event using the index", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence(noOp, [0, 1, 2]);
|
|
expect(seq.length).to.equal(3);
|
|
expect(seq.events[0]).to.equal(0);
|
|
expect(seq.events[1]).to.equal(1);
|
|
expect(seq.events[2]).to.equal(2);
|
|
expect(seq.events[3]).to.be.undefined;
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("can set the value of an existing event with an index", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence(noOp, [0, 1, 2]);
|
|
expect(seq.length).to.equal(3);
|
|
expect(seq.events[0]).to.equal(0);
|
|
seq.events[0] = 1;
|
|
expect(seq.events[0]).to.equal(1);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("can remove an event by index", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence(noOp, [0, 1, 2]);
|
|
expect(seq.length).to.equal(3);
|
|
seq.events.splice(0, 1);
|
|
expect(seq.length).to.equal(2);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("can add a subsequence and remove the entire subsequence", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence(noOp, [0, 1, 2]);
|
|
expect(seq.length).to.equal(3);
|
|
seq.events.shift();
|
|
seq.events[0] = [1, 2];
|
|
expect(seq.length).to.equal(3);
|
|
expect(seq.events[0][0]).to.equal(1);
|
|
expect(seq.events[0][1]).to.equal(2);
|
|
seq.events.shift();
|
|
expect(seq.length).to.equal(1);
|
|
expect(seq.events[0]).to.equal(2);
|
|
seq.events[0] = 4;
|
|
expect(seq.events[0]).to.equal(4);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
it("can remove all of the events", () => {
|
|
return Offline(() => {
|
|
const seq = new Sequence(noOp, [0, 1, 2, 3, 4, 5]);
|
|
expect(seq.length).to.equal(6);
|
|
seq.clear();
|
|
expect(seq.length).to.equal(0);
|
|
seq.dispose();
|
|
});
|
|
});
|
|
|
|
});
|
|
context("Sequence callback", () => {
|
|
|
|
it("invokes the callback after it's started", () => {
|
|
let invoked = false;
|
|
return Offline(({transport}) => {
|
|
const seq = new Sequence(() => {
|
|
seq.dispose();
|
|
invoked = true;
|
|
}, [0, 1]).start(0);
|
|
transport.start();
|
|
}, 0.1).then(() => {
|
|
expect(invoked).to.be.true;
|
|
});
|
|
});
|
|
|
|
it("can be scheduled to stop", () => {
|
|
let invoked = 0;
|
|
return Offline(({transport}) => {
|
|
const seq = new Sequence(() => {
|
|
invoked++;
|
|
}, [0, 1], 0.1).start(0).stop(0.5);
|
|
transport.start();
|
|
}, 1).then(() => {
|
|
expect(invoked).to.equal(6);
|
|
});
|
|
});
|
|
|
|
it("passes in the scheduled time to the callback", () => {
|
|
let invoked = false;
|
|
return Offline(({transport}) => {
|
|
const now = 0.1;
|
|
const seq = new Sequence((time) => {
|
|
expect(time).to.be.a("number");
|
|
expect(time - now).to.be.closeTo(0.3, 0.01);
|
|
seq.dispose();
|
|
invoked = true;
|
|
}, [0.5]);
|
|
seq.start(0.3);
|
|
transport.start(now);
|
|
}, 0.5).then(() => {
|
|
expect(invoked).to.be.true;
|
|
});
|
|
});
|
|
|
|
it("passes in the value to the callback", () => {
|
|
let invoked = false;
|
|
return Offline(({transport}) => {
|
|
const seq = new Sequence((time, thing) => {
|
|
expect(time).to.be.a("number");
|
|
expect(thing).to.equal("thing");
|
|
seq.dispose();
|
|
invoked = true;
|
|
}, ["thing"]).start();
|
|
transport.start();
|
|
}, 0.1).then(() => {
|
|
expect(invoked).to.be.true;
|
|
});
|
|
});
|
|
|
|
it("invokes the scheduled events in the right order", () => {
|
|
let count = 0;
|
|
return Offline(({transport}) => {
|
|
const seq = new Sequence((time, value) => {
|
|
expect(value).to.equal(count);
|
|
count++;
|
|
}, [0, [1, 2], [3, 4]], "16n").start();
|
|
seq.loop = false;
|
|
transport.start(0);
|
|
}, 0.5).then(() => {
|
|
expect(count).to.equal(5);
|
|
});
|
|
});
|
|
|
|
it("invokes the scheduled events at the correct times", () => {
|
|
let count = 0;
|
|
return Offline(({transport}) => {
|
|
const eighth = transport.toSeconds("8n");
|
|
const times = [0, eighth, eighth * 1.5, eighth * 2, eighth * (2 + 1 / 3), eighth * (2 + 2 / 3)];
|
|
const seq = new Sequence((time) => {
|
|
expect(time).to.be.closeTo(times[count], 0.01);
|
|
count++;
|
|
}, [0, [1, 2], [3, 4, 5]], "8n").start(0);
|
|
seq.loop = false;
|
|
transport.start(0);
|
|
}, 0.8).then(() => {
|
|
expect(count).to.equal(6);
|
|
});
|
|
});
|
|
|
|
it("can schedule rests using 'null'", () => {
|
|
let count = 0;
|
|
return Offline(({transport}) => {
|
|
const eighth = transport.toSeconds("8n");
|
|
const times = [0, eighth * 2.5];
|
|
const seq = new Sequence((time, value) => {
|
|
expect(time).to.be.closeTo(times[count], 0.01);
|
|
count++;
|
|
}, [0, null, [null, 1]], "8n").start(0);
|
|
seq.loop = false;
|
|
transport.start(0);
|
|
}, 0.8).then(() => {
|
|
expect(count).to.equal(2);
|
|
});
|
|
});
|
|
|
|
it("can schedule triple nested arrays", () => {
|
|
let count = 0;
|
|
return Offline(({transport}) => {
|
|
const eighth = transport.toSeconds("8n");
|
|
const times = [0, eighth, eighth * 1.5, eighth * 1.75];
|
|
const seq = new Sequence((time) => {
|
|
expect(time).to.be.closeTo(times[count], 0.01);
|
|
count++;
|
|
}, [0, [1, [2, 3]]], "8n").start(0);
|
|
seq.loop = false;
|
|
transport.start(0);
|
|
}, 0.7).then(() => {
|
|
expect(count).to.equal(4);
|
|
});
|
|
});
|
|
|
|
it("starts an event added after the seq was started", () => {
|
|
let invoked = false;
|
|
return Offline(({transport}) => {
|
|
const seq = new Sequence({
|
|
callback(time, value): void {
|
|
if (value === 1) {
|
|
seq.dispose();
|
|
invoked = true;
|
|
}
|
|
},
|
|
events : [[0, 2]],
|
|
}).start(0);
|
|
transport.start();
|
|
|
|
return atTime(0.1, () => {
|
|
seq.events[1] = 1;
|
|
});
|
|
}, 0.5).then(() => {
|
|
expect(invoked).to.be.true;
|
|
});
|
|
});
|
|
|
|
});
|
|
|
|
context("Looping", () => {
|
|
|
|
it("can be set to loop", () => {
|
|
let callCount = 0;
|
|
return Offline(({transport}) => {
|
|
const seq = new Sequence({
|
|
events : [0, 1],
|
|
loop : true,
|
|
loopEnd : 0.2,
|
|
callback(): void {
|
|
callCount++;
|
|
if (callCount > 2) {
|
|
seq.dispose();
|
|
}
|
|
},
|
|
}).start(0);
|
|
transport.start();
|
|
}, 0.5).then(() => {
|
|
expect(callCount).to.equal(3);
|
|
});
|
|
});
|
|
|
|
it("can loop between loopStart and loopEnd", () => {
|
|
let invokations = 0;
|
|
return Offline(({transport}) => {
|
|
const seq = new Sequence({
|
|
events : [0, [1, 2, 3], [4, 5]],
|
|
loopEnd : 2,
|
|
loopStart : 1,
|
|
subdivision : "8n",
|
|
callback(time, value): void {
|
|
expect(value).to.be.at.least(1);
|
|
expect(value).to.be.at.most(3);
|
|
invokations++;
|
|
},
|
|
}).start(0);
|
|
transport.start();
|
|
}, 0.7).then(() => {
|
|
expect(invokations).to.equal(9);
|
|
});
|
|
});
|
|
|
|
it("can set the loop points after starting", () => {
|
|
let invoked = false;
|
|
return Offline(({transport}) => {
|
|
let switched = false;
|
|
const seq = new Sequence({
|
|
callback(time, value): void {
|
|
if (value === 4) {
|
|
seq.loopStart = 2;
|
|
switched = true;
|
|
}
|
|
if (switched) {
|
|
expect(value).to.be.at.least(4);
|
|
expect(value).to.be.at.most(5);
|
|
invoked = true;
|
|
}
|
|
},
|
|
events : [0, [1, 2, 3], [4, 5]],
|
|
subdivision : "16n",
|
|
}).start(0);
|
|
transport.start();
|
|
}, 0.7).then(() => {
|
|
expect(invoked).to.be.true;
|
|
});
|
|
});
|
|
});
|
|
|
|
context("playbackRate", () => {
|
|
|
|
it("can adjust the playbackRate", () => {
|
|
let invoked = false;
|
|
return Offline(({transport}) => {
|
|
let lastCall;
|
|
new Sequence({
|
|
events : [0, 1],
|
|
playbackRate : 2,
|
|
subdivision : "4n",
|
|
callback(time): void {
|
|
if (lastCall) {
|
|
invoked = true;
|
|
expect(time - lastCall).to.be.closeTo(0.25, 0.01);
|
|
}
|
|
lastCall = time;
|
|
},
|
|
}).start(0);
|
|
transport.start();
|
|
}, 0.7).then(() => {
|
|
expect(invoked).to.be.true;
|
|
});
|
|
});
|
|
|
|
it("adjusts speed of subsequences", () => {
|
|
let invoked = false;
|
|
return Offline(({transport}) => {
|
|
let lastCall;
|
|
new Sequence({
|
|
events : [[0, 1], [2, 3]],
|
|
playbackRate : 0.5,
|
|
subdivision : "8n",
|
|
callback(time): void {
|
|
if (lastCall) {
|
|
expect(time - lastCall).to.be.closeTo(0.25, 0.01);
|
|
invoked = true;
|
|
}
|
|
lastCall = time;
|
|
},
|
|
}).start(0);
|
|
transport.start();
|
|
}, 0.7).then(() => {
|
|
expect(invoked).to.be.true;
|
|
});
|
|
});
|
|
|
|
it("can adjust the playbackRate after starting", () => {
|
|
let invoked = false;
|
|
return Offline(({transport}) => {
|
|
let lastCall;
|
|
const seq = new Sequence({
|
|
events : [0, 1],
|
|
playbackRate : 1,
|
|
subdivision : "8n",
|
|
callback(time): void {
|
|
if (lastCall) {
|
|
expect(time - lastCall).to.be.closeTo(0.5, 0.01);
|
|
invoked = true;
|
|
} else {
|
|
seq.playbackRate = 0.5;
|
|
}
|
|
lastCall = time;
|
|
},
|
|
}).start(0);
|
|
transport.start();
|
|
}, 2).then(() => {
|
|
expect(invoked).to.be.true;
|
|
});
|
|
});
|
|
});
|
|
});
|