@@ -31,6 +31,8 @@ class BeatWorkletProcessor extends AudioWorkletProcessor {
3131 throw new Error(\`BeatProcessor unknown command: '\${cmd}'\`);
3232 }
3333 };
34+ this.expressions = [];
35+ this.functions = [];
3436 }
3537
3638 // TODO: replace
@@ -42,7 +44,82 @@ class BeatWorkletProcessor extends AudioWorkletProcessor {
4244 this.byteBeat[fn].call(this.byteBeat, ...args);
4345 }
4446
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) {
46123 this.byteBeat.setExpressions(data);
47124 }
48125
@@ -86,14 +163,22 @@ export default class ByteBeatNode extends AudioWorkletNode {
86163 function : 3 , // return sin(t / 50)
87164 } ;
88165 static async setup ( context ) {
89- return context . audioWorklet . addModule ( workerURL ) ;
166+ return await context . audioWorklet . addModule ( workerURL ) ;
90167 }
91168 static createStack ( ) {
92169 return new WrappingStack ( ) ;
93170 }
94171 static createContext ( ) {
95172 return ByteBeatCompiler . makeContext ( ) ;
96173 }
174+
175+ #msgIdToResolveMap = new Map ( ) ;
176+ #nextId = 0 ;
177+ #type;
178+ #numChannels = 1 ;
179+ #desiredSampleRate;
180+ #actualSampleRate;
181+
97182 constructor ( context ) {
98183 super ( context , 'bytebeat-processor' , { outputChannelCount : [ 2 ] } ) ;
99184
@@ -104,7 +189,6 @@ export default class ByteBeatNode extends AudioWorkletNode {
104189 mouseX : event . clientX ,
105190 mouseY : event . clientY ,
106191 } ;
107- this . byteBeat . setExtra ( data ) ;
108192 this . #sendExtra( data ) ;
109193 } , true ) ;
110194
@@ -121,7 +205,6 @@ export default class ByteBeatNode extends AudioWorkletNode {
121205 // alpha is the compass direction the device is facing in degrees
122206 compass : eventData . alpha ,
123207 } ;
124- this . byteBeat . setExtra ( data ) ;
125208 this . #sendExtra( data ) ;
126209 } , false ) ;
127210 }
@@ -136,9 +219,32 @@ export default class ByteBeatNode extends AudioWorkletNode {
136219 this . pauseTime = this . startTime ; // time since the song was paused
137220 this . connected = false ; // whether or not we're playing the bytebeat
138221
139- this . byteBeat = new ByteBeatProcessor ( ) ;
140- this . byteBeat . setActualSampleRate ( context . sampleRate ) ;
222+ this . #actualSampleRate = context . sampleRate ;
141223 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+ }
142248 }
143249
144250 #sendExtra( data ) {
@@ -158,6 +264,22 @@ export default class ByteBeatNode extends AudioWorkletNode {
158264 } ) ;
159265 }
160266
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+
161283 connect ( dest ) {
162284 super . connect ( dest ) ;
163285 if ( ! this . connected ) {
@@ -177,13 +299,11 @@ export default class ByteBeatNode extends AudioWorkletNode {
177299
178300 resize ( width , height ) {
179301 const data = { width, height} ;
180- this . byteBeat . setExtra ( data ) ;
181302 this . #sendExtra( data ) ;
182303 }
183304
184305 reset ( ) {
185306 this . #callFunc( 'reset' ) ;
186- this . byteBeat . reset ( ) ;
187307 this . startTime = performance . now ( ) ;
188308 this . pauseTime = this . startTime ;
189309 }
@@ -194,82 +314,30 @@ export default class ByteBeatNode extends AudioWorkletNode {
194314
195315 getTime ( ) {
196316 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 ;
198318 }
199319
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 ;
255324 }
256325
257326 convertToDesiredSampleRate ( rate ) {
258- return Math . floor ( rate * this . desiredSampleRate / this . actualSampleRate ) ;
327+ return Math . floor ( rate * this . # desiredSampleRate / this . # actualSampleRate) ;
259328 }
260329
261330 setDesiredSampleRate ( rate ) {
331+ this . #desiredSampleRate = rate ;
262332 this . #callFunc( 'setDesiredSampleRate' , rate ) ;
263- this . byteBeat . setDesiredSampleRate ( rate ) ;
264333 }
265334
266335 getDesiredSampleRate ( ) {
267- return this . byteBeat . getDesiredSampleRate ( ) ;
336+ return this . #desiredSampleRate ;
268337 }
269338
270339 setExpressionType ( type ) {
271340 this . expressionType = type ;
272- this . byteBeat . setExpressionType ( type ) ;
273341 this . #callFunc( 'setExpressionType' , type ) ;
274342 }
275343
@@ -278,27 +346,19 @@ export default class ByteBeatNode extends AudioWorkletNode {
278346 }
279347
280348 getExpressionType ( ) {
281- return this . byteBeat . getExpressionType ( ) ;
349+ return this . expressionType ;
282350 }
283351
284352 setType ( type ) {
285- this . byteBeat . setType ( type ) ;
353+ this . # type = type ;
286354 this . #callFunc( 'setType' , type ) ;
287355 }
288356
289357 getType ( ) {
290- return this . byteBeat . getType ( ) ;
358+ return this . #type ;
291359 }
292360
293361 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;
303363 }
304364}
0 commit comments