Skip to content

Commit 536b007

Browse files
committed
[add] specification for wav and ogg decoding using symphonia.
1 parent 7cf8891 commit 536b007

1 file changed

Lines changed: 343 additions & 0 deletions

File tree

docs/specs/audio-file-loading.md

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
---
2+
title: "Audio File Loading (SoundBuffer)"
3+
document_id: "audio-file-loading-2026-01-31"
4+
status: "draft"
5+
created: "2026-01-31T22:07:49Z"
6+
last_updated: "2026-01-31T23:03:17Z"
7+
version: "0.2.0"
8+
engine_workspace_version: "2023.1.30"
9+
wgpu_version: "26.0.1"
10+
shader_backend_default: "naga"
11+
winit_version: "0.29.10"
12+
repo_commit: "7cf8891f861a625b989f3751fd61674d072a53fe"
13+
owners: ["lambda-sh"]
14+
reviewers: ["engine", "rendering"]
15+
tags: ["spec", "audio", "lambda-rs", "platform", "assets"]
16+
---
17+
18+
# Audio File Loading (SoundBuffer)
19+
20+
## Table of Contents
21+
22+
- [Summary](#summary)
23+
- [Scope](#scope)
24+
- [Terminology](#terminology)
25+
- [Architecture Overview](#architecture-overview)
26+
- [Design](#design)
27+
- [API Surface](#api-surface)
28+
- [lambda-rs Public API](#lambda-rs-public-api)
29+
- [Behavior](#behavior)
30+
- [Validation and Errors](#validation-and-errors)
31+
- [Cargo Features](#cargo-features)
32+
- [Constraints and Rules](#constraints-and-rules)
33+
- [Performance Considerations](#performance-considerations)
34+
- [Requirements Checklist](#requirements-checklist)
35+
- [Verification and Testing](#verification-and-testing)
36+
- [Compatibility and Migration](#compatibility-and-migration)
37+
- [Changelog](#changelog)
38+
39+
## Summary
40+
41+
- Add the ability to load audio files from disk or in-memory bytes into a
42+
decoded `SoundBuffer` suitable for future playback and mixing.
43+
- Implement application-facing APIs in `lambda-rs` while placing codec
44+
dependencies behind `lambda-rs-platform` wrappers to avoid leaking vendor
45+
types into the public API.
46+
- Support common formats starting with WAV and OGG Vorbis.
47+
48+
## Scope
49+
50+
### Goals
51+
52+
- Load WAV files into decoded audio buffers.
53+
- Load OGG Vorbis files into decoded audio buffers.
54+
- Provide a `SoundBuffer` type holding decoded audio data (`f32` samples).
55+
- Support loading from file path and from memory bytes.
56+
- Provide actionable, backend-agnostic error reporting for unsupported formats
57+
and decoding failures.
58+
59+
### Non-Goals
60+
61+
- MP3 support.
62+
- Streaming large files (incremental decode, disk-backed buffers).
63+
- Audio playback.
64+
65+
## Terminology
66+
67+
- SoundBuffer: a fully decoded, in-memory buffer of audio samples suitable for
68+
immediate use by a playback or mixing system.
69+
- Sample: a single channel value in nominal range `[-1.0, 1.0]`.
70+
- Frame: one sample per channel at a given time (for example, stereo has 2
71+
samples per frame).
72+
- Interleaved: sample order is per-frame with channels adjacent (for example,
73+
`L0, R0, L1, R1, ...`).
74+
- WAV: Waveform Audio File Format, typically PCM or IEEE float samples.
75+
- OGG Vorbis: Ogg container format carrying Vorbis-compressed audio.
76+
77+
## Architecture Overview
78+
79+
- Crate `lambda` (package: `lambda-rs`)
80+
- `audio` module provides the application-facing `SoundBuffer` API.
81+
- The public API MUST remain backend-agnostic and MUST NOT expose `symphonia`
82+
or `lambda-rs-platform` types.
83+
- Crate `lambda_platform` (package: `lambda-rs-platform`)
84+
- `audio::symphonia` module provides a WAV and OGG Vorbis decoding wrapper,
85+
backed by `symphonia` 0.5.5.
86+
- These modules are internal dependency wrappers and MAY change between
87+
releases.
88+
89+
Data flow
90+
91+
```
92+
application
93+
└── lambda::audio::SoundBuffer
94+
├── from_wav_file / from_wav_bytes
95+
│ └── lambda_platform::audio::symphonia (internal)
96+
└── from_ogg_file / from_ogg_bytes
97+
└── lambda_platform::audio::symphonia (internal)
98+
```
99+
100+
## Design
101+
102+
### API Surface
103+
104+
This section describes the platform layer surface used by `lambda-rs`
105+
implementations. Applications MUST NOT depend on `lambda-rs-platform` or use
106+
its decoding APIs directly.
107+
108+
Module layout (new)
109+
110+
- `crates/lambda-rs-platform/src/audio/symphonia/mod.rs`
111+
- Provides WAV and OGG Vorbis decode wrappers used by `lambda-rs`.
112+
- The wrapper MUST use `symphonia` 0.5.5 and MUST disable non-required
113+
decoders and demuxers via dependency feature configuration.
114+
115+
Platform data model
116+
117+
The platform layer MUST return decoded audio in a backend-agnostic shape that
118+
can be converted into `lambda::audio::SoundBuffer` without exposing codec
119+
types.
120+
121+
```rust
122+
// crates/lambda-rs-platform/src/audio_decode.rs (module name selected in
123+
// implementation)
124+
125+
#[derive(Clone, Debug, PartialEq)]
126+
pub struct DecodedAudio {
127+
pub samples: Vec<f32>,
128+
pub sample_rate: u32,
129+
pub channels: u16,
130+
}
131+
132+
#[derive(Clone, Debug)]
133+
pub enum AudioDecodeError {
134+
UnsupportedFormat { details: String },
135+
InvalidData { details: String },
136+
DecodeFailed { details: String },
137+
}
138+
```
139+
140+
Notes
141+
142+
- The implementation MAY avoid adding a shared `DecodedAudio` module and MAY
143+
instead implement format-specific decode functions returning an equivalent
144+
internal struct.
145+
- The platform error type MUST implement `Display` and MUST NOT include vendor
146+
error types in variants.
147+
148+
### lambda-rs Public API
149+
150+
The `SoundBuffer` API MUST be implemented in `lambda-rs`.
151+
152+
Module layout (new)
153+
154+
- `crates/lambda-rs/src/audio/buffer.rs` (new)
155+
- Defines `SoundBuffer` and its file/byte loading entry points.
156+
- `crates/lambda-rs/src/audio/mod.rs` (existing module; file layout MAY be
157+
converted from `audio.rs` to `audio/mod.rs` to host submodules).
158+
159+
Public API
160+
161+
```rust
162+
// crates/lambda-rs/src/audio/buffer.rs
163+
164+
pub struct SoundBuffer {
165+
samples: Vec<f32>,
166+
sample_rate: u32,
167+
channels: u16,
168+
}
169+
170+
impl SoundBuffer {
171+
pub fn from_wav_file(path: &std::path::Path) -> Result<Self, AudioError>;
172+
pub fn from_wav_bytes(bytes: &[u8]) -> Result<Self, AudioError>;
173+
pub fn from_ogg_file(path: &std::path::Path) -> Result<Self, AudioError>;
174+
pub fn from_ogg_bytes(bytes: &[u8]) -> Result<Self, AudioError>;
175+
176+
pub fn sample_rate(&self) -> u32;
177+
pub fn channels(&self) -> u16;
178+
pub fn duration_seconds(&self) -> f32;
179+
}
180+
```
181+
182+
### Behavior
183+
184+
- `SoundBuffer` samples MUST be interleaved `f32` samples in nominal range
185+
`[-1.0, 1.0]`.
186+
- `from_*_file` MUST read the entire file into memory and decode it.
187+
- Rationale: streaming is an explicit non-goal for this work item.
188+
- `from_*_bytes` MUST decode from the provided byte slice without attempting
189+
any filesystem access.
190+
- `duration_seconds` MUST be computed as:
191+
- `frames = samples.len() / channels`
192+
- `duration = frames as f32 / sample_rate as f32`
193+
- WAV decoding MUST support:
194+
- mono and stereo
195+
- 16-bit PCM, 24-bit PCM, and 32-bit float sample representations
196+
- OGG decoding MUST support:
197+
- OGG Vorbis in mono and stereo
198+
199+
### Validation and Errors
200+
201+
The public API MUST return actionable, backend-agnostic errors.
202+
203+
The existing `lambda::audio::AudioError` MUST be extended to represent decode
204+
and I/O errors produced by sound buffer loading.
205+
206+
Error behavior
207+
208+
- Unsupported formats MUST return an explicit error variant indicating that
209+
the input format is not supported (for example, a WAV with an unsupported
210+
bit depth or a non-Vorbis Ogg stream).
211+
- Invalid or corrupted input MUST return an explicit error variant indicating
212+
invalid data.
213+
- Filesystem read failures MUST return an explicit error variant indicating an
214+
I/O failure and SHOULD include the input path in the error details.
215+
- Errors MUST NOT panic.
216+
- Errors MUST NOT expose vendor types.
217+
218+
### Cargo Features
219+
220+
Audio behavior in this workspace is opt-in and controlled via Cargo features.
221+
This specification introduces new granular features that MUST be wired into
222+
existing umbrella features.
223+
224+
Crate `lambda-rs` (package: `lambda-rs`)
225+
226+
- New granular features (disabled by default)
227+
- `audio-sound-buffer-wav`: enables `SoundBuffer::from_wav_*`.
228+
- `audio-sound-buffer-vorbis`: enables `SoundBuffer::from_ogg_*`.
229+
- New umbrella feature (disabled by default)
230+
- `audio-sound-buffer`: composes `audio-sound-buffer-wav` and
231+
`audio-sound-buffer-vorbis`.
232+
- Existing umbrella feature (disabled by default)
233+
- `audio`: MUST compose `audio-output-device` and `audio-sound-buffer`.
234+
235+
Crate `lambda-rs-platform` (package: `lambda-rs-platform`)
236+
237+
- New granular features (disabled by default)
238+
- `audio-decode-wav`: enables WAV decode support via the `symphonia` wrapper.
239+
- `audio-decode-vorbis`: enables OGG Vorbis decode support via the `symphonia`
240+
wrapper.
241+
- Existing umbrella feature (disabled by default)
242+
- `audio`: MUST compose `audio-device`, `audio-decode-wav`, and
243+
`audio-decode-vorbis`.
244+
245+
Feature gating rules
246+
247+
- The `lambda::audio` module MUST be compiled when either `audio-output-device`
248+
or `audio-sound-buffer` is enabled.
249+
- Format-specific entry points SHOULD be gated behind the corresponding
250+
granular features and MUST return a deterministic error if called when the
251+
required feature is disabled (if the symbol remains available).
252+
- `docs/features.md` MUST be updated in the implementation change that adds
253+
these features.
254+
255+
## Constraints and Rules
256+
257+
- `SoundBuffer` MUST store decoded samples as `f32` to support future mixing
258+
and processing without requiring format-specific sample conversions.
259+
- `SoundBuffer` MUST store sample rate and channel count from the decoded
260+
source.
261+
- Loading functions MUST reject inputs with `channels == 0` or
262+
`sample_rate == 0` with a validation error.
263+
- Audio decode dependencies MUST only be referenced from `lambda-rs-platform`
264+
modules located under `crates/lambda-rs-platform/src/audio/symphonia/`.
265+
266+
## Performance Considerations
267+
268+
Recommendations
269+
270+
- Decode paths SHOULD decode directly into the destination `Vec<f32>` without
271+
additional intermediate allocations where feasible.
272+
- Rationale: file loading already requires full-buffer allocation; extra
273+
copies scale linearly with file size.
274+
- Loading functions SHOULD use `Vec::try_reserve` (or equivalent) to surface
275+
allocation errors as `AudioError` rather than panicking.
276+
- Rationale: buffer sizes depend on file contents and may exceed memory
277+
availability.
278+
279+
## Requirements Checklist
280+
281+
- Functionality
282+
- [ ] WAV decode implemented (16-bit PCM, 24-bit PCM, 32-bit float)
283+
- [ ] OGG Vorbis decode implemented
284+
- [ ] Load-from-file and load-from-bytes supported
285+
- API Surface
286+
- [ ] `SoundBuffer` public API implemented in `lambda-rs`
287+
- [ ] `lambda-rs` does not expose vendor/platform decode types
288+
- [ ] `lambda::audio` module is available when sound-buffer features enabled
289+
- Validation and Errors
290+
- [ ] Unsupported formats return actionable errors
291+
- [ ] Corrupt data returns actionable errors
292+
- [ ] File I/O errors return actionable errors
293+
- Documentation and Examples
294+
- [ ] `docs/features.md` updated with new features and defaults
295+
- [ ] Minimal example loads a sound file and prints metadata
296+
- Tests
297+
- [ ] Unit tests cover WAV mono and stereo
298+
- [ ] Unit tests cover OGG Vorbis mono and stereo
299+
- [ ] Test assets are stored under `crates/lambda-rs/assets/`
300+
301+
For each checked item, include a reference to a commit, pull request, or file
302+
path that demonstrates the implementation.
303+
304+
## Verification and Testing
305+
306+
### Unit Tests
307+
308+
Coverage targets
309+
310+
- WAV
311+
- mono 16-bit PCM
312+
- stereo 16-bit PCM
313+
- mono 24-bit PCM
314+
- stereo 32-bit float
315+
- OGG Vorbis
316+
- mono
317+
- stereo
318+
319+
Commands
320+
321+
- `cargo test -p lambda-rs -- --nocapture`
322+
- `cargo test --workspace`
323+
324+
### Example
325+
326+
- Add `crates/lambda-rs/examples/sound_buffer_load.rs`.
327+
- The example SHOULD load a file path provided via CLI args and print:
328+
- channels
329+
- sample rate
330+
- duration
331+
332+
## Compatibility and Migration
333+
334+
- Adding decoding variants to `lambda::audio::AudioError` is a source-breaking
335+
change for applications that match the enum exhaustively.
336+
- Migration: add a wildcard match arm or handle the new variants explicitly.
337+
- No other user-visible behavior changes are required.
338+
339+
## Changelog
340+
341+
- 2026-01-31 (v0.2.0) — Center decoding on `symphonia` 0.5.5.
342+
- 2026-01-31 (v0.1.1) — Align spec with platform audio module layout.
343+
- 2026-01-31 (v0.1.0) — Initial draft.

0 commit comments

Comments
 (0)