Skip to content

[Bug]: playback runs the audio graph at the output device's rate, costing up to 19x the DSP and 4x the RAM for no added quality #578

Description

@thcp

What happens

StemDeck creates its AudioContext with no sampleRate, so the graph runs at whatever the operating system's output device is set to. On a machine set to 24-bit/192 kHz, everything downstream runs at 192 kHz.

Nothing in StemDeck benefits from that. Every stem the pipeline produces is 44.1 kHz, 16-bit stereo, fixed at -ar 44100 in app/pipeline/runner.py. At a 192 kHz context the browser upsamples those 44.1 kHz stems on decode and then does several times the work on interpolated samples that carry no information the 44.1 kHz originals did not already have.

What it costs

The pitch chain's WSOLA search is O(overlap x seek) per sequence and both windows are set in milliseconds, so the cost per second of audio is quadratic in the context rate. One active pitch chain, offline render through the real soundtouch-processor.js, as a share of real time:

Context rate Cost
44.1 kHz 2.1%
48 kHz 2.4%
96 kHz 7.3%
192 kHz 28.2%

Memory scales linearly with the same number. Decoded AudioBuffers for a five minute, six stem track:

Context rate RAM
44.1 kHz 606 MB
48 kHz 659 MB
96 kHz 1.3 GB
192 kHz 2.6 GB

2.6 GB of buffers for a five minute track is enough to put a tab under memory pressure on its own, before any DSP runs.

Who this affects

Anyone who has deliberately set a high output rate, which is exactly the audiophile audience most likely to be running a DAC. #575 was reported by someone running 24-bit/192 kHz for Apple Music and Spotify Hi-Fi.

This is a separate problem from #576. That one was twelve pitch chains doing work on silence, which #577 fixes. This one is real work done at four times thenecessary resolution, and it remains after #577 lands: a single transposed laneat 192 kHz still costs 28% of the audio thread's budget where the same lane at44.1 kHz costs 2%.

Constraints for whoever fixes this

  • There are four AudioContext creation sites: static/js/audioEngine.js, static/js/chunkedAudioEngine.js, static/js/player.js (the footer waveform's decode-only context) and static/mobile/app.js (the shared, gesture-unlocked context the mobile UI passes in). A fix in one engine only is not a fix.
  • new AudioContext({ sampleRate }) is honoured in Chrome, Firefox and Safari, and the browser resamples to the device on output. I verified in Chromium that a requested 44100 and 48000 are both granted and that baseLatency does not change.
  • Whether the browser's output resampling to 192 kHz is clean on Windows WASAPI
    is not something I can test without the hardware. It has to be confirmed on a
    real 192 kHz device before this ships.
  • Lowering the rate for everybody is not obviously right. At 44.1 and 48 kHz the current behaviour is correct and costs nothing, and pinning would introduce a resample where there is none today. Capping only above 48 kHz leaves the common case untouched.
  • The context's rate is fixed when it is constructed, so the device rate has to be read before the real context is built, or the app has to be restarted after a device change. It already behaves that way today.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions