Skip to content

OfflineAudioContext Incremental Rendering#2675

Open
gabrielsanbrito wants to merge 6 commits into
WebAudio:mainfrom
gabrielsanbrito:offlineaudiocontext-progressive-rendering
Open

OfflineAudioContext Incremental Rendering#2675
gabrielsanbrito wants to merge 6 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


💥 Error: 422 Unprocessable Entity 💥

PR Preview failed to build. (Last tried on Jun 18, 2026, 11:47 PM UTC).

More

PR Preview relies on a number of web services to run. There seems to be an issue with the following one:

🚨 Spec Generator - Spec Generator is the web service used to build bikeshed/ReSpec specs

🔗 Related URL

Error output:

[
    {
        "lineNum": "4281:25",
        "messageType": "warning",
        "text": "At least one <img> doesn't have its size set (<img bs-line-number=\"4281:25\" alt=\"Graphical representation of calling cancelAndHoldAtTime when linearRampToValueAtTime has been called at this time.\" src=\"images/cancel-linear.svg\" style=\"height:75%;width:75%\">), but given the type of input document, Bikeshed can't figure out what the size should be.\nEither set 'width'/'height' manually, or opt out of auto-detection by setting the 'no-autosize' attribute."
    },
    {
        "lineNum": "2574:9",
        "messageType": "fatal",
        "text": "Can't find the 'contextOptions' argument of method 'OfflineAudioContext/constructor(numberOfChannels, length, sampleRate)' in the argumentdef block."
    },
    {
        "lineNum": "3780:9",
        "messageType": "fatal",
        "text": "Can't find the 'destinationNode' argument of method 'AudioNode/connect(destinationParam, output)' in the argumentdef block."
    },
    {
        "lineNum": "3780:9",
        "messageType": "fatal",
        "text": "Can't find the 'input' argument of method 'AudioNode/connect(destinationParam, output)' in the argumentdef block."
    },
    {
        "lineNum": "3881:9",
        "messageType": "fatal",
        "text": "Can't find the 'destinationNode' argument of method 'AudioNode/disconnect(destinationParam, output)' in the argumentdef block."
    },
    {
        "lineNum": "3894:9",
        "messageType": "fatal",
        "text": "Can't find the 'destinationNode' argument of method 'AudioNode/disconnect(destinationParam, output)' in the argumentdef block."
    },
    {
        "lineNum": "3909:9",
        "messageType": "fatal",
        "text": "Can't find the 'destinationNode' argument of method 'AudioNode/disconnect(destinationParam, output)' in the argumentdef block."
    },
    {
        "lineNum": "3909:9",
        "messageType": "fatal",
        "text": "Can't find the 'input' argument of method 'AudioNode/disconnect(destinationParam, output)' in the argumentdef block."
    },
    {
        "lineNum": "261:36",
        "messageType": "link",
        "text": "Multiple possible 'audio' element refs.\nArbitrarily chose https://html.spec.whatwg.org/multipage/media.html#audio\nTo auto-select one of the following refs, insert one of these lines into a <pre class=link-defaults> block:\nspec:html; type:element; text:audio\nspec:epub-34; type:element; text:audio"
    },
    {
        "lineNum": "309:39",
        "messageType": "link",
        "text": "Multiple possible 'audio' element refs.\nArbitrarily chose https://html.spec.whatwg.org/multipage/media.html#audio\nTo auto-select one of the following refs, insert one of these lines into a <pre class=link-defaults> block:\nspec:html; type:element; text:audio\nspec:epub-34; type:element; text:audio"
    },
    {
        "lineNum": "633:5",
        "messageType": "link",
        "text": "Multiple possible 'audio' element refs.\nArbitrarily chose https://html.spec.whatwg.org/multipage/media.html#audio\nTo auto-select one of the following refs, insert one of these lines into a <pre class=link-defaults> block:\nspec:html; type:element; text:audio\nspec:epub-34; type:element; text:audio"
    },
    {
        "lineNum": "1213:9",
        "messageType": "link",
        "text": "Multiple possible 'audio' element refs.\nArbitrarily chose https://html.spec.whatwg.org/multipage/media.html#audio\nTo auto-select one of the following refs, insert one of these lines into a <pre class=link-defaults> block:\nspec:html; type:element; text:audio\nspec:epub-34; type:element; text:audio"
    },
    {
        "lineNum": "2656:29",
        "messageType": "link",
        "text": "No 'idl' refs found for 'chunkSize'."
    },
    {
        "lineNum": "2663:25",
        "messageType": "link",
        "text": "No 'idl' refs found for 'chunkSize'."
    },
    {
        "lineNum": "2666:25",
        "messageType": "link",
        "text": "No 'idl' refs found for 'chunkSize'."
    },
    {
        "lineNum": "3002:15",
        "messageType": "link",
        "text": "Multiple possible 'audio' element refs.\nArbitrarily chose https://html.spec.whatwg.org/multipage/media.html#audio\nTo auto-select one of the following refs, insert one of these lines into a <pre class=link-defaults> block:\nspec:html; type:element; text:audio\nspec:epub-34; type:element; text:audio"
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 4901:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 4901:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 5315:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 5315:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 5973:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 5973:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 5993:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 5993:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 6455:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 6455:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 7032:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 7032:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 7148:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 7148:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 7265:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 7265:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 7360:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 7360:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 7635:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 7635:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 7813:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 7813:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 8274:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 8274:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 8386:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 8386:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "8532:51",
        "messageType": "link",
        "text": "Multiple possible 'audio' element refs.\nArbitrarily chose https://html.spec.whatwg.org/multipage/media.html#audio\nTo auto-select one of the following refs, insert one of these lines into a <pre class=link-defaults> block:\nspec:html; type:element; text:audio\nspec:epub-34; type:element; text:audio"
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 8668:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 8668:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 8958:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 8958:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 9217:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 9217:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 9993:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 9993:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 10062:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 10062:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 10175:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 10175:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "23:9 of file 'audionode.include' (included by a block on 10816:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-MODE]'."
    },
    {
        "lineNum": "27:9 of file 'audionode.include' (included by a block on 10816:1)",
        "messageType": "link",
        "text": "No 'idl' refs found for '[CC-INTERP]'."
    },
    {
        "lineNum": "13485:1",
        "messageType": "warning",
        "text": "W3C policy requires Privacy Considerations and Security Considerations to be separate sections, but you appear to have them combined into one."
    },
    {
        "lineNum": null,
        "messageType": "failure",
        "text": "Did not generate, due to errors exceeding the allowed error level."
    }
]

