@@ -31,6 +31,8 @@ class BeatWorkletProcessor extends AudioWorkletProcessor {
31
31
throw new Error(\`BeatProcessor unknown command: '\${cmd}'\`);
32
32
}
33
33
};
34
+ this.expressions = [];
35
+ this.functions = [];
34
36
}
35
37
36
38
// TODO: replace
@@ -42,7 +44,82 @@ class BeatWorkletProcessor extends AudioWorkletProcessor {
42
44
this.byteBeat[fn].call(this.byteBeat, ...args);
43
45
}
44
46
45
- setExpressions(data) {
47
+ callAsync({fn, msgId, args}) {
48
+ let result;
49
+ let error;
50
+ try {
51
+ result = this[fn].call(this, ...args);
52
+ } catch (e) {
53
+ error = e;
54
+ }
55
+ this.port.postMessage({
56
+ cmd: 'asyncResult',
57
+ data: {
58
+ msgId,
59
+ error,
60
+ result,
61
+ },
62
+ });
63
+ }
64
+
65
+ setExpressions(expressions, resetToZero) {
66
+ const compileExpressions = (expressions, expressionType, extra) => {
67
+ const funcs = [];
68
+ try {
69
+ for (let i = 0; i < expressions.length; ++i) {
70
+ const exp = expressions[i];
71
+ if (exp !== this.expressions[i]) {
72
+ funcs.push(ByteBeatCompiler.compileExpression(exp, expressionType, extra));
73
+ } else {
74
+ if (this.functions[i]) {
75
+ funcs.push(this.functions[i]);
76
+ }
77
+ }
78
+ }
79
+ } catch (e) {
80
+ if (e.stack) {
81
+ const m = /<anonymous>:1:(\\d+)/.exec(e.stack);
82
+ if (m) {
83
+ const charNdx = parseInt(m[1]);
84
+ console.error(e.stack);
85
+ console.error(expressions.join('\\n').substring(0, charNdx), '-----VVVVV-----\\n', expressions.substring(charNdx));
86
+ }
87
+ } else {
88
+ console.error(e, e.stack);
89
+ }
90
+ throw e;
91
+ }
92
+ return funcs;
93
+ };
94
+ const funcs = compileExpressions(expressions, this.byteBeat.getExpressionType(), this.byteBeat.getExtra());
95
+ if (!funcs) {
96
+ return {};
97
+ }
98
+
99
+ // copy the expressions
100
+ this.expressions = expressions.slice(0);
101
+ this.functions = funcs;
102
+ const exp = funcs.map(({expression}) => expression);
103
+ // I feel like a Windows programmer. The reset to zero
104
+ // is needed because some expressions do stuff like
105
+ //
106
+ // window.channels = t > 0 ? window.channels : data
107
+ //
108
+ // but because we are now async if I send 2 messages
109
+ // there's no guarantee the time will be zero between
110
+ // the message that sets the expression and the message
111
+ // that sets the time so it's possible t will never be zero
112
+ if (resetToZero) {
113
+ this.setExpressionsAndResetToZero(exp);
114
+ } else {
115
+ this.setExpressionsForReal(exp);
116
+ }
117
+ return {
118
+ numChannels: this.byteBeat.getNumChannels(),
119
+ };
120
+ }
121
+
122
+ setExpressionsForReal(data) {
46
123
this.byteBeat.setExpressions(data);
47
124
}
48
125
@@ -86,14 +163,22 @@ export default class ByteBeatNode extends AudioWorkletNode {
86
163
function : 3 , // return sin(t / 50)
87
164
} ;
88
165
static async setup ( context ) {
89
- return context . audioWorklet . addModule ( workerURL ) ;
166
+ return await context . audioWorklet . addModule ( workerURL ) ;
90
167
}
91
168
static createStack ( ) {
92
169
return new WrappingStack ( ) ;
93
170
}
94
171
static createContext ( ) {
95
172
return ByteBeatCompiler . makeContext ( ) ;
96
173
}
174
+
175
+ #msgIdToResolveMap = new Map ( ) ;
176
+ #nextId = 0 ;
177
+ #type;
178
+ #numChannels = 1 ;
179
+ #desiredSampleRate;
180
+ #actualSampleRate;
181
+
97
182
constructor ( context ) {
98
183
super ( context , 'bytebeat-processor' , { outputChannelCount : [ 2 ] } ) ;
99
184
@@ -104,7 +189,6 @@ export default class ByteBeatNode extends AudioWorkletNode {
104
189
mouseX : event . clientX ,
105
190
mouseY : event . clientY ,
106
191
} ;
107
- this . byteBeat . setExtra ( data ) ;
108
192
this . #sendExtra( data ) ;
109
193
} , true ) ;
110
194
@@ -121,7 +205,6 @@ export default class ByteBeatNode extends AudioWorkletNode {
121
205
// alpha is the compass direction the device is facing in degrees
122
206
compass : eventData . alpha ,
123
207
} ;
124
- this . byteBeat . setExtra ( data ) ;
125
208
this . #sendExtra( data ) ;
126
209
} , false ) ;
127
210
}
@@ -136,9 +219,32 @@ export default class ByteBeatNode extends AudioWorkletNode {
136
219
this . pauseTime = this . startTime ; // time since the song was paused
137
220
this . connected = false ; // whether or not we're playing the bytebeat
138
221
139
- this . byteBeat = new ByteBeatProcessor ( ) ;
140
- this . byteBeat . setActualSampleRate ( context . sampleRate ) ;
222
+ this . #actualSampleRate = context . sampleRate ;
141
223
this . #callFunc( 'setActualSampleRate' , context . sampleRate ) ;
224
+
225
+ this . port . onmessage = this . #processMsg. bind ( this ) ;
226
+ }
227
+
228
+ #processMsg( event ) {
229
+ const { cmd, data} = event . data ;
230
+ switch ( cmd ) {
231
+ case 'asyncResult' : {
232
+ const { msgId, error, result} = data ;
233
+ const { resolve, reject} = this . #msgIdToResolveMap. get ( msgId ) ;
234
+ if ( ! resolve ) {
235
+ throw new Error ( `unknown msg id: ${ msgId } ` ) ;
236
+ }
237
+ this . #msgIdToResolveMap. delete ( msgId ) ;
238
+ if ( error ) {
239
+ reject ( error ) ;
240
+ } else {
241
+ resolve ( result ) ;
242
+ }
243
+ break ;
244
+ }
245
+ default :
246
+ throw Error ( `unknown cmd: ${ cmd } ` ) ;
247
+ }
142
248
}
143
249
144
250
#sendExtra( data ) {
@@ -158,6 +264,22 @@ export default class ByteBeatNode extends AudioWorkletNode {
158
264
} ) ;
159
265
}
160
266
267
+ #callAsync( fnName , ...args ) {
268
+ const msgId = this . #nextId++ ;
269
+ this . port . postMessage ( {
270
+ cmd : 'callAsync' ,
271
+ data : {
272
+ fn : fnName ,
273
+ msgId,
274
+ args,
275
+ } ,
276
+ } ) ;
277
+ const m = this . #msgIdToResolveMap;
278
+ return new Promise ( ( resolve , reject ) => {
279
+ m . set ( msgId , { resolve, reject} ) ;
280
+ } ) ;
281
+ }
282
+
161
283
connect ( dest ) {
162
284
super . connect ( dest ) ;
163
285
if ( ! this . connected ) {
@@ -177,13 +299,11 @@ export default class ByteBeatNode extends AudioWorkletNode {
177
299
178
300
resize ( width , height ) {
179
301
const data = { width, height} ;
180
- this . byteBeat . setExtra ( data ) ;
181
302
this . #sendExtra( data ) ;
182
303
}
183
304
184
305
reset ( ) {
185
306
this . #callFunc( 'reset' ) ;
186
- this . byteBeat . reset ( ) ;
187
307
this . startTime = performance . now ( ) ;
188
308
this . pauseTime = this . startTime ;
189
309
}
@@ -194,82 +314,30 @@ export default class ByteBeatNode extends AudioWorkletNode {
194
314
195
315
getTime ( ) {
196
316
const time = this . connected ? performance . now ( ) : this . pauseTime ;
197
- return ( time - this . startTime ) * 0.001 * this . byteBeat . getDesiredSampleRate ( ) | 0 ;
317
+ return ( time - this . startTime ) * 0.001 * this . getDesiredSampleRate ( ) | 0 ;
198
318
}
199
319
200
- setExpressions ( expressions , resetToZero ) {
201
- const compileExpressions = ( expressions , expressionType , extra ) => {
202
- const funcs = [ ] ;
203
- try {
204
- for ( let i = 0 ; i < expressions . length ; ++ i ) {
205
- const exp = expressions [ i ] ;
206
- if ( exp !== this . expressions [ i ] ) {
207
- funcs . push ( ByteBeatCompiler . compileExpression ( exp , expressionType , extra ) ) ;
208
- } else {
209
- if ( this . functions [ i ] ) {
210
- funcs . push ( this . functions [ i ] ) ;
211
- }
212
- }
213
- }
214
- } catch ( e ) {
215
- if ( e . stack ) {
216
- const m = / < a n o n y m o u s > : 1 : ( \d + ) / . exec ( e . stack ) ;
217
- if ( m ) {
218
- const charNdx = parseInt ( m [ 1 ] ) ;
219
- console . error ( e . stack ) ;
220
- console . error ( expressions . join ( '\n' ) . substring ( 0 , charNdx ) , '-----VVVVV-----\n' , expressions . substring ( charNdx ) ) ;
221
- }
222
- } else {
223
- console . error ( e , e . stack ) ;
224
- }
225
- throw e ;
226
- }
227
- return funcs ;
228
- } ;
229
- const funcs = compileExpressions ( expressions , this . expressionType , this . extra ) ;
230
- if ( ! funcs ) {
231
- return ;
232
- }
233
-
234
- // copy the expressions
235
- this . expressions = expressions . slice ( 0 ) ;
236
- this . functions = funcs ;
237
- const exp = funcs . map ( ( { expression} ) => expression ) ;
238
- // I feel like a Windows programmer. The reset to zero
239
- // is needed because some expressions do stuff like
240
- //
241
- // window.channels = t > 0 ? window.channels : data
242
- //
243
- // but because we are now async if I send 2 messages
244
- // there's no guarantee the time will be zero between
245
- // the message that sets the expression and the message
246
- // that sets the time so it's possible t will never be zero
247
- this . port . postMessage ( {
248
- cmd : resetToZero ? 'setExpressionsAndResetToZero' : 'setExpressions' ,
249
- data : exp ,
250
- } ) ;
251
- this . byteBeat . setExpressions ( exp ) ;
252
- if ( resetToZero ) {
253
- this . reset ( ) ;
254
- }
320
+ async setExpressions ( expressions , resetToZero ) {
321
+ const data = await this . #callAsync( 'setExpressions' , expressions , resetToZero ) ;
322
+ this . #numChannels = data . numChannels ;
323
+ return ;
255
324
}
256
325
257
326
convertToDesiredSampleRate ( rate ) {
258
- return Math . floor ( rate * this . desiredSampleRate / this . actualSampleRate ) ;
327
+ return Math . floor ( rate * this . # desiredSampleRate / this . # actualSampleRate) ;
259
328
}
260
329
261
330
setDesiredSampleRate ( rate ) {
331
+ this . #desiredSampleRate = rate ;
262
332
this . #callFunc( 'setDesiredSampleRate' , rate ) ;
263
- this . byteBeat . setDesiredSampleRate ( rate ) ;
264
333
}
265
334
266
335
getDesiredSampleRate ( ) {
267
- return this . byteBeat . getDesiredSampleRate ( ) ;
336
+ return this . #desiredSampleRate ;
268
337
}
269
338
270
339
setExpressionType ( type ) {
271
340
this . expressionType = type ;
272
- this . byteBeat . setExpressionType ( type ) ;
273
341
this . #callFunc( 'setExpressionType' , type ) ;
274
342
}
275
343
@@ -278,27 +346,19 @@ export default class ByteBeatNode extends AudioWorkletNode {
278
346
}
279
347
280
348
getExpressionType ( ) {
281
- return this . byteBeat . getExpressionType ( ) ;
349
+ return this . expressionType ;
282
350
}
283
351
284
352
setType ( type ) {
285
- this . byteBeat . setType ( type ) ;
353
+ this . # type = type ;
286
354
this . #callFunc( 'setType' , type ) ;
287
355
}
288
356
289
357
getType ( ) {
290
- return this . byteBeat . getType ( ) ;
358
+ return this . #type ;
291
359
}
292
360
293
361
getNumChannels ( ) {
294
- return this . byteBeat . getNumChannels ( ) ;
295
- }
296
-
297
- process ( dataLength , leftData , rightData ) {
298
- this . byteBeat . process ( dataLength , leftData , rightData ) ;
299
- }
300
-
301
- getSampleForTime ( time , context , stack , channel ) {
302
- return this . byteBeat . getSampleForTime ( time , context , stack , channel ) ;
362
+ return this . #numChannels;
303
363
}
304
364
}
0 commit comments