From 11544708fcf61cd53d44e5c7c7851c6b84aedab3 Mon Sep 17 00:00:00 2001 From: Yotam Mann Date: Thu, 3 Oct 2019 17:29:30 -0400 Subject: [PATCH] feat: OfflineContext yields thread every second of audio rendered so that it doesn't block the thread. fixes #436 --- Tone/core/context/OfflineContext.test.ts | 18 ++++++++++++++++++ Tone/core/context/OfflineContext.ts | 21 ++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Tone/core/context/OfflineContext.test.ts b/Tone/core/context/OfflineContext.test.ts index c8f6b0676..6fc489f2f 100644 --- a/Tone/core/context/OfflineContext.test.ts +++ b/Tone/core/context/OfflineContext.test.ts @@ -38,4 +38,22 @@ context("OfflineContext", () => { } }); }); + + it("can render audio not async", () => { + const ctx = new OfflineContext(1, 0.2, 44100); + const osc = ctx.createOscillator(); + osc.connect(ctx.rawContext.destination); + osc.start(0.1); + return ctx.render(false).then(buffer => { + expect(buffer).to.have.property("length"); + expect(buffer).to.have.property("sampleRate"); + const array = buffer.getChannelData(0); + for (let i = 0; i < array.length; i++) { + if (array[i] !== 0) { + expect(i / array.length).to.be.closeTo(0.5, 0.01); + break; + } + } + }); + }); }); diff --git a/Tone/core/context/OfflineContext.ts b/Tone/core/context/OfflineContext.ts index c3ed7d45b..1a43c8ebd 100644 --- a/Tone/core/context/OfflineContext.ts +++ b/Tone/core/context/OfflineContext.ts @@ -66,23 +66,34 @@ export class OfflineContext extends Context { } /** - * Render just the clock portion of the audiocontext. + * Render just the clock portion of the audio context. */ - private _renderClock(): void { + private async _renderClock(asynchronous: boolean): Promise { + let index = 0; while (this._duration - this._currentTime >= 0) { + // invoke all the callbacks on that time this.emit("tick"); - // increment the clock in 5ms chunks + + // increment the clock in block-sized chunks this._currentTime += 128 / this.sampleRate; + + // yield once a second of audio + index++; + const yieldEvery = Math.floor(this.sampleRate / 128); + if (asynchronous && index % yieldEvery === 0) { + await new Promise(done => setTimeout(done, 1)); + } } } /** * Render the output of the OfflineContext + * @param async If the clock should be rendered asynchronously, which will not block the main thread, but be slightly slower. */ - async render(): Promise { + async render(asynchronous: boolean = true): Promise { await this.workletsAreReady(); - this._renderClock(); + await this._renderClock(asynchronous); return this._context.startRendering(); }