Am not sure if this is a specification issue or a Chromium bug (Chromium/Chrome is the only browser implementation am aware of at this time).
Initially ignored the issue that first observed when calling port.postMessage() to main thread where resume() is executed. Occasionally the processing would not commence, so refreshed the page.
When a user-defined method is created in the extended class if values used in process() were set in the method those values were not immediately available in process() function.
Upon further inquiry found that process() can be executed exactly 32 times, then appears to
not be capable of reading the value set in the method of the class.
async appendBuffers({ data: { readable, processStream } }) {
processStream = new Function(`return ${processStream}`)();
let ready = false;
let next = [];
let overflow = [[], []];
let init = false;
globalThis.console.log({
currentTime,
currentFrame,
buffers: this.buffers,
});
const strategy = new ByteLengthQueuingStrategy({
highWaterMark: 32 * 1024,
});
const source = {
write: async value => {
// value (Uint8Array) length is not guaranteed to be multiple of 2 for Uint16Array
// store remainder of value in next array
if (value.length % 2 !== 0 && next.length === 0) {
next.push(...value.slice(value.length - 1));
value = value.slice(0, value.length - 1);
} else {
const prev = [...next.splice(0, next.length), ...value];
while (prev.length % 2 !== 0) {
next.push(...prev.splice(-1));
}
value = new Uint8Array(prev);
}
// we do not need length here, we process input until no more, or infinity
let data = new Uint16Array(value.buffer);
if (!init) {
init = true;
data = data.subarray(22);
console.log('init');
}
let { ch0, ch1 } = processStream(data);
// send 128 sample frames to process()
// to reduce, not entirely avoid, glitches
while (ch0.length && ch1.length) {
let __ch0, __ch1;
let _ch0 = ch0.splice(0, 128);
let _ch1 = ch1.splice(0, 128);
let [overflow0, overflow1] = overflow;
if (_ch0.length < 128 || _ch1.length < 128) {
overflow0.push(..._ch0);
overflow1.push(..._ch1);
break;
}
if (overflow0.length) {
__ch0 = overflow0.splice(0, overflow0.length);
__ch1 = overflow1.splice(0, overflow1.length);
while (__ch0.length < 128 && _ch0.length) {
let [float] = _ch0.splice(0, 1);
__ch0.push(float);
}
while (__ch1.length < 128 && _ch1.length) {
let [float] = _ch1.splice(0, 1);
__ch1.push(float);
}
}
const channel0 = new Float32Array(__ch0 || _ch0);
const channel1 = new Float32Array(__ch1 || _ch1);
this.buffers.set(this.i, {
channel0,
channel1,
});
// this is where the pause in execution of process() is observable
if (this.buffers.size < 64) {
console.log(this.buffers.size);
}
++this.i;
if (!ready) {
ready = true;
console.log(this.buffers.get(this.n));
this.port.postMessage({
start: true,
});
}
}
},
close: _ => {
console.log('writable close');
// handle overflow floats < 128 length
let [overflow0, overflow1] = overflow;
if (overflow0.length || overflow1.length) {
const channel0 = new Float32Array(overflow0.splice(0));
const channel1 = new Float32Array(overflow1.splice(0));
this.buffers.set(this.i, {
channel0,
channel1,
});
++this.i;
}
},
};
const writable = new WritableStream(source, strategy);
Object.assign(this, { readable, writable });
await readable.pipeTo(writable, {
preventCancel: true,
});
process
process(inputs, outputs) {
if (currentTime > 0.9 && this.buffers.size === 0) {
console.log(
this.i,
this.n,
this.buffers.size,
this.readable,
this.writable
);
this.endOfStream();
return false;
}
let channel0, channel1;
// this is where the pause/inability to read the value set is observable
// the pause always lasts exactly at least 32 executions of process
try {
if (this.n < 33) {
console.log(this.n, this.buffers.size);
}
// process() can be executed before appendBuffers()
// where this.buffers is set with values
// handle this.buffers.get(this.n) being undefined
// for up to 32 calls to process()
({ channel0, channel1 } = this.buffers.get(this.n));
// glitches can occur when sample frame size is less than 128
if (
(channel0 && channel0.length < 128) ||
(channel1 && channel1.length < 128)
) {
console.log(
channel0.length,
channel1.length,
currentTime,
currentFrame,
this.buffers.size
);
}
} catch (e) {
console.error(e, this.buffers.get(this.n), this.i, this.n);
// handle this.buffers.size === 0 while this.readable.locked (reading)
// where entry in this.buffers (Map) deleted and this.writable (writing)
// not having set new entry this.buffers,
// resulting in no data to set at currentTime
// example of JavaScript being single threaded?
return true;
}
const [[outputChannel0], [outputChannel1]] = outputs;
outputChannel0.set(channel0);
outputChannel1.set(channel1);
this.buffers.delete(this.n);
++this.n;
return true;
}
Above, 64 (x2) Float32Arrays are created having length set to 128 to avoid glitches due to the fact that if a Float32Array is set at outputs in process() where the length is less than 128 the remainder of the TypedArray remains filled with 0s.
That means there are at least 128 Float32Arrays set at this.buffers (Map), 64 for each channel.
Describe the issue
Am not sure if this is a specification issue or a Chromium bug (Chromium/Chrome is the only browser implementation am aware of at this time).
Initially ignored the issue that first observed when calling
port.postMessage()
to main thread whereresume()
is executed. Occasionally the processing would not commence, so refreshed the page.When a user-defined method is created in the extended
class
if values used inprocess()
were set in the method those values were not immediately available inprocess()
function.Upon further inquiry found that
process()
can be executed exactly 32 times, then appears to not be capable of reading the value set in the method of theclass
.Where Is It https://webaudio.github.io/web-audio-api/#AudioWorkletProcessor-methods
Additional Information
process
Logs
Above, 64 (x2)
Float32Array
s are created havinglength
set to128
to avoid glitches due to the fact that if aFloat32Array
is set atoutputs
inprocess()
where thelength
is less than128
the remainder of theTypedArray
remains filled with0
s.That means there are at least 128
Float32Array
s set atthis.buffers
(Map
), 64 for each channel.However, after 32 calls
this.buffers.size
is0
Another example of logs
This effectively means that when
process()
is being executed no other methods defined in extendedAudioWorkletProcess
are being executed.Is that by design?
Or, the result of JavaScript being "single threaded"?