Skip to content

Commit b28f455

Browse files
committed
make it run user-code only in AudtoWorklet
Need to fix 2 visualizers
1 parent 64b842d commit b28f455

File tree

3 files changed

+149
-84
lines changed

3 files changed

+149
-84
lines changed

editor/index.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import WebGLVisualizer from './visualizers/WebGLVisualizer.js';
1010
import CanvasVisualizer from './visualizers/CanvasVisualizer.js';
1111
import NullVisualizer from './visualizers/NullVisualizer.js';
1212

13-
import DataEffect from './visualizers/effects/DataEffect.js';
13+
//import DataEffect from './visualizers/effects/DataEffect.js';
1414
import FFTEffect from './visualizers/effects/FFTEffect.js';
1515
//import SampleEffect from './visualizers/effects/SampleEffect.js';
1616
import VSAEffect from './visualizers/effects/VSAEffect.js';
17-
import WaveEffect from './visualizers/effects/WaveEffect.js';
17+
//import WaveEffect from './visualizers/effects/WaveEffect.js';
1818

1919
import songList from './songList.js';
2020

@@ -252,9 +252,9 @@ async function main() {
252252
g_vsaVisualizer = new WebGLVisualizer(gl, [g_vsaEffect]);
253253

254254
const effects = [
255-
new DataEffect(gl),
255+
//new DataEffect(gl),
256256
// ...(showSample ? [new SampleEffect(gl)] : []),
257-
new WaveEffect(gl),
257+
//new WaveEffect(gl),
258258
new FFTEffect(gl),
259259
];
260260
g_visualizers = [
@@ -589,6 +589,7 @@ async function setExpressions(expressions, resetToZero) {
589589
let error;
590590
try {
591591
await g_byteBeat.setExpressions(expressions, resetToZero);
592+
592593
} catch (e) {
593594
error = e;
594595
}

src/ByteBeatNode.js

Lines changed: 140 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -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 = /<anonymous>: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
}

src/ByteBeatProcessor.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ export default class ByteBeatProcessor {
153153
Object.assign(this.extra, props);
154154
}
155155

156+
getExtra() {
157+
return {...this.extra};
158+
}
159+
156160
getTime() {
157161
return this.convertToDesiredSampleRate(this.dstSampleCount);
158162
}

0 commit comments

Comments
 (0)