Skip to content

OfflineAudioContext Incremental Rendering - #2675

Merged
hoch merged 15 commits into
WebAudio:mainfrom
gabrielsanbrito:offlineaudiocontext-progressive-rendering
Aug 6, 2026
Merged

OfflineAudioContext Incremental Rendering#2675
hoch merged 15 commits into
WebAudio:mainfrom
gabrielsanbrito:offlineaudiocontext-progressive-rendering

Conversation

@gabrielsanbrito

@gabrielsanbrito gabrielsanbrito commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

This PR proposes changes to the Web Audio spec to enable rendering audio from OfflineAudioContexts in chunks.

Github issue: #2445
Feature explainer: https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/OfflineAudioContext/explainer.md


Preview | Diff

@gabrielsanbrito
gabrielsanbrito marked this pull request as ready for review June 5, 2026 23:42
@gabrielsanbrito

Copy link
Copy Markdown
Contributor Author

@hoch @padenot @mjwilson-google, this is the first draft for OfflineAudioContext incremental rendering. PTAL when you have some time.

While writing this, we had some open questions that might be better to discuss here:

  • In this draft I am proposing using Infinity to denote the undefined-length render scenario. However, OfflineAudioContextOptions.length is currently an unsigned long, which cannot be Infinity. What would be preferrable?
    • Swap all Infinity usage for null; or
    • Convert OfflineAudioContextOptions.length to an union type like (long | float) that only accept the Infinity value for float.
  • When OfflineAudioContextOptions.length is Infinity and chunksSize is not provided to startRendering(), chunkSize defaults to the render quantum size. But the render quantum size is very small. Maybe, should we consider a more optimal default?
  • Should OfflineAudioContextOptions.length be a multiple of chunkSize? I am assuming it doesn't need to.

(unfortunately, there is no preview available, but I think there might be something actually broken in the repo's CI pipeline)

@gabrielsanbrito

Copy link
Copy Markdown
Contributor Author

Audio WG meeting (06/11/2026) discussion:

  1. Using null is less controversial given that the length property is a long
  2. The render quantum size can be configured when the OfflineAudioContext is being constructed via the renderSizeHint parameter.
  3. It's not trivial to enforce since chunkSize can change with every call. At the end of the rendering, if the chunkSize is larger than the number of remaining samples, the OfflineAudioContext just returns a smaller AudioBuffer.

@mjwilson-google mjwilson-google left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some comments. Also, I think I fixed the Git workflow issue if you rebase the change on the latest commit.

Comment thread index.bs Outdated
Comment thread index.bs Outdated
Comment thread index.bs
Comment thread index.bs Outdated
<ol>
<li> If {{OfflineAudioContext/startRendering(chunkSize)/chunkSize}} is provided:
<ol>
<li> Let <var>renderedFrames</var> be the number of sample-frames already rendered by the {{OfflineAudioContext}}.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want renderedFrames to be an internal slot; then we can also specify when and how it is updated.

@mjwilson-google

Copy link
Copy Markdown
Contributor

It looks like the git checks are now passing but the PR preview still isn't working; sorry. I'm not sure why.

@hoch hoch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good, but I do have some suggestions. Please take a look at my comments.

Also it would be great if you can wrap the body text at column 80.

Comment thread index.bs Outdated
Comment thread index.bs
Comment thread index.bs Outdated
The total size of the audio render in sample-frames. This is the same as the
value of the <code>length</code> parameter for the constructor.

For undefined-length rendering, this attribute SHOULD be set to positive infinity.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO - since we have an explicit close() in this change, rendering indefinitely can be done by calling startRendering(chunkSize) repeatedly.

Probably this is aligned with what @karlt proposed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if understand this comment :/ Would you mind clarifying?

When I wrote this, I intended to have length be Infinity (after the last update it should be null) because an undefined render session has no defined length.

Do you mean that we shouldn't set length to null in the undefined length scenario and this should be tracked via an internal slot? For example, we need this info when determining the size of the returned AudioBuffer. The OfflineAudioContext needs to know whether we are in an undefined-length render or not:

<li> Otherwise, if {{OfflineAudioContext/length}} is
<code>null</code>, set <var>bufferLength</var> to the
<a>render quantum size</a>.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming that I was proposing that indefinite repeated rendering be available even when an unsigned long value for the length member is explicitly provided.

Even an optional member needs a (default) value, for which null seems a reasonable choice to correspond to "unspecified", but we need not necessarily require a specific or explicit value for indefinite repeated rendering.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quoting @karlt message from here:

I see the the default length of one rendering chunk (if not specified as a startRendering() parameter) as being consistent with the legacy length member.
IIUC @gabrielsanbrito sees the limit on the sum of the lengths of rendering chunks as being more consistent.
Perhaps this choice is less important if the length member can be optional.

I would prefer that startRendering(chunkSize) have control over how much is rendered, as I would see a limit from a limiting OfflineAudioContextOptions member as action at a distance.

I want to point out that, if, moving forward, the only way to close an OfflineAudioContext is via calling close(), this is potentially breaking. Currently, after the render is finished, the OfflineAudioContext goes to the "closed" state and fires a "statechange" event - i.e. after rendering length sample-frames. This means that pages that expect this state change may not work properly since we won't automatically close the OfflineAudioContext. This would be prevented by my proposal.

On a side note: I couldn't find this behavior in the spec (moving OfflineAudioContext to the "closed" state after the rendering is done), but this is what I noticed happening in both Chromium and Gecko.

Other concern is that, by allowing contexts to be solely manually closed, web pages could end up leaking resources - especially older ones unaware of close(). I recognize that this could be a minor concern since most WebAudio usage on the web is done through libraries and pages don't depend directly on WebAudio.

I am fine with adopting @karlt suggestions, although I think my concerns are valid. If we think that the breaking risk is small and that the performance implications are not that impactful, I feel like this is basically a matter of editorial preference. Both proposals should work for incremental and one-shot rendering scenarios. @hoch @padenot what is your opinion?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thank you for pointing that out. I had looked only at the spec and hadn't noticed that browsers were transitioning to "closed".
Even webkit is doing similarly.
Treating non-null length as a total limit allows maintaining that behavior.
I understand the consensus is to stay with the length as limit approach.

Comment thread index.bs Outdated
<li> If {{OfflineAudioContext/startRendering(chunkSize)/chunkSize}} is provided:
<ol>
<li> Let <var>renderedFrames</var> be the number of sample-frames already rendered by the {{OfflineAudioContext}}.
<li> If <var>renderedFrames</var> + {{OfflineAudioContext/startRendering(chunkSize)/chunkSize}} <= {{OfflineAudioContext/length}}, set <var>bufferLength</var> to {{OfflineAudioContext/startRendering(chunkSize)/chunkSize}}.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Let's explain this logic in descriptive prose rather than using abbreviated math.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Does it look good now? I was not sure if it was needed to make changes to the addition/subtraction.

Comment thread index.bs Outdated

@hoch hoch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to have input from @karlt and @padenot on this.

Comment thread index.bs
Comment thread index.bs Outdated

@karlt karlt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for putting this together and for the opportunity to comment.

Comment thread index.bs Outdated
The total size of the audio render in sample-frames. This is the same as the
value of the <code>length</code> parameter for the constructor.

For undefined-length rendering, this attribute SHOULD be set to positive infinity.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming that I was proposing that indefinite repeated rendering be available even when an unsigned long value for the length member is explicitly provided.

Even an optional member needs a (default) value, for which null seems a reasonable choice to correspond to "unspecified", but we need not necessarily require a specific or explicit value for indefinite repeated rendering.

Comment thread index.bs

@padenot padenot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@hoch hoch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this contribution, @gabrielsanbrito!

LGTM

@hoch
hoch merged commit bfc7143 into WebAudio:main Aug 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants