Skip to content

tecs.audio: load a clip from the bytes of an encoded file in memory - #9

Merged
mtdowling merged 1 commit into
tecs-dev:mainfrom
albanmgicquel:audio-load-bytes
Oct 15, 2026
Merged

mtdowling merged 1 commit into
tecs-dev:mainfrom
albanmgicquel:audio-load-bytes

Conversation

@albanmgicquel

Copy link
Copy Markdown
Collaborator

Audio:loadBytes(name, bytes, options?) decodes a clip from a whole encoded file a game already holds in memory, through the same symphonia probe a file uses, so every format load reads from disk also reads from memory. A game that unpacks its own archives passes each file through as it found it; a game holding raw PCM writes a 44-byte WAV header itself.

  • Identity: name is the clip's identity on the same terms as a path, in the same namespace (clipId/clipPath): a second load returns the held clip, a failure is cached, and Sound rows and snapshots carry the name.
  • Residency: memory clips are always resident and keep no copy of the bytes, so stream = true raises and reload returns false with a reason. Clip.memory tells the two kinds apart.
  • Backend: the contract gains an optional loadClipBytes, implemented by the silent backend and by tecs-audio (decode_bytes, Engine::load_clip_bytes, tecs_audio_load_clip_bytes, which takes a pointer and a length because an encoded file is full of zeroes).

Tests: decoding from memory equals decoding the same file sample for sample; a name without an extension decodes by content; a hand-built 8-bit unsigned mono WAV decodes to the expected values; odd-length data chunks with the RIFF pad byte at 10989 and 18868 Hz decode exactly and resample to 48 kHz frame for frame; non-audio and empty bytes give errors naming the input; the Nupp identity, duplicate, reload, cached-failure, missing-backend-field, silent-backend and snapshot rules. cargo test -p tecs-audio --all-targets, clippy -D warnings, cargo fmt --check, nupp check --strict, nupp task format-check and nupp test pass. Used in a game for its sound effects (8-bit samples cut from an archive, wrapped in WAV headers in memory).

Not run here: nupp task docs-check (this toolchain lacks the lunamark rock). Independent of the synth PR; rebased onto it, it applies without conflicts.

Audio:loadBytes(name, bytes, options?) decodes a clip from a whole
encoded file a game already holds, container and all, through the same
symphonia probe a file goes through, so every format load reads from
disk reads from memory too. A game holding raw samples writes the
container itself; for PCM that is a 44-byte WAV header.

The name is the clip's identity on the same terms as a path is for
load, in the same namespace: loading a name again returns the clip
already held without reading the new bytes, a failure is cached the
same way, and a Sound row and a snapshot carry the name. A memory clip
is always resident and keeps no copy of the bytes, so options.stream =
true raises and reload reports false with a reason. Clip.memory tells
the two kinds apart in clips() and clip(id).

The backend contract gains an optional loadClipBytes, which the silent
backend and the native binding implement; a backend without it makes
loadBytes return nil and a reason. tecs-audio adds decode::decode_bytes,
Engine::load_clip_bytes and tecs_audio_load_clip_bytes, which takes a
pointer and a length because an encoded file is full of zeroes.
@mtdowling
mtdowling merged commit bcb3a21 into tecs-dev:main Oct 15, 2026
7 of 9 checks 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.

3 participants