diff --git a/Tone/component/envelope/Envelope.test.ts b/Tone/component/envelope/Envelope.test.ts index 856122d1f..b2f9916e5 100644 --- a/Tone/component/envelope/Envelope.test.ts +++ b/Tone/component/envelope/Envelope.test.ts @@ -737,6 +737,20 @@ describe("Envelope", () => { }); }); + it("can render the envelope to a curve", async () => { + const env = new Envelope(); + const curve = await env.asArray(); + curve.forEach(v => expect(v).to.be.within(0, 1)); + env.dispose(); + }); + + it("can render the envelope to an array with a given length", async () => { + const env = new Envelope(); + const curve = await env.asArray(256); + expect(curve.length).to.equal(256); + env.dispose(); + }); + it("can retrigger partial envelope with custom type", () => { return Offline(() => { const env = new Envelope({ diff --git a/Tone/component/envelope/Envelope.ts b/Tone/component/envelope/Envelope.ts index 2050f4e59..aa14d85e7 100644 --- a/Tone/component/envelope/Envelope.ts +++ b/Tone/component/envelope/Envelope.ts @@ -4,6 +4,7 @@ import { NormalRange, Time } from "../../core/type/Units"; import { optionsFromArguments } from "../../core/util/Defaults"; import { isArray, isObject, isString } from "../../core/util/TypeCheck"; import { connectSignal, Signal } from "../../signal/Signal"; +import { OfflineContext } from "../../core/context/OfflineContext"; type BasicEnvelopeCurve = "linear" | "exponential"; type InternalEnvelopeCurve = BasicEnvelopeCurve | number[]; @@ -438,6 +439,27 @@ export class Envelope extends ToneAudioNode { return this; } + /** + * Render the envelope curve to an array of the given length. + * Good for visualizing the envelope curve + */ + async asArray(length: number = 1024): Promise { + const duration = length / this.context.sampleRate; + const context = new OfflineContext(1, duration, this.context.sampleRate); + // normalize the ADSR for the given duration with 20% sustain time + const totalDuration = (this.toSeconds(this.attack) + this.toSeconds(this.decay) + this.toSeconds(this.release)) * 1.2; + // @ts-ignore + const clone = new this.constructor(Object.assign(this.get(), { + attack: duration * this.toSeconds(this.attack) / totalDuration, + decay: duration * this.toSeconds(this.decay) / totalDuration, + release: duration * this.toSeconds(this.release) / totalDuration, + context + })).toDestination() as Envelope; + clone.triggerAttackRelease(duration * 0.2, 0); + const buffer = await context.render(); + return buffer.getChannelData(0); + } + dispose(): this { super.dispose(); this._sig.dispose();