WebAudio / web-audio-api-v2

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

k rate output from AudioWorkletNode #80

Closed charlieschwabacher closed 4 years ago

charlieschwabacher commented 4 years ago

A nice feature of this API is the ability to write custom nodes for modulation - things like parameterized envelopes or step sequencers w/ parameterized slew. This can be achieved now with a-rate outputs, but often k-rate would work just as well and it would be significantly more efficient.

This could be accomplished by adding outputRate alongside outputChannelCount to AudioWorkletNode options:

new AudioWorkletNode(context, 'ModulationProcessor', {
  numberOfInputs: 0,
  numberOfOutputs: 1,
  outputChannelCount: [1],
  outputRate: ['k-rate'],
  channelCount: 1,
  channelCountMode: 'explicit',
  channelInterpretation: 'discrete',
});

Connecting a k-rate output to an a-rate param or to the input of another AudioNode could cause the value to be upscaled by filling a 128 length buffer.

karlt commented 4 years ago

In the case of an AudioWorkletNode output connected to an AudioParam, the AudioParam can be set to k-rate so as to use only the first sample of the render quantum.

For the case of connecting an AudioWorkletNode output to an (indexed) input of an AudioNode, a ConstantSourceNode with node.offset.automationRate set to "k-rate" can be used to fill all 128 samples with the value of the first.

charlieschwabacher commented 4 years ago

Ahh - so w/ that approach, the only cost is the extra 508 bytes of memory and whatever it cpu it costs to zero out or reallocate the 128 sample buffer each frame? I'm coming at this from the javascript side and don't have the best intuition on how to weigh that cost. Do you have a clear idea of the relative costs of:

1) A worklet processor w/ k rate output filling a one sample output buffer 2) A worklet processor filling the first sample of a 128 sample buffer 3) A worklet processing filling all 128 samples of its buffer w/ the same value 4) Some other node (say an oscillator) filling its 128 sample buffer

If the difference between 1/2/3 is insignificant, I agree this proposal doesn't make sense. If the difference between 1 and 2 or 2 and 3 is significant, it does seem worth having first class support for the k rate output to me. Needing to stick a ConstantSourceNode in between connections feels like an API usability issue.

charlieschwabacher commented 4 years ago

I did some benchmarking, creating the following processors representing cases 2 and 3 above

registerProcessor(
  "AllSamplesProcessor",
  class AllSamplesProcessor extends AudioWorkletProcessor {
    process(_, [output]) {
      output.fill(1);
    }
  }
);
registerProcessor(
  "FirstSampleProcessor",
  class FirstSampleProcessor extends AudioWorkletProcessor {
    process(_, [output]) {
      output[0] = 1;
    }
  }
);

There doesn't seem to be a significant difference in performance between the two.

padenot commented 4 years ago

Virtual F2F: