Skip to content

Commit 10cf3e3

Browse files
committed
[add] audio examples and make audio features enabled by default.
1 parent c264127 commit 10cf3e3

5 files changed

Lines changed: 219 additions & 1 deletion

File tree

demos/audio/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ publish = false
88
lambda-rs = { path = "../../crates/lambda-rs" }
99

1010
[features]
11-
default = []
11+
default = ["audio"]
12+
audio = ["audio-output-device", "audio-sound-buffer"]
1213
audio-output-device = ["lambda-rs/audio-output-device"]
1314
audio-sound-buffer = ["lambda-rs/audio-sound-buffer"]
1415
audio-sound-buffer-wav = ["lambda-rs/audio-sound-buffer-wav"]

demos/audio/src/bin/.gitkeep

Whitespace-only changes.

demos/audio/src/bin/play_sound.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#![allow(clippy::needless_return)]
2+
//! Audio demo that plays the bundled "slash" OGG Vorbis fixture.
3+
//!
4+
//! This demo validates that `SoundBuffer` decoding and audio output playback
5+
//! can be composed together using only the `lambda-rs` API surface.
6+
7+
use std::{
8+
sync::{
9+
atomic::{
10+
AtomicUsize,
11+
Ordering,
12+
},
13+
Arc,
14+
},
15+
time::Duration,
16+
};
17+
18+
use lambda::audio::{
19+
AudioOutputDeviceBuilder,
20+
SoundBuffer,
21+
};
22+
23+
fn main() {
24+
const SLASH_VORBIS_STEREO_48000_OGG: &[u8] = include_bytes!(concat!(
25+
env!("CARGO_MANIFEST_DIR"),
26+
"/../../crates/lambda-rs-platform/assets/audio/slash_vorbis_stereo_48000.ogg"
27+
));
28+
29+
let buffer =
30+
SoundBuffer::from_ogg_bytes(SLASH_VORBIS_STEREO_48000_OGG).unwrap();
31+
32+
let cursor = Arc::new(AtomicUsize::new(0));
33+
let buffer = Arc::new(buffer);
34+
35+
let cursor_for_callback = cursor.clone();
36+
let buffer_for_callback = buffer.clone();
37+
38+
let _device = AudioOutputDeviceBuilder::new()
39+
.with_label("play-sound")
40+
.with_sample_rate(buffer.sample_rate())
41+
.with_channels(buffer.channels())
42+
.build_with_output_callback(move |writer, _info| {
43+
let writer_channels = writer.channels() as usize;
44+
let writer_frames = writer.frames();
45+
46+
writer.clear();
47+
48+
if writer_channels == 0 {
49+
return;
50+
}
51+
52+
let write_samples = writer_frames.saturating_mul(writer_channels);
53+
let start =
54+
cursor_for_callback.fetch_add(write_samples, Ordering::Relaxed);
55+
56+
let source_samples = buffer_for_callback.samples();
57+
58+
for frame in 0..writer_frames {
59+
for channel in 0..writer_channels {
60+
let sample_index = start
61+
.saturating_add(frame.saturating_mul(writer_channels))
62+
.saturating_add(channel);
63+
let value = source_samples.get(sample_index).copied().unwrap_or(0.0);
64+
writer.set_sample(frame, channel, value);
65+
}
66+
}
67+
68+
return;
69+
})
70+
.unwrap();
71+
72+
std::thread::sleep(Duration::from_secs_f32(buffer.duration_seconds() + 0.20));
73+
drop(_device);
74+
return;
75+
}

