mirror of
https://github.com/Tonejs/Tone.js
synced 2025-01-14 04:43:54 +00:00
Allow instrument and PolySynth to be scheduled to the transport stop/loop events
addresses #924
This commit is contained in:
parent
6dd22e752f
commit
954a4fce37
4 changed files with 168 additions and 19 deletions
|
@ -83,6 +83,10 @@ export abstract class Instrument<Options extends InstrumentOptions> extends Tone
|
||||||
if (this._syncState()) {
|
if (this._syncState()) {
|
||||||
this._syncMethod("triggerAttack", 1);
|
this._syncMethod("triggerAttack", 1);
|
||||||
this._syncMethod("triggerRelease", 0);
|
this._syncMethod("triggerRelease", 0);
|
||||||
|
|
||||||
|
this.context.transport.on("stop", this._syncedRelease);
|
||||||
|
this.context.transport.on("pause", this._syncedRelease);
|
||||||
|
this.context.transport.on("loopEnd", this._syncedRelease);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -126,6 +130,10 @@ export abstract class Instrument<Options extends InstrumentOptions> extends Tone
|
||||||
this._synced = false;
|
this._synced = false;
|
||||||
this.triggerAttack = this._original_triggerAttack;
|
this.triggerAttack = this._original_triggerAttack;
|
||||||
this.triggerRelease = this._original_triggerRelease;
|
this.triggerRelease = this._original_triggerRelease;
|
||||||
|
|
||||||
|
this.context.transport.off("stop", this._syncedRelease);
|
||||||
|
this.context.transport.off("pause", this._syncedRelease);
|
||||||
|
this.context.transport.off("loopEnd", this._syncedRelease);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -166,6 +174,11 @@ export abstract class Instrument<Options extends InstrumentOptions> extends Tone
|
||||||
abstract triggerRelease(...args: any[]): this;
|
abstract triggerRelease(...args: any[]): this;
|
||||||
private _original_triggerRelease = this.triggerRelease;
|
private _original_triggerRelease = this.triggerRelease;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The release which is scheduled to the timeline.
|
||||||
|
*/
|
||||||
|
protected _syncedRelease = (time: number) => this._original_triggerRelease(time);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clean up
|
* clean up
|
||||||
* @returns {Instrument} this
|
* @returns {Instrument} this
|
||||||
|
|
|
@ -135,23 +135,6 @@ describe("PolySynth", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can be synced to the transport", () => {
|
|
||||||
return Offline(({ transport }) => {
|
|
||||||
const polySynth = new PolySynth(Synth, {
|
|
||||||
envelope: {
|
|
||||||
release: 0.1,
|
|
||||||
},
|
|
||||||
}).sync();
|
|
||||||
polySynth.toDestination();
|
|
||||||
polySynth.triggerAttackRelease("C4", 0.1, 0.1);
|
|
||||||
polySynth.triggerAttackRelease("E4", 0.1, 0.3);
|
|
||||||
transport.start(0.1);
|
|
||||||
}, 0.8).then((buffer) => {
|
|
||||||
expect(buffer.getTimeOfFirstSound()).to.be.closeTo(0.2, 0.01);
|
|
||||||
expect(buffer.getTimeOfLastSound()).to.be.closeTo(0.6, 0.01);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("disposes voices when they are no longer used", () => {
|
it("disposes voices when they are no longer used", () => {
|
||||||
return Offline(() => {
|
return Offline(() => {
|
||||||
const polySynth = new PolySynth(Synth, {
|
const polySynth = new PolySynth(Synth, {
|
||||||
|
@ -266,6 +249,86 @@ describe("PolySynth", () => {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context("Transport sync", () => {
|
||||||
|
it("can be synced to the transport", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const polySynth = new PolySynth(Synth, {
|
||||||
|
envelope: {
|
||||||
|
release: 0.1,
|
||||||
|
},
|
||||||
|
}).sync();
|
||||||
|
polySynth.toDestination();
|
||||||
|
polySynth.triggerAttackRelease("C4", 0.1, 0.1);
|
||||||
|
polySynth.triggerAttackRelease("E4", 0.1, 0.3);
|
||||||
|
transport.start(0.1);
|
||||||
|
}, 0.8).then((buffer) => {
|
||||||
|
expect(buffer.getTimeOfFirstSound()).to.be.closeTo(0.2, 0.01);
|
||||||
|
expect(buffer.getTimeOfLastSound()).to.be.closeTo(0.6, 0.01);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is silent until the transport is started", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const synth = new PolySynth(Synth).sync().toDestination();
|
||||||
|
synth.triggerAttackRelease("C4", 0.5);
|
||||||
|
transport.start(0.5);
|
||||||
|
}, 1).then((buffer) => {
|
||||||
|
expect(buffer.getTimeOfFirstSound()).is.closeTo(0.5, 0.1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops when the transport is stopped", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const synth = new PolySynth(Synth, {
|
||||||
|
envelope: {
|
||||||
|
release: 0
|
||||||
|
}
|
||||||
|
}).sync().toDestination();
|
||||||
|
synth.triggerAttackRelease("C4", 0.5);
|
||||||
|
transport.start(0.5).stop(1);
|
||||||
|
}, 1.5).then((buffer) => {
|
||||||
|
expect(buffer.getTimeOfLastSound()).is.closeTo(1, 0.1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("goes silent at the loop boundary", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const synth = new PolySynth(Synth, {
|
||||||
|
envelope: {
|
||||||
|
release: 0
|
||||||
|
}
|
||||||
|
}).sync().toDestination();
|
||||||
|
synth.triggerAttackRelease("C4", 0.8, 0.5);
|
||||||
|
transport.loopEnd = 1;
|
||||||
|
transport.loop = true;
|
||||||
|
transport.start();
|
||||||
|
}, 2).then((buffer) => {
|
||||||
|
expect(buffer.getRmsAtTime(0)).to.be.closeTo(0, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(0.6)).to.be.closeTo(0.2, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(1.1)).to.be.closeTo(0, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(1.6)).to.be.closeTo(0.2, 0.05);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can unsync", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const synth = new PolySynth(Synth, {
|
||||||
|
envelope: {
|
||||||
|
sustain: 1,
|
||||||
|
release: 0
|
||||||
|
}
|
||||||
|
}).sync().toDestination().unsync();
|
||||||
|
synth.triggerAttackRelease("C4", 1, 0.5);
|
||||||
|
transport.start().stop(1);
|
||||||
|
}, 2).then((buffer) => {
|
||||||
|
expect(buffer.getRmsAtTime(0)).to.be.closeTo(0, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(0.6)).to.be.closeTo(0.6, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(1.4)).to.be.closeTo(0.6, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(1.6)).to.be.closeTo(0, 0.05);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
context("API", () => {
|
context("API", () => {
|
||||||
|
|
||||||
it("can be constructed with an options object", () => {
|
it("can be constructed with an options object", () => {
|
||||||
|
@ -282,7 +345,7 @@ describe("PolySynth", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
new PolySynth(PluckSynth);
|
new PolySynth(PluckSynth);
|
||||||
}).throws(Error)
|
}).throws(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can pass in the volume", () => {
|
it("can pass in the volume", () => {
|
||||||
|
|
|
@ -341,10 +341,20 @@ export class PolySynth<Voice extends Monophonic<any> = Synth> extends Instrument
|
||||||
if (this._syncState()) {
|
if (this._syncState()) {
|
||||||
this._syncMethod("triggerAttack", 1);
|
this._syncMethod("triggerAttack", 1);
|
||||||
this._syncMethod("triggerRelease", 1);
|
this._syncMethod("triggerRelease", 1);
|
||||||
|
|
||||||
|
// make sure that the sound doesn't play after its been stopped
|
||||||
|
this.context.transport.on("stop", this._syncedRelease);
|
||||||
|
this.context.transport.on("pause", this._syncedRelease);
|
||||||
|
this.context.transport.on("loopEnd", this._syncedRelease);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The release which is scheduled to the timeline.
|
||||||
|
*/
|
||||||
|
protected _syncedRelease = (time: number) => this.releaseAll(time);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a member/attribute of the voices
|
* Set a member/attribute of the voices
|
||||||
* @example
|
* @example
|
||||||
|
|
|
@ -106,6 +106,69 @@ describe("Synth", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context("Transport sync", () => {
|
||||||
|
it("is silent until the transport is started", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const synth = new Synth().sync().toDestination();
|
||||||
|
synth.triggerAttackRelease("C4", 0.5);
|
||||||
|
transport.start(0.5);
|
||||||
|
}, 1).then((buffer) => {
|
||||||
|
expect(buffer.getTimeOfFirstSound()).is.closeTo(0.5, 0.1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stops when the transport is stopped", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const synth = new Synth({
|
||||||
|
envelope: {
|
||||||
|
release: 0
|
||||||
|
}
|
||||||
|
}).sync().toDestination();
|
||||||
|
synth.triggerAttackRelease("C4", 0.5);
|
||||||
|
transport.start(0.5).stop(1);
|
||||||
|
}, 1.5).then((buffer) => {
|
||||||
|
expect(buffer.getTimeOfLastSound()).is.closeTo(1, 0.1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("goes silent at the loop boundary", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const synth = new Synth({
|
||||||
|
envelope: {
|
||||||
|
release: 0
|
||||||
|
}
|
||||||
|
}).sync().toDestination();
|
||||||
|
synth.triggerAttackRelease("C4", 0.8, 0.5);
|
||||||
|
transport.loopEnd = 1;
|
||||||
|
transport.loop = true;
|
||||||
|
transport.start();
|
||||||
|
}, 2).then((buffer) => {
|
||||||
|
expect(buffer.getRmsAtTime(0)).to.be.closeTo(0, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(0.6)).to.be.closeTo(0.2, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(1.1)).to.be.closeTo(0, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(1.6)).to.be.closeTo(0.2, 0.05);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can unsync", () => {
|
||||||
|
return Offline(({ transport }) => {
|
||||||
|
const synth = new Synth({
|
||||||
|
envelope: {
|
||||||
|
sustain: 1,
|
||||||
|
release: 0
|
||||||
|
}
|
||||||
|
}).sync().toDestination().unsync();
|
||||||
|
synth.triggerAttackRelease("C4", 1, 0.5);
|
||||||
|
transport.start().stop(1);
|
||||||
|
}, 2).then((buffer) => {
|
||||||
|
expect(buffer.getRmsAtTime(0)).to.be.closeTo(0, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(0.6)).to.be.closeTo(0.6, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(1.4)).to.be.closeTo(0.6, 0.05);
|
||||||
|
expect(buffer.getRmsAtTime(1.6)).to.be.closeTo(0, 0.05);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
context("Portamento", () => {
|
context("Portamento", () => {
|
||||||
it("can play notes with a portamento", () => {
|
it("can play notes with a portamento", () => {
|
||||||
return Offline(() => {
|
return Offline(() => {
|
||||||
|
|
Loading…
Reference in a new issue