-
Notifications
You must be signed in to change notification settings - Fork 977
/
Ticker.ts
154 lines (133 loc) · 3.26 KB
/
Ticker.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { Seconds } from "../type/Units";
export type TickerClockSource = "worker" | "timeout" | "offline";
/**
* A class which provides a reliable callback using either
* a Web Worker, or if that isn't supported, falls back to setTimeout.
*/
export class Ticker {
/**
* Either "worker" or "timeout" or "offline"
*/
private _type: TickerClockSource;
/**
* The update interval of the worker
*/
private _updateInterval!: Seconds;
/**
* The lowest allowable interval, preferably calculated from context sampleRate
*/
private _minimumUpdateInterval: Seconds;
/**
* The callback to invoke at regular intervals
*/
private _callback: () => void;
/**
* track the callback interval
*/
private _timeout!: ReturnType<typeof setTimeout>;
/**
* private reference to the worker
*/
private _worker!: Worker;
constructor(callback: () => void, type: TickerClockSource, updateInterval: Seconds, contextSampleRate?: number) {
this._callback = callback;
this._type = type;
this._minimumUpdateInterval = Math.max(128/(contextSampleRate || 44100), .001);
this.updateInterval = updateInterval;
// create the clock source for the first time
this._createClock();
}
/**
* Generate a web worker
*/
private _createWorker(): void {
const blob = new Blob([
/* javascript */`
// the initial timeout time
let timeoutTime = ${(this._updateInterval * 1000).toFixed(1)};
// onmessage callback
self.onmessage = function(msg){
timeoutTime = parseInt(msg.data);
};
// the tick function which posts a message
// and schedules a new tick
function tick(){
setTimeout(tick, timeoutTime);
self.postMessage('tick');
}
// call tick initially
tick();
`
], { type: "text/javascript" });
const blobUrl = URL.createObjectURL(blob);
const worker = new Worker(blobUrl);
worker.onmessage = this._callback.bind(this);
this._worker = worker;
}
/**
* Create a timeout loop
*/
private _createTimeout(): void {
this._timeout = setTimeout(() => {
this._createTimeout();
this._callback();
}, this._updateInterval * 1000);
}
/**
* Create the clock source.
*/
private _createClock(): void {
if (this._type === "worker") {
try {
this._createWorker();
} catch (e) {
// workers not supported, fallback to timeout
this._type = "timeout";
this._createClock();
}
} else if (this._type === "timeout") {
this._createTimeout();
}
}
/**
* Clean up the current clock source
*/
private _disposeClock(): void {
if (this._timeout) {
clearTimeout(this._timeout);
}
if (this._worker) {
this._worker.terminate();
this._worker.onmessage = null;
}
}
/**
* The rate in seconds the ticker will update
*/
get updateInterval(): Seconds {
return this._updateInterval;
}
set updateInterval(interval: Seconds) {
this._updateInterval = Math.max(interval, this._minimumUpdateInterval);
if (this._type === "worker") {
this._worker?.postMessage(this._updateInterval * 1000);
}
}
/**
* The type of the ticker, either a worker or a timeout
*/
get type(): TickerClockSource {
return this._type;
}
set type(type: TickerClockSource) {
this._disposeClock();
this._type = type;
this._createClock();
}
/**
* Clean up
*/
dispose(): void {
this._disposeClock();
}
}