A clip was cut from its sound's file, so a game decoded the whole file
for it and held it for as long as the page was open. A long recording
played through a few short clips cost the recording. Every clip now has
a copy of its cut as Opus, with Stream on or off, and a game decodes
that instead.
- StreamKeeper makes the copy of each clip whether its sound streams
or not. Stream only decides whether a sound gets a copy of its own.
- clipCopyTable says which cut decodes from which copy. The sound
library loads the copy for a clip unless the whole file is decoded
already. Then the clip is cut from that as before.
- projectFiles decodes the whole file ahead only where something may
play the sound itself: no clips, a clip without a fitting copy, or
the sound's id anywhere in an asset that can name one. That last
test walks the data, not the fields, so no place is missed.
- A copy that cannot be had falls back to the whole file. A library
that bakes keeps to the whole files, as the bake's fingerprint does.
Tests:
- Gate: CHECK EXIT 0, 280 files, 4319 tests (7 new: streams +4,
sound-library +3).
- e2e 34: ALL PASSED, 108 checks (2 new). With Stream off a fresh
library decodes the ten-second reversed clip's copy and not the
five-minute file. It is the cut made from the original to within
0.0091 a sample and 0.013 dB in any 20 ms. Once a node plays the
sound itself, the whole file is decoded ahead again.
- e2e 65: all passed with this change.
- Counter-checks red as they should be: the library ignoring copies,
the whole file always decoded ahead, clip copies only with Stream.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sound was decoded whole as soon as the project opened, in the editor
and in a released game: five minutes of stereo held some 115 MB for as
long as the page was open. A sound with Stream on now plays from a copy
as Opus in Ogg, fetched a piece at a time and decoded a few seconds
ahead by WebCodecs, so a game holds a few megabytes for it.
- The Stream switch is a row of the sound, off by default. The Sound
tab suggests it from a minute on and says how far the copy has got.
- StreamKeeper makes the copies in the browser, a moment after the
last change, one at a time: the sound's own and one per clip of its
cut, fades and Reverse baked in. A copy that is no longer wanted is
taken off and deleted once the project is saved without it.
- streamTable says which played names stream from which copy. A clip
without a fitting copy, a Sample and a Reverb impulse stay decoded.
A sound with Stream on is never decoded ahead.
- The page fetches by Range and takes the whole file from a server
that ignores it, as the CI's PHP server does. It keeps the bytes
and the first two seconds decoded, the rest goes four seconds ahead.
- A jump decodes 32 packets before the place first. Measured in
Chrome, what follows is then the same to the bit as decoding from
the start. With 4 packets it was 0.06 off.
- The stream-player on the FX worklet reads the samples at the rate
of the context along a Catmull-Rom curve. What comes late is silent
and skipped once it is there, so a clip keeps its beat.
- The opening stretch goes with the player as it is made. Sent by
message it came a few milliseconds late, and 557 samples of the
attack were lost.
- Pitch on a streamed sound is a shifter after the player, as on a
bus: the length stays and it is heard 42.7 ms late.
- Bakes of patterns and songs (#340) always stream now.
- The song timeline draws a streamed sound from its copy decoded at
8 kHz, keeping only the peaks.
Tests:
- Gate: CHECK EXIT 0, 280 files, 4312 tests (31 new: ogg +3,
stream-queue 6, streams 11, streaming 6, sound-library +5).
- e2e 34: ALL PASSED, 106 checks (7 new). Five minutes of mono,
4.8 MB as WAV, 3.6 MB as its copy: streamed the renderer grows by
5.8 MB, decoded by 69.6 MB. Offline at 48 kHz, start, loop seam and
a reversed clip are the copy decoded whole to the sample (0).
- Counter-checks red as they should be: no pre-roll, no catch-up of
late samples at the end, no opening stretch, a streamed sound
decoded ahead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>