2019-08-19 18:15:53 +00:00
|
|
|
import { createOfflineAudioContext } from "../context/AudioContext";
|
2019-05-23 18:00:49 +00:00
|
|
|
import { Context } from "../context/Context";
|
2019-07-30 19:35:27 +00:00
|
|
|
import { Seconds } from "../type/Units";
|
2019-08-19 17:01:37 +00:00
|
|
|
import { isOfflineAudioContext } from "../util/AdvancedTypeCheck";
|
2019-10-03 22:03:21 +00:00
|
|
|
import { ToneAudioBuffer } from "./ToneAudioBuffer";
|
2019-05-23 18:00:49 +00:00
|
|
|
|
|
|
|
/**
|
2019-08-30 16:02:18 +00:00
|
|
|
* Wrapper around the OfflineAudioContext
|
2019-08-26 17:44:43 +00:00
|
|
|
* @category Core
|
2019-05-23 18:00:49 +00:00
|
|
|
*/
|
|
|
|
export class OfflineContext extends Context {
|
|
|
|
|
2019-09-04 23:18:44 +00:00
|
|
|
readonly name: string = "OfflineContext";
|
2019-05-23 18:00:49 +00:00
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* A private reference to the duration
|
2019-05-23 18:00:49 +00:00
|
|
|
*/
|
|
|
|
private readonly _duration: Seconds;
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* An artificial clock source
|
2019-05-23 18:00:49 +00:00
|
|
|
*/
|
|
|
|
private _currentTime: Seconds = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Private reference to the OfflineAudioContext.
|
|
|
|
*/
|
|
|
|
protected _context!: OfflineAudioContext;
|
|
|
|
|
2019-10-04 15:51:52 +00:00
|
|
|
readonly isOffline: boolean = true;
|
|
|
|
|
2019-08-26 17:44:43 +00:00
|
|
|
/**
|
2019-08-30 16:06:38 +00:00
|
|
|
* @param channels The number of channels to render
|
|
|
|
* @param duration The duration to render in seconds
|
|
|
|
* @param sampleRate the sample rate to render at
|
2019-08-26 17:44:43 +00:00
|
|
|
*/
|
2019-08-16 16:49:04 +00:00
|
|
|
constructor(
|
2019-08-19 16:59:07 +00:00
|
|
|
channels: number,
|
2019-08-16 16:49:04 +00:00
|
|
|
duration: Seconds, sampleRate: number,
|
2019-08-19 16:59:07 +00:00
|
|
|
);
|
2019-08-30 16:02:18 +00:00
|
|
|
constructor(context: OfflineAudioContext);
|
2019-08-19 16:59:07 +00:00
|
|
|
constructor() {
|
2019-05-23 18:00:49 +00:00
|
|
|
|
|
|
|
super({
|
|
|
|
clockSource: "offline",
|
2019-08-19 16:59:07 +00:00
|
|
|
context: isOfflineAudioContext(arguments[0]) ?
|
2019-08-19 18:15:53 +00:00
|
|
|
arguments[0] : createOfflineAudioContext(arguments[0], arguments[1] * arguments[2], arguments[2]),
|
2019-05-23 18:00:49 +00:00
|
|
|
lookAhead: 0,
|
2019-08-19 16:59:07 +00:00
|
|
|
updateInterval: isOfflineAudioContext(arguments[0]) ?
|
2019-09-16 03:32:40 +00:00
|
|
|
128 / arguments[0].sampleRate : 128 / arguments[2],
|
2019-05-23 18:00:49 +00:00
|
|
|
});
|
|
|
|
|
2019-08-19 16:59:07 +00:00
|
|
|
this._duration = isOfflineAudioContext(arguments[0]) ?
|
|
|
|
arguments[0].length / arguments[0].sampleRate : arguments[1];
|
2019-05-23 18:00:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* Override the now method to point to the internal clock time
|
2019-05-23 18:00:49 +00:00
|
|
|
*/
|
|
|
|
now(): Seconds {
|
|
|
|
return this._currentTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Same as this.now()
|
|
|
|
*/
|
|
|
|
get currentTime(): Seconds {
|
|
|
|
return this._currentTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-10-03 21:29:30 +00:00
|
|
|
* Render just the clock portion of the audio context.
|
2019-05-23 18:00:49 +00:00
|
|
|
*/
|
2019-10-03 21:29:30 +00:00
|
|
|
private async _renderClock(asynchronous: boolean): Promise<void> {
|
|
|
|
let index = 0;
|
2019-05-23 18:00:49 +00:00
|
|
|
while (this._duration - this._currentTime >= 0) {
|
2019-10-03 21:29:30 +00:00
|
|
|
|
2019-05-23 18:00:49 +00:00
|
|
|
// invoke all the callbacks on that time
|
|
|
|
this.emit("tick");
|
2019-10-03 21:29:30 +00:00
|
|
|
|
|
|
|
// increment the clock in block-sized chunks
|
2019-08-19 17:01:37 +00:00
|
|
|
this._currentTime += 128 / this.sampleRate;
|
2019-10-03 21:29:30 +00:00
|
|
|
|
|
|
|
// 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));
|
|
|
|
}
|
2019-05-23 18:00:49 +00:00
|
|
|
}
|
2019-09-19 20:55:09 +00:00
|
|
|
}
|
2019-05-23 18:00:49 +00:00
|
|
|
|
2019-09-19 20:55:09 +00:00
|
|
|
/**
|
|
|
|
* Render the output of the OfflineContext
|
2019-10-23 03:04:52 +00:00
|
|
|
* @param asynchronous If the clock should be rendered asynchronously, which will not block the main thread, but be slightly slower.
|
2019-09-19 20:55:09 +00:00
|
|
|
*/
|
2019-10-03 22:03:21 +00:00
|
|
|
async render(asynchronous: boolean = true): Promise<ToneAudioBuffer> {
|
2019-09-27 21:52:37 +00:00
|
|
|
await this.workletsAreReady();
|
2019-10-03 21:29:30 +00:00
|
|
|
await this._renderClock(asynchronous);
|
2019-10-03 22:03:21 +00:00
|
|
|
const buffer = await this._context.startRendering();
|
|
|
|
return new ToneAudioBuffer(buffer);
|
2019-05-23 18:00:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* Close the context
|
2019-05-23 18:00:49 +00:00
|
|
|
*/
|
2019-08-16 16:49:04 +00:00
|
|
|
close(): Promise<void> {
|
|
|
|
return Promise.resolve();
|
2019-05-23 18:00:49 +00:00
|
|
|
}
|
|
|
|
}
|