Skip to content

Commit fc5b6f7

Browse files
committed
feat: Render a segment of the envelope as an array
1 parent 9ad519e commit fc5b6f7

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

Tone/component/envelope/Envelope.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,20 @@ describe("Envelope", () => {
737737
});
738738
});
739739

740+
it("can render the envelope to a curve", async () => {
741+
const env = new Envelope();
742+
const curve = await env.asArray();
743+
curve.forEach(v => expect(v).to.be.within(0, 1));
744+
env.dispose();
745+
});
746+
747+
it("can render the envelope to an array with a given length", async () => {
748+
const env = new Envelope();
749+
const curve = await env.asArray(256);
750+
expect(curve.length).to.equal(256);
751+
env.dispose();
752+
});
753+
740754
it("can retrigger partial envelope with custom type", () => {
741755
return Offline(() => {
742756
const env = new Envelope({

Tone/component/envelope/Envelope.ts

+22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { NormalRange, Time } from "../../core/type/Units";
44
import { optionsFromArguments } from "../../core/util/Defaults";
55
import { isArray, isObject, isString } from "../../core/util/TypeCheck";
66
import { connectSignal, Signal } from "../../signal/Signal";
7+
import { OfflineContext } from "../../core/context/OfflineContext";
78

89
type BasicEnvelopeCurve = "linear" | "exponential";
910
type InternalEnvelopeCurve = BasicEnvelopeCurve | number[];
@@ -438,6 +439,27 @@ export class Envelope extends ToneAudioNode<EnvelopeOptions> {
438439
return this;
439440
}
440441

442+
/**
443+
* Render the envelope curve to an array of the given length.
444+
* Good for visualizing the envelope curve
445+
*/
446+
async asArray(length: number = 1024): Promise<Float32Array> {
447+
const duration = length / this.context.sampleRate;
448+
const context = new OfflineContext(1, duration, this.context.sampleRate);
449+
// normalize the ADSR for the given duration with 20% sustain time
450+
const totalDuration = (this.toSeconds(this.attack) + this.toSeconds(this.decay) + this.toSeconds(this.release)) * 1.2;
451+
// @ts-ignore
452+
const clone = new this.constructor(Object.assign(this.get(), {
453+
attack: duration * this.toSeconds(this.attack) / totalDuration,
454+
decay: duration * this.toSeconds(this.decay) / totalDuration,
455+
release: duration * this.toSeconds(this.release) / totalDuration,
456+
context
457+
})).toDestination() as Envelope;
458+
clone.triggerAttackRelease(duration * 0.2, 0);
459+
const buffer = await context.render();
460+
return buffer.getChannelData(0);
461+
}
462+
441463
dispose(): this {
442464
super.dispose();
443465
this._sig.dispose();

0 commit comments

Comments
 (0)