/
Draw.ts
125 lines (110 loc) · 3.34 KB
/
Draw.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { ToneWithContext, ToneWithContextOptions } from "../context/ToneWithContext";
import { Seconds, Time } from "../type/Units";
import { Timeline, TimelineEvent } from "./Timeline";
import { onContextClose, onContextInit } from "../context/ContextInitialization";
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
* Tone.Transport.schedule((time) => {
* // use the time argument to schedule a callback with Draw
* Tone.Draw.schedule(() => {
* // do drawing or DOM manipulation here
* console.log(time);
* }, time);
* }, "+0.5");
* Tone.Transport.start();
* @category Core
*/
export class Draw extends ToneWithContext<ToneWithContextOptions> {
readonly name: string = "Draw";
/**
* The duration after which events are not invoked.
*/
expiration: Seconds = 0.25;
/**
* 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).
*/
anticipation: Seconds = 0.008;
/**
* All of the events.
*/
private _events: Timeline<DrawEvent> = new Timeline();
/**
* The draw loop
*/
private _boundDrawLoop = this._drawLoop.bind(this);
/**
* The animation frame id
*/
private _animationFrame = -1;
/**
* Schedule a function at the given time to be invoked
* on the nearest animation frame.
* @param callback Callback is invoked at the given time.
* @param time The time relative to the AudioContext time to invoke the callback.
* @example
* Tone.Transport.scheduleRepeat(time => {
* Tone.Draw.schedule(() => console.log(time), time);
* }, 1);
* Tone.Transport.start();
*/
schedule(callback: () => void, time: Time): this {
this._events.add({
callback,
time: this.toSeconds(time),
});
// start the draw loop on the first event
if (this._events.length === 1) {
this._animationFrame = requestAnimationFrame(this._boundDrawLoop);
}
return this;
}
/**
* Cancel events scheduled after the given time
* @param after Time after which scheduled events will be removed from the scheduling timeline.
*/
cancel(after?: Time): this {
this._events.cancel(this.toSeconds(after));
return this;
}
/**
* The draw loop
*/
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 {
super.dispose();
this._events.dispose();
cancelAnimationFrame(this._animationFrame);
return this;
}
}
//-------------------------------------
// INITIALIZATION
//-------------------------------------
onContextInit(context => {
context.draw = new Draw({ context });
});
onContextClose(context => {
context.draw.dispose();
});