-
Notifications
You must be signed in to change notification settings - Fork 3.4k
[AUDIO_WORKLET] Added API for getting the buffer's quantum size #22681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
a275a20
to
0e9958e
Compare
I recall that Web Audio has gained a feature to allow a custom render quantum, though I actually never got around to tending to this:
so I wonder instead of promoting a define (which will be hardcoded in size), we should look into how to expand the api to read the actual quantum in a configurable way? I haven't looked into the details yet, on how to pull this off in a manner that doesn't break existing users (or I wonder if we might even care about that). |
0e9958e
to
e96adc5
Compare
I did read through most of the proposals but I think I zoned out after a while due to the multi-year nature of it. But you're right, the safest would be to have an API call to get the value. The details are in the draft: https://webaudio.github.io/web-audio-api/#BaseAudioContext I can test for renderQuantumSize, and if it exists great, otherwise return the default. |
c80eb2a
to
9bab4e5
Compare
@juj The context's Whilst making changes to the
Becomes:
Then we never need to keep track of these manually with more magic numbers. And also same with the field offsets (playing nice with wasm64. All of these get resolved to constants. |
ff8620c
to
bc67d74
Compare
66dfe2f
to
274cc56
Compare
src/audio_worklet.js
Outdated
@@ -38,60 +38,69 @@ function createWasmAudioWorkletProcessor(audioParams) { | |||
} | |||
|
|||
process(inputList, outputList, parameters) { | |||
// hardcoding this until the Web Audio 1.1 API spec makes it into browsers and we can get the AudioWorkletProcessor's context quantum | |||
const quantumSize = 128; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading the spec at https://webaudio.github.io/web-audio-api/ , I think we can (and should) already implement this, as it will be need to be implemented in a backwards compatible manner anyways.
I.e. something like const quantumSize = context.renderQuantumSize || 128;
The reason I say that we should implement this already now, because the data flow needs tending to, in order to get the renderQuantumSize
posted into the worklet. So it would be good to implement it already pretending that the renderQuamtumSize
parameter might exist in an implementation. Reading the spec, it seems fairly complete to assume that the quantum size will exist at some point.
Also, I wonder if we should expand the context creation function to allow taking in a quantum size?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I.e. something like
const quantumSize = context.renderQuantumSize || 128;
Originally I did, but I chose not to in the end because it's [SecureContext]
in the spec, and when it finally makes it to a browser I didn't want an exception. I thought of wrapping in a try/catch, but not knowing at all what the behaviour would be, I opted for the safer option.
Also, I wonder if we should expand the context creation function to allow taking in a quantum size?
I also considered this, in preparation, but since I didn't know how it would be treated in reality I chose the safer option.
The rationale behind both decisions were: if we expose setting it, we need to be able to read it otherwise things will break, and if we read it things might also break. We're itching to see the larger buffer sizes at work, so it's definitely something I or @tklajnscek will revisit as soon as.
I see there are still pointer e.g. assignments to HEAPU32 that won't actually work in Wasm64 mode, which will need HEAPU64 assignments. But that is good work for a separate PR, to keep the wasm64 part orthogonal. |
I'm getting an impression this might not happen any time soon, though maybe we should still implement the renderQuantumSize request/read flow in a way that is complete/forward-looking for the implementation that should appear. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good. Personally I would squeeze some code size bytes out of the JS code, though I think it's fine like this.
One quirk is that the quantumSize is not part of the AudioSampleFrame, i.e. there cannot be separate quantum sizes for separate sample inputs and outputs, it's always a constant.
But the only way to address that would probably be to change the signature of the audio process callback, which would be a breaking change, so this looks like a nice middle ground.
I'd like to see the setting and reading the renderQuantumSize flow to exist, just to make sure we can actually get the renderQuantumSize information into the worklet cleanly. (we probably need to postMessage it at audio worklet creation time?) But works either way.
I may just shorten some variable names for now. Ultimately, time permitting (which is wishful thinking), I want to look at what it would take to get the worklet code through Closure, the same as sbc100 did recently with general worklets (IIRC).
This also irks me, but as you mention I didn't want to make a breaking change to the callback signature. It might be worth breaking though, since the API didn't work with Closure until #22472 a few months back, I wonder how much production pain this would cause for others? I could mark
I'd pass it as one of the processor opts, along with the callback, etc. We already know it at that point, so in the creation code I could read it from |
@juj The quantum size has a single getter now, used internally where needed: emscripten/src/library_webaudio.js Line 41 in 8e1115a
The value is also passed in the options to the emscripten/src/library_webaudio.js Line 299 in 8e1115a
If this value is expected to change, e.g. the worklet node gets reattached to another context (I don't think this possible) then we could simple read it in |
(I see the filesize checks are failing in other PRs, it's not just me...) |
Filesize tests rebaselined. Please rebase or merge |
8e1115a
to
b9ce59c
Compare
You should not need to use short variable names in the source code. We can rely on closure/minifiers to shorten the names. I think @juj was likely referring to other tricks when he said "I would squeeze some code size bytes out of the JS code" |
The worklet doesn’t get minified currently and I add a few extra lines in there to use the struct sizes/offsets instead of magic numbers. |
I'm not sure what you mean. The code running the worklet is the exact same code that runs on the main thread right? i.e. we only generate one single program from the JS library files, right? If you build with |
I’ll need to recheck tomorrow but I’m pretty sure the property and variable names I added are staying the same (e.g. The library file certainly gets minified (and I’m not adding much that could be written more compact, AFAIK). It’s the worklet JS file I didn’t think was running through Closure? |
Ah yes, but that is just the bootstrap code the audio worklet, that main program code (all the code that lives in library_xxx.js) goes in |
There are changes to both the bootstrap and library, the bootstrap is mostly the likes of |
Ah, sorry I didn't see that. Still I don't think you want to be doing minification of variable names or other things that closure generatelly does in this code. A better path forward there would be to work on getting closure to run on that code |
Unless there's anything hideous I believe this PR is done, then I plan on looking at getting it all through Closure and other tweaks. I need to first move code away from our own Audio Worklet implementation (#12502 which we've been maintaining and shipping since 2020!) beforehand though. |
Any feedback on this? I've more PRs to follow but I'm building each on the last since they're touching the same files. I'm happy to mark the current API as deprecated and create a parallel API with the quantum, or go with this current PR and come back to this later. I'm itching to get to my next task of replacing this in the worklet:
Which I think is doable because the address never changes (given the worklet's stack is passed in externally) so we can create a view on the channel data valid for the life of the worklet, and thus can perform a garbage-free copy. |
A single JS function exists for the quantum size, the value is also passed to the processor node on creation.
e6ad164
to
5249dbb
Compare
// It comes two caveats: it needs the hint when generating the context adding to | ||
// emscripten_create_audio_context(), and altering the quantum requires a secure | ||
// context and fallback implementing. Until then we simply use the 1.0 API value: | ||
return 128; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't this code already today read
return EmAudio[contextHandle]['renderQuantumSize'] || 128;
That way the code would also be verifying that contextHandle
is a valid context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could, but I was worried about its [SecureContext]
marking in the spec and what that entails, and also since we can't yet set it then it doesn't make a difference.
But I'll happily change it and look at it again if/when it breaks, and when the API allows for the setting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, works either way.
[SecureContext] just means that the parameter won't be populated if the page is served via an insecure http:// handler. The value won't then be present, and will return undefined
.
(http://localhost URLs get a special case and is always considered secure).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern was it'd throw an exception, not just silently return undefined.
(For localhost it still needs Cross-Origin-Opener-Policy
and Cross-Origin-Embedder-Policy
headers for audio demos to run, BTW, since SharedArrayBuffer
isn't available and throws a reference error)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern was it'd throw an exception, not just silently return undefined.
Ah, right. An exception won't happen, that is guaranteed by the IDL spec of [SecureContext]: https://www.w3.org/TR/secure-contexts/#integration-idl which states multiple times
But let's leave this later for when an implementation comes along.
assert(EmAudio[contextHandle] instanceof (window.AudioContext || window.webkitAudioContext), `Called emscripten_audio_context_quantum_size() on handle ${contextHandle} that is not an AudioContext, but of type ${EmAudio[contextHandle]}`); | ||
#endif | ||
return emscriptenGetContextQuantumSize(contextHandle); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would delete the second function $emscriptenGetContextQuantumSize
and just have one function emscripten_audio_context_quantum_size()
and use that - it would be simpler that way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One was to expose it to JS for use in the worklet or in other code, the other native. Originally I didn't pass the value to the worklet, instead reading it from this JS function, but you'd said to demonstrate the value being passed.
I can re-look at this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$emscriptenGetContextQuantumSize()
gets called in two places from JS (one to pass to now pass to the worklet, the other to expose to native). I guess I could call emscripten_audio_context_quantum_size()
from emscripten_create_wasm_audio_worklet_node()
since both are JS in the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both versions of the function receive the handle ID? So in that case, I think it would be fine to call _emscripten_audio_context_quantum_size()
from JS code as well. (need to add the underscore at the call site though, since the function won't start with a $
prefix)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both versions of the function receive the handle ID?
I can tidy this in another PR.
{ | ||
float s = emscripten_math_sin(phase); | ||
phase += phaseIncrement; | ||
for(int ch = 0; ch < outputs[o].numberOfChannels; ++ch) | ||
outputs[o].data[ch*128 + i] = s * currentVolume; | ||
outputs[o].data[ch*outputs[o].quantumSize + i] = s * currentVolume; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the quantum size is a constant, I wonder if it would be better to have all these examples use that global quantumSize
integer, e.g.
int quantumSize;
bool ProcessAudio(int numInputs, ...) {
for(int o = 0; o < numOutputs; ++o)
for(int i = 0; i < quantumSize; ++i)
...
}
int main() {
quantumSize = emscripten_audio_context_quantum_size(context);
}
This would communicate the invariant that the quantumSize cannot change between inputs and outputs in a single call to ProcessAudio(). That might help someone learning about AudioWorklets.
Alternatively, I wonder if the field AudioSampleFrame::quantumSize
might be simpler to rename to AudioSampleFrame::length
to signal that it just means the length of the data array. Then users could have
for(int i = 0; i < outputs[o].length; ++i) {
outputs[o].data[i] = ...;
with very idiomatic reading C code. The "quantumSize" name in this context reads fancier than it necessarily needs to be (and doesn't match the Web Audio spec which calls it "renderQuantumSize").
Works either way, leaving it up to you to decide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like setting it as a global, and I agree, I also don't like the name quantumSize
either (or renderQuantumSize
or the use of quantum in general for this, or the way audio frame is used, etc.).
If I'm going to spend more time with this I'd rather that be spent marking the existing API as deprecated and creating a new callback signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, that makes sense. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made #22741 since it was easy to do (for me) now this has landed, that calls it samplesPerChannel
and bytesPerChannel
, which I think is better wording. I only left in emscripten_audio_context_quantum_size()
because that's what the API (almost) calls it which affects only advanced API usage.
Yeh, maybe breaking the signature of the audio callback won't be worth it, as the information can be acquired already in two different ways.
Ohh, this looks like an oversight. It definitely will be good to be Closured when --closure is passed. Though that's definitely something for another PR.
Yeah, we don't need to manually minify any variable names (when Closure can do the minification for us). In cases where Closure cannot minify variable names (like the key names for postMessage boundaries), then we can do some custom tweaking (like we do with the |
Thanks for working on this! |
It's actually fun, so it's not really work! |
The Web Audio API defines the processed sample size as always being
128
, which is hardcoded in both the test code and docs. The upcoming Web Audio API has the option to set this to a user defined setting or request the machine's preference, so in preparation the Audio Worklet is extended with a function to query the context's quantum at creation time (and before the worklet is created), and also the processing callback contains a field with the same value.For the simplest uses transitioning to the processing callback's field value will mean future changes will simply work.
Once the 1.1 version of the Web Audio API is supported, the context creation can be amended to accept a quantum hint, and any code written again these PR's changes will still work.