This seems to be an issue with the Spec Generator service. PR Preview doesn't manage this service and so has no control over it. If you've identified an issue with it, you can report the issue to the maintainers of Spec Generator directly. Please be courteous. Thank you!

If you don't have enough information above to solve the error by yourself or if the issue doesn't seem related to Spec Generator, you can file an issue with PR Preview.

@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
@@ -2500,9 +2480,10 @@ returned promise with the rendered result as an
interface OfflineAudioContext : BaseAudioContext {
constructor(OfflineAudioContextOptions contextOptions);
constructor(unsigned long numberOfChannels, unsigned long length, float sampleRate);

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.

If we make length nullable I think it would become length?

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.

Based on the discussion in the spec issue, was using length the final resolution?

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.

The discussion has stalled since my last comment. AFAIU, it seems that everyone agreed on using length, but there is some divergence on how it should be used. Should it be optional or nullable?

I'd rather have length be nullable instead of optional. This way callers need to be a little more intentional when creating indefinite-length OfflineAudioContexts. What do you think?

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
@@ -2500,9 +2480,10 @@ returned promise with the rendered result as an
interface OfflineAudioContext : BaseAudioContext {
constructor(OfflineAudioContextOptions contextOptions);
constructor(unsigned long numberOfChannels, unsigned long length, float sampleRate);

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.

Based on the discussion in the spec issue, was using length the final resolution?

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

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

3 participants