mirror of
https://github.com/Tonejs/Tone.js
synced 2025-01-13 12:28:47 +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()) {
|
||||
this._syncMethod("triggerAttack", 1);
|
||||
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;
|
||||
}
|
||||
|
@ -126,6 +130,10 @@ export abstract class Instrument<Options extends InstrumentOptions> extends Tone
|
|||
this._synced = false;
|
||||
this.triggerAttack = this._original_triggerAttack;
|
||||
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;
|
||||
}
|
||||
|
@ -166,6 +174,11 @@ export abstract class Instrument<Options extends InstrumentOptions> extends Tone
|
|||
abstract triggerRelease(...args: any[]): this;
|
||||
private _original_triggerRelease = this.triggerRelease;
|
||||
|
||||
/**
|
||||
* The release which is scheduled to the timeline.
|
||||
*/
|
||||
protected _syncedRelease = (time: number) => this._original_triggerRelease(time);
|
||||
|
||||
/**
|
||||
* clean up
|
||||
* @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", () => {
|
||||
return Offline(() => {
|
||||
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", () => {
|
||||
|
||||
it("can be constructed with an options object", () => {
|
||||
|
@ -282,7 +345,7 @@ describe("PolySynth", () => {
|
|||
expect(() => {
|
||||
// @ts-ignore
|
||||
new PolySynth(PluckSynth);
|
||||
}).throws(Error)
|
||||
}).throws(Error);
|
||||
});
|
||||
|
||||
it("can pass in the volume", () => {
|
||||
|
|
|
@ -341,10 +341,20 @@ export class PolySynth<Voice extends Monophonic<any> = Synth> extends Instrument
|
|||
if (this._syncState()) {
|
||||
this._syncMethod("triggerAttack", 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* The release which is scheduled to the timeline.
|
||||
*/
|
||||
protected _syncedRelease = (time: number) => this.releaseAll(time);
|
||||
|
||||
/**
|
||||
* Set a member/attribute of the voices
|
||||
* @example
|
||||
|
@ -382,7 +392,7 @@ export class PolySynth<Voice extends Monophonic<any> = Synth> extends Instrument
|
|||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
dispose(): this {
|
||||
super.dispose();
|
||||
this._dummyVoice.dispose();
|
||||
|
|
|
@ -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", () => {
|
||||
it("can play notes with a portamento", () => {
|
||||
return Offline(() => {
|
||||
|
|
Loading…
Reference in a new issue