Skip to content

Compress the export: WebCodecs in the browser, muxing here - #48

Merged
Gamah merged 1 commit into
masterfrom
seed/compressed-export
Aug 13, 2026
Merged

Compress the export: WebCodecs in the browser, muxing here#48
Gamah merged 1 commit into
masterfrom
seed/compressed-export

Conversation

@Gamah

@Gamah Gamah commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Closes PLAN row 99.

songToWav re-rendered the song and wrapped it raw, so one save was ~13 MB of 16-bit stereo. The web export now encodes in the browser.

What it does

  • web/encode.js (new, DOM-free): probes AudioEncoder.isConfigSupported once per realm and picks FLAC if the browser encodes it, else Opus at 256 kb/s (application: audio, signal: music, complexity 10). Neither → the engine's WAV, so the button always does something. The probe also labels the button (⬇ .flac / ⬇ .opus / ⬇ .wav), rather than promising an extension the browser may not produce.
  • WebCodecs returns encoded chunks and no container, so the muxing is ours: FLAC is description (fLaC + STREAMINFO) followed by the frames; Opus gets a real page-by-page Ogg stream — OpusHead/OpusTags, lacing, granule positions at 48 kHz starting at the pre-skip, and Ogg's non-reflected CRC. The resolved seed rides along as a TITLE tag.
  • The engine renders at 44.1 kHz and an encoder may refuse that, so there is a 48 kHz fallback through OfflineAudioContext rather than a hand-rolled resampler.
  • Export now reuses the PCM already in the cache when the song is one the timeline is holding, instead of re-rendering it.

Web-only by choice: Engine/ is untouched, s&box keeps writing WAV through Wav.cs, and no wasm re-stage is involved (tools/bundle-stamp.sh check is clean).

Drive-by fix

The playlist's per-row seek and export passed row.n — the song's index in its station, which under shuffle is 0 for every row. Both take row.position now.

Testing

  • test/encode.mjs (new, in make test) demuxes the Ogg stream back out page by page against the framing spec: CRC checked against an independent bit-at-a-time implementation, headers, lacing (including the 255-multiple 0 terminator), the 255-segment page limit, packets byte-for-byte, granule monotonicity and pre-skip. FLAC's missing/bogus header is an error rather than a guess.
  • test/{queue,palette,player,element}.mjs and test/{smoke,page}.mjs pass; the single-file bundler was rebuilt and test/dist-single.mjs passes.
  • Not tested here: any actual encode. WebCodecs is the browser's and there is no node build of it, and no engine or browser toolchain on this host — the FLAC path in particular is untestable until a browser ships FLAC encoding. Needs an ear and a real browser.

Spec citations in web/encode.js are dated 2026-08-13 (WebCodecs + its FLAC/Opus registrations, RFC 7845, Ogg framing).

The web export was 13 MB of raw PCM. It now encodes in the browser through
WebCodecs — FLAC where a browser offers it, Opus at 256 kb/s otherwise, and the
engine's WAV only where neither is available. WebCodecs hands back chunks and no
container, so the FLAC concatenation and the Ogg Opus muxer are here, with a node
test that demuxes the stream back out against the framing spec.

Web-only by choice: Engine/ is untouched and s&box still writes WAV.

Also: the playlist's per-row seek and export addressed a song by its index in its
own station, which is 0 for every row of a shuffled line. They take the timeline
position now.
@Gamah
Gamah merged commit 65a5bd7 into master Aug 13, 2026
1 check passed
@Gamah
Gamah deleted the seed/compressed-export branch August 13, 2026 03:01
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.

1 participant