2019-06-17 18:03:02 +00:00
|
|
|
import { expect } from "chai";
|
|
|
|
import { BasicTests } from "test/helper/Basic";
|
|
|
|
import { CompareToFile } from "test/helper/CompareToFile";
|
|
|
|
import { Offline, whenBetween } from "test/helper/Offline";
|
|
|
|
import { ONLINE_TESTING } from "test/helper/Supports";
|
|
|
|
import { Frequency } from "Tone/core/type/Frequency";
|
|
|
|
import { ToneOscillatorNode } from "./OscillatorNode";
|
|
|
|
|
|
|
|
describe("ToneOscillatorNode", () => {
|
|
|
|
|
|
|
|
BasicTests(ToneOscillatorNode);
|
|
|
|
|
|
|
|
it("matches a file", () => {
|
|
|
|
return CompareToFile(() => {
|
|
|
|
const osc = new ToneOscillatorNode().toMaster();
|
|
|
|
osc.start(0.5);
|
|
|
|
}, "oscillatorNode.wav");
|
|
|
|
});
|
|
|
|
|
|
|
|
context("Constructor", () => {
|
|
|
|
|
|
|
|
it("can be constructed with a frequency and type", () => {
|
|
|
|
const osc0 = new ToneOscillatorNode(330, "square");
|
|
|
|
expect(osc0.frequency.value).to.equal(330);
|
|
|
|
osc0.dispose();
|
|
|
|
const osc1 = new ToneOscillatorNode(Frequency(550).valueOf(), "sawtooth");
|
|
|
|
expect(osc1.frequency.value).to.equal(550);
|
|
|
|
osc1.dispose();
|
|
|
|
const osc2 = new ToneOscillatorNode("A3", "triangle");
|
|
|
|
expect(osc2.frequency.value).to.equal(220);
|
|
|
|
osc2.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can be constructed with no arguments", () => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
expect(osc.frequency.value).to.equal(440);
|
|
|
|
expect(osc.detune.value).to.equal(0);
|
|
|
|
expect(osc.type).to.equal("sine");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can be constructed with an options object", () => {
|
|
|
|
const osc = new ToneOscillatorNode({
|
|
|
|
detune: -20,
|
|
|
|
frequency: 123,
|
|
|
|
type: "square",
|
|
|
|
});
|
|
|
|
expect(osc.frequency.value).to.be.closeTo(123, 0.01);
|
|
|
|
expect(osc.detune.value).to.equal(-20);
|
|
|
|
expect(osc.type).to.equal("square");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
context("Type", () => {
|
|
|
|
|
|
|
|
it("can get and set the type", () => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.type = "triangle";
|
|
|
|
expect(osc.type).to.equal("triangle");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can set a periodic wave", () => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
const periodicWave = osc.context.createPeriodicWave(Float32Array.from([1, 0]), Float32Array.from([1, 0]));
|
|
|
|
osc.setPeriodicWave(periodicWave);
|
|
|
|
expect(osc.type).to.equal("custom");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
context("onended", () => {
|
|
|
|
|
|
|
|
if (ONLINE_TESTING) {
|
|
|
|
|
|
|
|
it("invokes the onended callback in the online context", (done) => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start();
|
|
|
|
osc.stop("+0.3");
|
|
|
|
const now = osc.now();
|
|
|
|
osc.onended = () => {
|
|
|
|
expect(osc.now() - now).to.be.within(0.25, 0.5);
|
|
|
|
osc.dispose();
|
|
|
|
done();
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
it("invokes the onended callback only once in the online context", (done) => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start();
|
|
|
|
osc.stop("+0.1");
|
|
|
|
osc.stop("+0.2");
|
|
|
|
osc.stop("+0.3");
|
|
|
|
const now = osc.now();
|
|
|
|
osc.onended = () => {
|
|
|
|
expect(osc.now() - now).to.be.within(0.25, 0.5);
|
|
|
|
osc.dispose();
|
|
|
|
done();
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-06-19 21:20:12 +00:00
|
|
|
it("invokes the onended callback in the offline context", () => {
|
|
|
|
let wasInvoked = false;
|
|
|
|
return Offline(() => {
|
2019-06-17 18:03:02 +00:00
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start(0);
|
|
|
|
osc.stop(0.2);
|
|
|
|
osc.onended = () => {
|
|
|
|
expect(osc.now() - 0.2).to.be.closeTo(0, 0.05);
|
|
|
|
osc.dispose();
|
2019-06-19 21:20:12 +00:00
|
|
|
wasInvoked = true;
|
2019-06-17 18:03:02 +00:00
|
|
|
};
|
2019-06-19 21:20:12 +00:00
|
|
|
}, 0.3).then(() => {
|
|
|
|
expect(wasInvoked).to.equal(true);
|
|
|
|
});
|
2019-06-17 18:03:02 +00:00
|
|
|
});
|
|
|
|
|
2019-06-19 21:20:12 +00:00
|
|
|
it("invokes the onended callback only once in offline context", () => {
|
|
|
|
let wasInvoked = false;
|
|
|
|
return Offline(() => {
|
2019-06-17 18:03:02 +00:00
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start(0);
|
|
|
|
osc.stop(0.1);
|
|
|
|
osc.stop(0.2);
|
|
|
|
osc.stop(0.3);
|
|
|
|
osc.onended = () => {
|
|
|
|
expect(osc.now() - 0.3).to.be.closeTo(0, 0.05);
|
|
|
|
osc.dispose();
|
2019-06-19 21:20:12 +00:00
|
|
|
expect(wasInvoked).to.equal(false);
|
|
|
|
wasInvoked = true;
|
2019-06-17 18:03:02 +00:00
|
|
|
};
|
2019-06-19 21:20:12 +00:00
|
|
|
}, 0.4).then(() => {
|
|
|
|
expect(wasInvoked).to.equal(true);
|
|
|
|
});
|
2019-06-17 18:03:02 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
context("Scheduling", () => {
|
|
|
|
|
|
|
|
it("throw an error if start is called multiple time", () => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start();
|
|
|
|
expect(() => {
|
|
|
|
osc.start();
|
|
|
|
}).to.throw();
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can play for a specific duration", () => {
|
2019-06-23 19:03:31 +00:00
|
|
|
return Offline((context) => {
|
|
|
|
const osc = new ToneOscillatorNode().toDestination();
|
2019-06-17 18:03:02 +00:00
|
|
|
osc.start(0).stop(0.1);
|
|
|
|
}, 0.4).then(buffer => {
|
|
|
|
expect(buffer.getRmsAtTime(0)).to.be.above(0);
|
|
|
|
expect(buffer.getRmsAtTime(0.09)).to.be.above(0);
|
|
|
|
expect(buffer.getRmsAtTime(0.1)).to.equal(0);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can call stop multiple times and takes the last value", () => {
|
|
|
|
return Offline(() => {
|
|
|
|
const osc = new ToneOscillatorNode().toMaster();
|
|
|
|
osc.start(0).stop(0.1).stop(0.2);
|
|
|
|
}, 0.4).then((buffer) => {
|
|
|
|
expect(buffer.getRmsAtTime(0)).to.be.above(0);
|
|
|
|
expect(buffer.getRmsAtTime(0.1)).to.be.above(0);
|
|
|
|
expect(buffer.getRmsAtTime(0.19)).to.be.above(0);
|
|
|
|
expect(buffer.getRmsAtTime(0.2)).to.equal(0);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
if (ONLINE_TESTING) {
|
|
|
|
|
|
|
|
it("clamps start time to the currentTime", () => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start(0);
|
|
|
|
const currentTime = osc.context.currentTime;
|
|
|
|
expect(osc.getStateAtTime(0)).to.equal("stopped");
|
|
|
|
expect(osc.getStateAtTime(currentTime)).to.equal("started");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("clamps stop time to the currentTime", (done) => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start(0);
|
|
|
|
let currentTime = osc.context.currentTime;
|
|
|
|
expect(osc.getStateAtTime(0)).to.equal("stopped");
|
|
|
|
expect(osc.getStateAtTime(currentTime)).to.equal("started");
|
|
|
|
setTimeout(() => {
|
|
|
|
currentTime = osc.context.currentTime;
|
|
|
|
osc.stop(0);
|
|
|
|
expect(osc.getStateAtTime(currentTime + 0.01)).to.equal("stopped");
|
|
|
|
osc.dispose();
|
|
|
|
done();
|
|
|
|
}, 100);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
context("State", () => {
|
|
|
|
|
|
|
|
it("reports the right state", () => {
|
|
|
|
|
|
|
|
return Offline(() => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start(0);
|
|
|
|
osc.stop(0.05);
|
|
|
|
return (time) => {
|
|
|
|
whenBetween(time, 0, 0.05, () => {
|
|
|
|
expect(osc.state).to.equal("started");
|
|
|
|
});
|
|
|
|
whenBetween(time, 0.05, 0.1, () => {
|
|
|
|
expect(osc.state).to.equal("stopped");
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}, 0.1);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can call stop multiple times, takes the last value", () => {
|
|
|
|
|
|
|
|
return Offline(() => {
|
|
|
|
const osc = new ToneOscillatorNode();
|
|
|
|
osc.start(0);
|
|
|
|
osc.stop(0.05);
|
|
|
|
osc.stop(0.1);
|
|
|
|
return (time) => {
|
|
|
|
whenBetween(time, 0, 0.1, () => {
|
|
|
|
expect(osc.state).to.equal("started");
|
|
|
|
});
|
|
|
|
whenBetween(time, 0.1, 0.2, () => {
|
|
|
|
expect(osc.state).to.equal("stopped");
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}, 0.2);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|