demos/audio/src/bin/sine_wave.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#![allow(clippy::needless_return)]
2+
//! Audio output demo that plays a short sine wave tone.
3+
//!
4+
//! This demo assumes `lambda-demos-audio` is built with its default features.
5+
6+
use std::{
7+
thread,
8+
time::Duration,
9+
};
10+
11+
use lambda::audio::{
12+
enumerate_output_devices,
13+
AudioOutputDeviceBuilder,
14+
};
15+
16+
fn main() {
17+
let devices = match enumerate_output_devices() {
18+
Ok(devices) => devices,
19+
Err(error) => {
20+
eprintln!("Failed to enumerate audio output devices: {error:?}");
21+
return;
22+
}
23+
};
24+
25+
println!("Available output devices:");
26+
for device in devices {
27+
let default_marker = if device.is_default { " (default)" } else { "" };
28+
println!(" - {}{default_marker}", device.name);
29+
}
30+
31+
let frequency_hz = 440.0f32;
32+
let amplitude = 0.10f32;
33+
let mut phase_radians = 0.0f32;
34+
35+
let device = AudioOutputDeviceBuilder::new()
36+
.with_label("sine-wave")
37+
.build_with_output_callback(move |writer, callback_info| {
38+
let phase_delta = std::f32::consts::TAU * frequency_hz
39+
/ (callback_info.sample_rate as f32);
40+
41+
for frame_index in 0..writer.frames() {
42+
let sample = (phase_radians.sin()) * amplitude;
43+
for channel_index in 0..(writer.channels() as usize) {
44+
writer.set_sample(frame_index, channel_index, sample);
45+
}
46+
47+
phase_radians += phase_delta;
48+
if phase_radians > std::f32::consts::TAU {
49+
phase_radians -= std::f32::consts::TAU;
50+
}
51+
}
52+
53+
return;
54+
});
55+
56+
let _device = match device {
57+
Ok(device) => device,
58+
Err(error) => {
59+
eprintln!("Failed to initialize audio output device: {error:?}");
60+
return;
61+
}
62+
};
63+
64+
println!("Playing 440 Hz sine wave for 2 seconds...");
65+
thread::sleep(Duration::from_secs(2));
66+
println!("Done.");
67+
return;
68+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#![allow(clippy::needless_return)]
2+
//! Sound buffer loading demo that decodes a WAV or OGG Vorbis file.
3+
//!
4+
//! This demo assumes `lambda-demos-audio` is built with its default features.
5+
6+
use std::path::{
7+
Path,
8+
PathBuf,
9+
};
10+
11+
use lambda::audio::{
12+
AudioError,
13+
SoundBuffer,
14+
};
15+
16+
fn main() {
17+
let path = match parse_path_argument() {
18+
Ok(path) => path,
19+
Err(message) => {
20+
eprintln!("{message}");
21+
std::process::exit(2);
22+
}
23+
};
24+
25+
let buffer = match load_sound_buffer(&path) {
26+
Ok(buffer) => buffer,
27+
Err(error) => {
28+
eprintln!("failed to load sound buffer: {error}");
29+
std::process::exit(1);
30+
}
31+
};
32+
33+
println!("path: {}", path.display());
34+
println!("sample_rate: {}", buffer.sample_rate());
35+
println!("channels: {}", buffer.channels());
36+
println!("duration_seconds: {:.3}", buffer.duration_seconds());
37+
return;
38+
}
39+
40+
fn parse_path_argument() -> Result<PathBuf, String> {
41+
let mut args = std::env::args_os();
42+
let program_name = args
43+
.next()
44+
.and_then(|value| value.into_string().ok())
45+
.unwrap_or_else(|| "sound_buffer".to_string());
46+
47+
let path = args.next().ok_or_else(|| {
48+
return format!("usage: {program_name} <path-to-wav-or-ogg>");
49+
})?;
50+
51+
return Ok(PathBuf::from(path));
52+
}
53+
54+
fn load_sound_buffer(path: &Path) -> Result<SoundBuffer, AudioError> {
55+
let extension = path
56+
.extension()
57+
.and_then(|value| value.to_str())
58+
.map(|value| value.to_ascii_lowercase())
59+
.unwrap_or_default();
60+
61+
match extension.as_str() {
62+
"wav" => {
63+
return SoundBuffer::from_wav_file(path);
64+
}
65+
"ogg" | "oga" => {
66+
return SoundBuffer::from_ogg_file(path);
67+
}
68+
_ => {
69+
return Err(AudioError::UnsupportedFormat {
70+
details: format!("unsupported file extension: {extension:?}"),
71+
});
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)