WebAudio / web-audio-api-v2

The Web Audio API v2.0, developed by the W3C Audio WG
Other
121 stars 11 forks source link

AudioWorkletProcessor::process() halts execution of other methods in the class for 32 calls #72

Closed guest271314 closed 4 years ago

guest271314 commented 4 years ago

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 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.

Where Is It https://webaudio.github.io/web-audio-api/#AudioWorkletProcessor-methods

Additional Information

  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;
  }

Logs

previewer.79829115fde743502576.js:14 0.18575963718820862 // baseLatency
2previewer.79829115fde743502576.js:14 suspended
previewer.79829115fde743502576.js:14 durationchange
previewer.79829115fde743502576.js:14 loadedmetadata
previewer.79829115fde743502576.js:14 play
previewer.79829115fde743502576.js:14 playing
audioWorklet.js:17 {currentTime: 0, currentFrame: 0, buffers: Map(0)}
audioWorklet.js:46 init
audioWorklet.js:80 1
audioWorklet.js:85 {channel0: Float32Array(128), channel1: Float32Array(128)}
audioWorklet.js:80 2
audioWorklet.js:80 3
audioWorklet.js:80 4
audioWorklet.js:80 5
audioWorklet.js:80 6
audioWorklet.js:80 7
audioWorklet.js:80 8
audioWorklet.js:80 9
audioWorklet.js:80 10
audioWorklet.js:80 11
audioWorklet.js:80 12
audioWorklet.js:80 13
audioWorklet.js:80 14
audioWorklet.js:80 15
audioWorklet.js:80 16
audioWorklet.js:80 17
audioWorklet.js:80 18
audioWorklet.js:80 19
audioWorklet.js:80 20
audioWorklet.js:80 21
audioWorklet.js:80 22
audioWorklet.js:80 23
audioWorklet.js:80 24
audioWorklet.js:80 25
audioWorklet.js:80 26
audioWorklet.js:80 27
audioWorklet.js:80 28
audioWorklet.js:80 29
audioWorklet.js:80 30
audioWorklet.js:80 31
audioWorklet.js:80 32
audioWorklet.js:80 33
audioWorklet.js:80 34
audioWorklet.js:80 35
audioWorklet.js:80 36
audioWorklet.js:80 37
audioWorklet.js:80 38
audioWorklet.js:80 39
audioWorklet.js:80 40
audioWorklet.js:80 41
audioWorklet.js:80 42
audioWorklet.js:80 43
audioWorklet.js:80 44
audioWorklet.js:80 45
audioWorklet.js:80 46
audioWorklet.js:80 47
audioWorklet.js:80 48
audioWorklet.js:80 49
audioWorklet.js:80 50
audioWorklet.js:80 51
audioWorklet.js:80 52
audioWorklet.js:80 53
audioWorklet.js:80 54
audioWorklet.js:80 55
audioWorklet.js:80 56
audioWorklet.js:80 57
audioWorklet.js:80 58
audioWorklet.js:80 59
audioWorklet.js:80 60
audioWorklet.js:80 61
audioWorklet.js:80 62
audioWorklet.js:80 63

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.

audioWorklet.js:133 0 "process first call" 63 // first call to process()
audioWorklet.js:149 0 63
audioWorklet.js:149 1 62
previewer.79829115fde743502576.js:14 running
audioWorklet.js:149 2 61
audioWorklet.js:149 3 60
audioWorklet.js:149 4 59
audioWorklet.js:149 5 58
audioWorklet.js:149 6 57
audioWorklet.js:149 7 56
audioWorklet.js:149 8 55
audioWorklet.js:149 9 54
audioWorklet.js:149 10 53
audioWorklet.js:149 11 52
audioWorklet.js:149 12 51
audioWorklet.js:149 13 50
audioWorklet.js:149 14 49
audioWorklet.js:149 15 48
audioWorklet.js:149 16 47
audioWorklet.js:149 17 46
audioWorklet.js:149 18 45
audioWorklet.js:149 19 44
audioWorklet.js:149 20 43
audioWorklet.js:149 21 42
audioWorklet.js:149 22 41
audioWorklet.js:149 23 40
audioWorklet.js:149 24 39
audioWorklet.js:149 25 38
audioWorklet.js:149 26 37
audioWorklet.js:149 27 36
audioWorklet.js:149 28 35
audioWorklet.js:149 29 34
audioWorklet.js:149 30 33
audioWorklet.js:149 31 32
audioWorklet.js:149 32 31

However, after 32 calls this.buffers.size is 0

audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 63 63
process @ audioWorklet.js:170
audioWorklet.js:80 1
audioWorklet.js:80 2
audioWorklet.js:80 3
audioWorklet.js:80 4
audioWorklet.js:80 5
audioWorklet.js:80 6
audioWorklet.js:80 7
audioWorklet.js:80 8
audioWorklet.js:80 9
audioWorklet.js:80 10
audioWorklet.js:80 11
audioWorklet.js:80 12
audioWorklet.js:80 13
audioWorklet.js:80 14
audioWorklet.js:80 15
audioWorklet.js:80 16
audioWorklet.js:80 17
audioWorklet.js:80 18
audioWorklet.js:80 19
audioWorklet.js:80 20
audioWorklet.js:80 21
audioWorklet.js:80 22
audioWorklet.js:80 23
audioWorklet.js:80 24
audioWorklet.js:80 25
audioWorklet.js:80 26
audioWorklet.js:80 27
audioWorklet.js:80 28
audioWorklet.js:80 29
audioWorklet.js:80 30
audioWorklet.js:80 31
audioWorklet.js:80 32
audioWorklet.js:80 33
audioWorklet.js:80 34
audioWorklet.js:80 35
audioWorklet.js:80 36
audioWorklet.js:80 37
audioWorklet.js:80 38
audioWorklet.js:80 39
audioWorklet.js:80 40
audioWorklet.js:80 41
audioWorklet.js:80 42
audioWorklet.js:80 43
audioWorklet.js:80 44
audioWorklet.js:80 45
audioWorklet.js:80 46
audioWorklet.js:80 47
audioWorklet.js:80 48
audioWorklet.js:80 49
audioWorklet.js:80 50
audioWorklet.js:80 51
audioWorklet.js:80 52
audioWorklet.js:80 53
audioWorklet.js:80 54
audioWorklet.js:80 55
audioWorklet.js:80 56
audioWorklet.js:80 57
audioWorklet.js:80 58
audioWorklet.js:80 59
audioWorklet.js:80 60
audioWorklet.js:80 61
audioWorklet.js:80 62
audioWorklet.js:80 63

Another example of logs

running // AudioContext state
audioWorklet.js:133 0 "process first call" 31 // first call to process()
audioWorklet.js:149 0 31
audioWorklet.js:149 1 30
audioWorklet.js:149 2 29
audioWorklet.js:149 3 28
audioWorklet.js:149 4 27
audioWorklet.js:149 5 26
audioWorklet.js:149 6 25
audioWorklet.js:149 7 24
audioWorklet.js:149 8 23
audioWorklet.js:149 9 22
audioWorklet.js:149 10 21
audioWorklet.js:149 11 20
audioWorklet.js:149 12 19
audioWorklet.js:149 13 18
audioWorklet.js:149 14 17
audioWorklet.js:149 15 16
audioWorklet.js:149 16 15
audioWorklet.js:149 17 14
audioWorklet.js:149 18 13
audioWorklet.js:149 19 12
audioWorklet.js:149 20 11
audioWorklet.js:149 21 10
audioWorklet.js:149 22 9
audioWorklet.js:149 23 8
audioWorklet.js:149 24 7
audioWorklet.js:149 25 6
audioWorklet.js:149 26 5
audioWorklet.js:149 27 4
audioWorklet.js:149 28 3
audioWorklet.js:149 29 2
audioWorklet.js:149 30 1
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:149 31 0
audioWorklet.js:170 TypeError: Cannot destructure property 'channel0' of 'this.buffers.get(...)' as it is undefined.
    at AudioWorkletProcessor.process (audioWorklet.js:155) 0 31 31
process @ audioWorklet.js:170
audioWorklet.js:80 1
audioWorklet.js:80 2
audioWorklet.js:80 3
audioWorklet.js:80 4
audioWorklet.js:80 5
audioWorklet.js:80 6
audioWorklet.js:80 7
audioWorklet.js:80 8
audioWorklet.js:80 9
audioWorklet.js:80 10
audioWorklet.js:80 11
audioWorklet.js:80 12
audioWorklet.js:80 13
audioWorklet.js:80 14
audioWorklet.js:80 15
audioWorklet.js:80 16
audioWorklet.js:80 17
audioWorklet.js:80 18
audioWorklet.js:80 19
audioWorklet.js:80 20
audioWorklet.js:80 21
audioWorklet.js:80 22
audioWorklet.js:80 23
audioWorklet.js:80 24
audioWorklet.js:80 25
audioWorklet.js:80 26
audioWorklet.js:80 27
audioWorklet.js:80 28
audioWorklet.js:80 29
audioWorklet.js:80 30
audioWorklet.js:80 31
audioWorklet.js:80 32
audioWorklet.js:80 33
audioWorklet.js:80 34
audioWorklet.js:80 35
audioWorklet.js:80 36
audioWorklet.js:80 37
audioWorklet.js:80 38
audioWorklet.js:80 39
audioWorklet.js:80 40
audioWorklet.js:80 41
audioWorklet.js:80 42
audioWorklet.js:80 43
audioWorklet.js:80 44
audioWorklet.js:80 45
audioWorklet.js:80 46
audioWorklet.js:80 47
audioWorklet.js:80 48
audioWorklet.js:80 49
audioWorklet.js:80 50
audioWorklet.js:80 51
audioWorklet.js:80 52
audioWorklet.js:80 53
audioWorklet.js:80 54
audioWorklet.js:80 55
audioWorklet.js:80 56
audioWorklet.js:80 57
audioWorklet.js:80 58
audioWorklet.js:80 59
audioWorklet.js:80 60
audioWorklet.js:80 61
audioWorklet.js:80 62
audioWorklet.js:80 63
audioWorklet.js:149 31 160
audioWorklet.js:149 32 159

This effectively means that when process() is being executed no other methods defined in extended AudioWorkletProcess are being executed.

Is that by design?

Or, the result of JavaScript being "single threaded"?