2019-06-19 13:56:21 +00:00
|
|
|
import { ToneWithContext, ToneWithContextOptions } from "../context/ToneWithContext";
|
2019-07-30 19:35:27 +00:00
|
|
|
import { Seconds, Time } from "../type/Units";
|
2019-06-19 13:53:36 +00:00
|
|
|
import { Timeline, TimelineEvent } from "./Timeline";
|
2019-10-28 16:12:27 +00:00
|
|
|
import { onContextClose, onContextInit } from "../context/ContextInitialization";
|
2019-06-19 13:53:36 +00:00
|
|
|
|
|
|
|
interface DrawEvent extends TimelineEvent {
|
|
|
|
callback: () => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draw is useful for synchronizing visuals and audio events.
|
|
|
|
* Callbacks from Tone.Transport or any of the Tone.Event classes
|
|
|
|
* always happen _before_ the scheduled time and are not synchronized
|
|
|
|
* to the animation frame so they are not good for triggering tightly
|
|
|
|
* synchronized visuals and sound. Draw makes it easy to schedule
|
|
|
|
* callbacks using the AudioContext time and uses requestAnimationFrame.
|
|
|
|
* @example
|
2020-04-17 02:24:18 +00:00
|
|
|
* Tone.Transport.schedule((time) => {
|
2020-04-29 21:15:12 +00:00
|
|
|
* // use the time argument to schedule a callback with Draw
|
|
|
|
* Tone.Draw.schedule(() => {
|
|
|
|
* // do drawing or DOM manipulation here
|
|
|
|
* console.log(time);
|
|
|
|
* }, time);
|
2019-10-24 22:01:27 +00:00
|
|
|
* }, "+0.5");
|
2020-04-29 21:15:12 +00:00
|
|
|
* Tone.Transport.start();
|
2019-08-26 17:44:43 +00:00
|
|
|
* @category Core
|
2019-06-19 13:53:36 +00:00
|
|
|
*/
|
2019-06-19 13:56:21 +00:00
|
|
|
export class Draw extends ToneWithContext<ToneWithContextOptions> {
|
2019-06-19 13:53:36 +00:00
|
|
|
|
2019-09-04 23:18:44 +00:00
|
|
|
readonly name: string = "Draw";
|
2019-06-19 13:53:36 +00:00
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* The duration after which events are not invoked.
|
2019-06-19 13:53:36 +00:00
|
|
|
*/
|
|
|
|
expiration: Seconds = 0.25;
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* The amount of time before the scheduled time
|
|
|
|
* that the callback can be invoked. Default is
|
|
|
|
* half the time of an animation frame (0.008 seconds).
|
2019-06-19 13:53:36 +00:00
|
|
|
*/
|
|
|
|
anticipation: Seconds = 0.008;
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* All of the events.
|
2019-06-19 13:53:36 +00:00
|
|
|
*/
|
|
|
|
private _events: Timeline<DrawEvent> = new Timeline();
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* The draw loop
|
2019-06-19 13:53:36 +00:00
|
|
|
*/
|
|
|
|
private _boundDrawLoop = this._drawLoop.bind(this);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The animation frame id
|
|
|
|
*/
|
2019-11-17 18:09:19 +00:00
|
|
|
private _animationFrame = -1;
|
2019-06-19 13:53:36 +00:00
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* Schedule a function at the given time to be invoked
|
|
|
|
* on the nearest animation frame.
|
2019-08-30 16:06:38 +00:00
|
|
|
* @param callback Callback is invoked at the given time.
|
|
|
|
* @param time The time relative to the AudioContext time to invoke the callback.
|
2020-04-29 21:15:12 +00:00
|
|
|
* @example
|
|
|
|
* Tone.Transport.scheduleRepeat(time => {
|
|
|
|
* Tone.Draw.schedule(() => console.log(time), time);
|
|
|
|
* }, 1);
|
|
|
|
* Tone.Transport.start();
|
2019-06-19 13:53:36 +00:00
|
|
|
*/
|
|
|
|
schedule(callback: () => void, time: Time): this {
|
|
|
|
this._events.add({
|
|
|
|
callback,
|
2019-09-16 03:32:40 +00:00
|
|
|
time: this.toSeconds(time),
|
2019-06-19 13:53:36 +00:00
|
|
|
});
|
|
|
|
// start the draw loop on the first event
|
|
|
|
if (this._events.length === 1) {
|
|
|
|
this._animationFrame = requestAnimationFrame(this._boundDrawLoop);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* Cancel events scheduled after the given time
|
2019-08-30 16:06:38 +00:00
|
|
|
* @param after Time after which scheduled events will be removed from the scheduling timeline.
|
2019-06-19 13:53:36 +00:00
|
|
|
*/
|
|
|
|
cancel(after?: Time): this {
|
|
|
|
this._events.cancel(this.toSeconds(after));
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-09-14 20:39:18 +00:00
|
|
|
* The draw loop
|
2019-06-19 13:53:36 +00:00
|
|
|
*/
|
|
|
|
private _drawLoop(): void {
|
|
|
|
const now = this.context.currentTime;
|
|
|
|
while (this._events.length && (this._events.peek() as DrawEvent).time - this.anticipation <= now) {
|
|
|
|
const event = this._events.shift();
|
|
|
|
if (event && now - event.time <= this.expiration) {
|
|
|
|
event.callback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this._events.length > 0) {
|
|
|
|
this._animationFrame = requestAnimationFrame(this._boundDrawLoop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dispose(): this {
|
2019-07-23 16:11:57 +00:00
|
|
|
super.dispose();
|
2019-06-19 13:53:36 +00:00
|
|
|
this._events.dispose();
|
|
|
|
cancelAnimationFrame(this._animationFrame);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
2019-10-28 16:12:27 +00:00
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// INITIALIZATION
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
onContextInit(context => {
|
|
|
|
context.draw = new Draw({ context });
|
|
|
|
});
|
|
|
|
|
|
|
|
onContextClose(context => {
|
|
|
|
context.draw.dispose();
|
|
|
|
});
|