2019-06-19 19:53:14 +00:00
|
|
|
import { expect } from "chai";
|
|
|
|
import { BasicTests } from "test/helper/Basic";
|
|
|
|
import { CompareToFile } from "test/helper/CompareToFile";
|
|
|
|
import { Offline } from "test/helper/Offline";
|
|
|
|
import { OscillatorTests } from "test/helper/OscillatorTests";
|
|
|
|
import { OutputAudio } from "test/helper/OutputAudio";
|
|
|
|
import { SourceTests } from "test/helper/SourceTests";
|
|
|
|
import { Oscillator } from "./Oscillator";
|
2019-07-19 16:32:17 +00:00
|
|
|
import { ToneOscillatorType } from "./OscillatorInterface";
|
2019-06-19 19:53:14 +00:00
|
|
|
|
|
|
|
describe("Oscillator", () => {
|
|
|
|
|
|
|
|
// run the common tests
|
|
|
|
BasicTests(Oscillator);
|
|
|
|
SourceTests(Oscillator);
|
|
|
|
OscillatorTests(Oscillator);
|
|
|
|
|
|
|
|
it("matches a file", () => {
|
|
|
|
return CompareToFile(() => {
|
2019-07-25 15:32:56 +00:00
|
|
|
const osc = new Oscillator().toDestination();
|
2019-06-19 19:53:14 +00:00
|
|
|
osc.type = "square";
|
|
|
|
osc.start(0).stop(0.2);
|
|
|
|
}, "oscillator.wav", 0.1);
|
|
|
|
});
|
|
|
|
|
|
|
|
context("Get/Set", () => {
|
|
|
|
|
|
|
|
it("can be set with an options object", () => {
|
|
|
|
const osc = new Oscillator();
|
2019-07-19 17:20:32 +00:00
|
|
|
osc.set({
|
|
|
|
detune: -21,
|
|
|
|
frequency: 231,
|
|
|
|
type: "square",
|
|
|
|
});
|
2019-06-19 19:53:14 +00:00
|
|
|
expect(osc.frequency.value).to.equal(231);
|
|
|
|
expect(osc.detune.value).to.equal(-21);
|
|
|
|
expect(osc.type).to.equal("square");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can be get the values as an object", () => {
|
|
|
|
const osc = new Oscillator(450, "square");
|
|
|
|
expect(osc.get().frequency).to.equal(450);
|
|
|
|
expect(osc.get().type).to.equal("square");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
context("Phase Rotation", () => {
|
|
|
|
it("can change the phase to 90", () => {
|
|
|
|
return Offline(() => {
|
|
|
|
const instance = new Oscillator({
|
|
|
|
frequency: 1,
|
|
|
|
phase: 90,
|
|
|
|
});
|
2019-07-25 15:32:56 +00:00
|
|
|
instance.toDestination();
|
2019-06-19 19:53:14 +00:00
|
|
|
instance.start(0);
|
|
|
|
}, 1).then((buffer) => {
|
|
|
|
buffer.forEach((sample, time) => {
|
|
|
|
if (time < 0.25) {
|
|
|
|
expect(sample).to.be.within(-1, 0);
|
|
|
|
} else if (time > 0.25 && time < 0.5) {
|
|
|
|
expect(sample).to.be.within(0, 1);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can change the phase to -90", () => {
|
|
|
|
return Offline(() => {
|
|
|
|
const instance = new Oscillator({
|
|
|
|
frequency: 1,
|
|
|
|
phase: 270,
|
|
|
|
});
|
2019-07-25 15:32:56 +00:00
|
|
|
instance.toDestination();
|
2019-06-19 19:53:14 +00:00
|
|
|
instance.start(0);
|
|
|
|
}, 1).then((buffer) => {
|
|
|
|
buffer.forEach((sample, time) => {
|
|
|
|
if (time < 0.25) {
|
|
|
|
expect(sample).to.be.within(0, 1);
|
|
|
|
} else if (time > 0.25 && time < 0.5) {
|
|
|
|
expect(sample).to.be.within(-1, 0);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-09-17 18:04:32 +00:00
|
|
|
it("can go past the cache max size of 100", () => {
|
|
|
|
const osc = new Oscillator();
|
|
|
|
for (let i = 0; i < 110; i++) {
|
|
|
|
osc.phase = i;
|
|
|
|
}
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
2019-06-19 19:53:14 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
context("Type", () => {
|
|
|
|
|
|
|
|
it("can get and set the type", () => {
|
|
|
|
const osc = new Oscillator({
|
|
|
|
type: "sawtooth",
|
|
|
|
});
|
|
|
|
expect(osc.type).to.equal("sawtooth");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
2019-10-31 18:22:19 +00:00
|
|
|
it("can set the type after starting", () => {
|
|
|
|
const osc = new Oscillator(110, "sawtooth10").start();
|
|
|
|
expect(osc.type).to.equal("sawtooth10");
|
|
|
|
osc.type = "sawtooth20";
|
|
|
|
expect(osc.type).to.equal("sawtooth20");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
2019-06-19 19:53:14 +00:00
|
|
|
it("handles 4 basic types", () => {
|
|
|
|
const osc = new Oscillator();
|
2019-07-19 16:32:17 +00:00
|
|
|
const types: ToneOscillatorType[] = ["triangle", "sawtooth", "sine", "square"];
|
2019-06-23 10:53:57 +00:00
|
|
|
for (const type of types) {
|
|
|
|
osc.type = type;
|
|
|
|
expect(osc.type).to.equal(type);
|
2019-06-19 19:53:14 +00:00
|
|
|
}
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("throws an error if invalid type is set", () => {
|
|
|
|
const osc = new Oscillator();
|
|
|
|
expect(() => {
|
2019-07-19 16:32:17 +00:00
|
|
|
// @ts-ignore
|
2019-06-19 19:53:14 +00:00
|
|
|
osc.type = "invalid";
|
|
|
|
}).to.throw(Error);
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can set extended types", () => {
|
|
|
|
const osc = new Oscillator();
|
|
|
|
osc.type = "sine5";
|
|
|
|
expect(osc.type).to.equal("sine5");
|
|
|
|
osc.type = "triangle2";
|
|
|
|
expect(osc.type).to.equal("triangle2");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can get/set the baseType", () => {
|
|
|
|
const osc = new Oscillator();
|
|
|
|
osc.type = "sine5";
|
|
|
|
expect(osc.baseType).to.equal("sine");
|
|
|
|
osc.baseType = "triangle";
|
|
|
|
expect(osc.type).to.equal("triangle5");
|
|
|
|
expect(osc.partialCount).to.equal(5);
|
|
|
|
osc.partialCount = 2;
|
|
|
|
expect(osc.type).to.equal("triangle2");
|
|
|
|
osc.baseType = "custom";
|
|
|
|
expect(osc.type).to.equal("custom");
|
|
|
|
osc.partials = [1, 2, 3];
|
|
|
|
expect(osc.baseType).to.equal("custom");
|
|
|
|
osc.baseType = "square";
|
|
|
|
expect(osc.type).to.equal("square");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
context("Partials", () => {
|
|
|
|
|
|
|
|
it("can pass partials in the constructor", () => {
|
|
|
|
const osc = new Oscillator({
|
|
|
|
partials: [1, 0.3, 0.3],
|
|
|
|
type: "custom",
|
|
|
|
});
|
|
|
|
expect(osc.type).to.equal("custom");
|
|
|
|
expect(osc.partials[1]).to.equal(0.3);
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can set partials", () => {
|
|
|
|
const osc = new Oscillator();
|
|
|
|
osc.partials = [1, 0.2, 0.2, 0.2];
|
|
|
|
expect(osc.type).to.equal("custom");
|
|
|
|
expect(osc.partials[1]).to.equal(0.2);
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("makes a sound with custom partials", () => {
|
|
|
|
return OutputAudio(() => {
|
2019-07-25 15:32:56 +00:00
|
|
|
const osc = new Oscillator().toDestination().start();
|
2019-06-19 19:53:14 +00:00
|
|
|
osc.partials = [1, 0.2, 0.2, 0.2];
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("outputs the partials of the given waveform", () => {
|
|
|
|
const osc = new Oscillator();
|
|
|
|
osc.type = "sine2";
|
|
|
|
expect(osc.type).to.equal("sine2");
|
|
|
|
expect(osc.partials.length).to.equal(2);
|
|
|
|
expect(osc.partials).to.deep.equal([1, 1]);
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("partialCount is 0 when set to max", () => {
|
|
|
|
const osc = new Oscillator();
|
|
|
|
expect(osc.partialCount).to.equal(0);
|
|
|
|
osc.type = "square32";
|
|
|
|
expect(osc.partialCount).to.equal(32);
|
|
|
|
osc.type = "square";
|
|
|
|
expect(osc.partialCount).to.equal(0);
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can pass in number of partials into constructor", () => {
|
|
|
|
const osc = new Oscillator({
|
|
|
|
partialCount: 3,
|
|
|
|
type: "sine",
|
|
|
|
});
|
|
|
|
expect(osc.type).to.equal("sine3");
|
|
|
|
expect(osc.partialCount).to.equal(3);
|
|
|
|
osc.partialCount = 4;
|
|
|
|
expect(osc.partialCount).to.equal(4);
|
|
|
|
expect(osc.type).to.equal("sine4");
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2019-07-11 03:33:36 +00:00
|
|
|
context("Synchronization", () => {
|
|
|
|
it("can sync the frequency to the Transport", () => {
|
2019-09-16 03:32:40 +00:00
|
|
|
return Offline(({ transport }) => {
|
2019-07-11 03:33:36 +00:00
|
|
|
transport.bpm.value = 120;
|
|
|
|
const osc = new Oscillator(2);
|
2019-07-25 15:32:56 +00:00
|
|
|
osc.frequency.toDestination();
|
2019-07-11 03:33:36 +00:00
|
|
|
osc.syncFrequency();
|
|
|
|
transport.bpm.value = 240;
|
|
|
|
}).then((buffer) => {
|
|
|
|
expect(buffer.value()).to.be.closeTo(4, 0.001);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can unsync the frequency from the Transport", () => {
|
2019-09-16 03:32:40 +00:00
|
|
|
return Offline(({ transport }) => {
|
2019-07-11 03:33:36 +00:00
|
|
|
transport.bpm.value = 120;
|
|
|
|
const osc = new Oscillator(2);
|
2019-07-25 15:32:56 +00:00
|
|
|
osc.frequency.toDestination();
|
2019-07-11 03:33:36 +00:00
|
|
|
osc.syncFrequency();
|
|
|
|
transport.bpm.value = 240;
|
|
|
|
osc.unsyncFrequency();
|
|
|
|
}).then((buffer) => {
|
|
|
|
expect(buffer.value()).to.be.closeTo(2, 0.001);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2019-06-19 19:53:14 +00:00
|
|
|
|
2019-09-05 00:01:16 +00:00
|
|
|
context("initialValue", () => {
|
2019-09-16 03:32:40 +00:00
|
|
|
it("can get the initial value of a basic oscillator type", () => {
|
2019-09-05 00:01:16 +00:00
|
|
|
const osc = new Oscillator(10, "sine");
|
|
|
|
expect(osc.getInitialValue()).to.be.closeTo(0, 0.01);
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
2019-09-16 03:32:40 +00:00
|
|
|
it("can get the initial value when the phase is rotated", () => {
|
2019-09-05 00:01:16 +00:00
|
|
|
const osc = new Oscillator({
|
|
|
|
phase: 90,
|
|
|
|
type: "sine",
|
|
|
|
});
|
|
|
|
expect(osc.getInitialValue()).to.be.closeTo(-1, 0.01);
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
|
2019-09-16 03:32:40 +00:00
|
|
|
it("can get the initial value of more complex types", () => {
|
2019-09-05 00:01:16 +00:00
|
|
|
const osc = new Oscillator({
|
|
|
|
partials: [0, 2, 4, 1, 3],
|
|
|
|
phase: 145,
|
|
|
|
type: "custom",
|
|
|
|
});
|
|
|
|
expect(osc.getInitialValue()).to.be.closeTo(-0.2, 0.05);
|
|
|
|
osc.dispose();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-06-19 19:53:14 +00:00
|
|
|
});
|