2019-07-18 18:07:03 +00:00
|
|
|
import { expect } from "chai";
|
2024-05-03 18:31:14 +00:00
|
|
|
import { Instrument } from "../../Tone/instrument/Instrument.js";
|
|
|
|
import { connectTo } from "./Connect.js";
|
|
|
|
import { Offline } from "./Offline.js";
|
|
|
|
import { OutputAudio } from "./OutputAudio.js";
|
|
|
|
import { Monophonic } from "../../Tone/instrument/Monophonic.js";
|
2019-07-18 18:07:03 +00:00
|
|
|
|
2020-08-26 08:58:14 +00:00
|
|
|
function wait(time) {
|
2024-05-03 18:31:14 +00:00
|
|
|
return new Promise((done) => setTimeout(done, time));
|
2020-08-26 08:58:14 +00:00
|
|
|
}
|
|
|
|
|
2019-07-18 18:07:03 +00:00
|
|
|
export function InstrumentTest(Constr, note, constrArg?, optionsIndex?): void {
|
|
|
|
context("Instrument Tests", () => {
|
|
|
|
it("extends Tone.Instrument", () => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
expect(instance).to.be.an.instanceof(Instrument);
|
|
|
|
instance.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can connect the output", () => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.connect(connectTo());
|
|
|
|
instance.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can set the volume", () => {
|
|
|
|
let instance;
|
|
|
|
if (!optionsIndex) {
|
|
|
|
instance = new Constr({
|
2019-10-29 22:09:39 +00:00
|
|
|
volume: -10,
|
2019-07-18 18:07:03 +00:00
|
|
|
});
|
|
|
|
} else if (optionsIndex === 1) {
|
|
|
|
instance = new Constr(constrArg, {
|
2019-10-29 22:09:39 +00:00
|
|
|
volume: -10,
|
2019-07-18 18:07:03 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
expect(instance.volume.value).to.be.closeTo(-10, 0.1);
|
|
|
|
instance.dispose();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("makes a sound", () => {
|
|
|
|
return OutputAudio(() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
instance.triggerAttack(note);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("is silent before being triggered", () => {
|
|
|
|
return Offline(() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
}).then((buffer) => {
|
|
|
|
expect(buffer.isSilent()).to.be.true;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
if (Constr.prototype.triggerRelease) {
|
|
|
|
it("can trigger release after attack", () => {
|
|
|
|
return Offline(() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.05);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.05);
|
|
|
|
}
|
|
|
|
instance.triggerRelease(0.1);
|
|
|
|
}, 1).then((buffer) => {
|
2024-05-03 18:31:14 +00:00
|
|
|
expect(buffer.getTimeOfFirstSound()).to.be.within(
|
|
|
|
0.05,
|
|
|
|
0.1
|
|
|
|
);
|
2019-07-18 18:07:03 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can trigger another attack before the release has ended", () => {
|
|
|
|
// compute the end time
|
|
|
|
return Offline(() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.05);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.05);
|
|
|
|
}
|
|
|
|
instance.triggerRelease(0.1);
|
|
|
|
}, 1).then((buffer) => {
|
|
|
|
const bufferDuration = buffer.getTimeOfLastSound();
|
|
|
|
const secondTrigger = 0.15;
|
2024-05-03 18:31:14 +00:00
|
|
|
return Offline(
|
|
|
|
() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.05);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.05);
|
|
|
|
}
|
|
|
|
instance.triggerRelease(0.1);
|
|
|
|
// star the note again before the last one has finished
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, secondTrigger);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(secondTrigger);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
bufferDuration + secondTrigger * 2
|
|
|
|
).then((resultingBuffer) => {
|
|
|
|
expect(resultingBuffer.getTimeOfLastSound()).to.be.gt(
|
|
|
|
bufferDuration
|
|
|
|
);
|
2019-07-18 18:07:03 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can combine triggerAttack and triggerRelease", () => {
|
|
|
|
return Offline(() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttackRelease(note, 0.1, 0.05);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttackRelease(0.1, 0.05);
|
|
|
|
}
|
|
|
|
}, 0.2).then((buffer) => {
|
2024-05-03 18:31:14 +00:00
|
|
|
expect(buffer.getTimeOfFirstSound()).to.be.within(
|
|
|
|
0.05,
|
|
|
|
0.1
|
|
|
|
);
|
2019-07-18 18:07:03 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
it("be scheduled to start in the future", () => {
|
|
|
|
return Offline(() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.1);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.1);
|
|
|
|
}
|
|
|
|
}, 0.2).then((buffer) => {
|
|
|
|
expect(buffer.getTimeOfFirstSound()).to.be.within(0.1, 0.15);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can sync triggerAttack to the Transport", () => {
|
2019-10-29 22:09:39 +00:00
|
|
|
return Offline(({ transport }) => {
|
2019-07-18 18:07:03 +00:00
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
instance.sync();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.1);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.1);
|
|
|
|
}
|
|
|
|
transport.start(0.1);
|
|
|
|
}, 0.3).then((buffer) => {
|
|
|
|
expect(buffer.getTimeOfFirstSound()).to.be.within(0.19, 0.25);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can unsync triggerAttack to the Transport", () => {
|
2019-10-29 22:09:39 +00:00
|
|
|
return Offline(({ transport }) => {
|
2019-07-18 18:07:03 +00:00
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
instance.sync();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.1);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.1);
|
|
|
|
}
|
|
|
|
instance.unsync();
|
|
|
|
transport.start(0.1);
|
|
|
|
}, 0.3).then((buffer) => {
|
|
|
|
expect(buffer.isSilent()).to.be.true;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2020-08-26 08:58:14 +00:00
|
|
|
it("can unsync and re-sync triggerAttack to the Transport", () => {
|
|
|
|
return Offline(async ({ transport }) => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
|
|
|
|
instance.sync();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.1);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.1);
|
|
|
|
}
|
|
|
|
transport.start(0.1);
|
|
|
|
await wait(100);
|
|
|
|
instance.unsync();
|
|
|
|
transport.stop();
|
|
|
|
|
|
|
|
instance.sync();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.1);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.1);
|
|
|
|
}
|
|
|
|
transport.start(0.1);
|
|
|
|
}, 1).then((buffer) => {
|
|
|
|
expect(buffer.getTimeOfFirstSound()).to.be.within(0.19, 0.25);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-07-18 18:07:03 +00:00
|
|
|
it("calling sync and unsync multiple times has no effect", () => {
|
2019-10-29 22:09:39 +00:00
|
|
|
return Offline(({ transport }) => {
|
2019-07-18 18:07:03 +00:00
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
instance.sync();
|
|
|
|
instance.sync();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttack(note, 0.1);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttack(0.1);
|
|
|
|
}
|
|
|
|
instance.unsync();
|
|
|
|
instance.unsync();
|
|
|
|
transport.start(0.1);
|
|
|
|
}, 0.3).then((buffer) => {
|
|
|
|
expect(buffer.isSilent()).to.be.true;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("can sync triggerAttackRelease to the Transport", () => {
|
2019-10-29 22:09:39 +00:00
|
|
|
return Offline(({ transport }) => {
|
2019-07-18 18:07:03 +00:00
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
instance.toDestination();
|
|
|
|
instance.sync();
|
|
|
|
if (note) {
|
|
|
|
instance.triggerAttackRelease(note, 0.25, 0.1);
|
|
|
|
} else {
|
|
|
|
instance.triggerAttackRelease(0.25, 0.1);
|
|
|
|
}
|
|
|
|
transport.start(0.1);
|
|
|
|
}, 1).then((buffer) => {
|
|
|
|
expect(buffer.getTimeOfFirstSound()).to.be.within(0.19, 0.25);
|
|
|
|
// test a sample enough in the future for the decay to die down
|
|
|
|
expect(buffer.getRmsAtTime(0.9)).to.be.closeTo(0, 0.1);
|
|
|
|
});
|
|
|
|
});
|
2019-10-29 22:09:39 +00:00
|
|
|
|
|
|
|
it("invokes onsilence", (done) => {
|
|
|
|
Offline(() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
if (instance instanceof Monophonic) {
|
|
|
|
instance.triggerAttackRelease(note, 0.1, 0);
|
|
|
|
instance.onsilence = (voice) => {
|
|
|
|
expect(voice).to.equal(instance);
|
|
|
|
done();
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
done();
|
|
|
|
}
|
|
|
|
}, 3);
|
|
|
|
});
|
2020-08-02 21:29:36 +00:00
|
|
|
|
|
|
|
it("can do portamento glide between notes", () => {
|
|
|
|
return Offline(() => {
|
|
|
|
const instance = new Constr(constrArg);
|
|
|
|
if (instance instanceof Monophonic) {
|
|
|
|
instance.portamento = 0.5;
|
|
|
|
instance.triggerAttackRelease("C4", 0.2, 0);
|
|
|
|
expect(instance.getLevelAtTime(0.4)).to.be.greaterThan(0);
|
|
|
|
instance.triggerAttackRelease("C2", 0.2, 0.4);
|
|
|
|
}
|
|
|
|
}, 0.5);
|
|
|
|
});
|
2019-07-18 18:07:03 +00:00
|
|
|
});
|
|
|
|
}
|