1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use std::{
thread,
time::{Duration, Instant},
};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
const DURATION_SECS: u32 = 6;
fn main() {
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("no output device available");
let sample_rate = device.default_output_config().unwrap().sample_rate();
let config = cpal::StreamConfig {
channels: 2,
sample_rate,
buffer_size: cpal::BufferSize::Default,
};
// create our oddio handles for a `SpatialScene`. We could also use a `Mixer`,
// which doesn't spatialize signals.
let (mut scene_handle, mut scene) = oddio::SpatialScene::new();
// We send `scene` into this closure, where changes to `scene_handle` are reflected.
// `scene_handle` is how we add new sounds and modify the scene live.
let stream = device
.build_output_stream(
&config,
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let frames = oddio::frame_stereo(data);
oddio::run(&mut scene, sample_rate.0, frames);
},
move |err| {
eprintln!("{}", err);
},
)
.unwrap();
stream.play().unwrap();
// Let's make some audio.
// Here, we're manually constructing a sound, which might otherwise be e.g. decoded from an mp3.
// in `oddio`, a sound like this is called `Frames` (each frame consisting of one sample per channel).
let boop = oddio::Frames::from_iter(
sample_rate.0,
// Generate a simple sine wave
(0..sample_rate.0 * DURATION_SECS).map(|i| {
let t = i as f32 / sample_rate.0 as f32;
(t * 500.0 * 2.0 * core::f32::consts::PI).sin() * 80.0
}),
);
// We need to create a `FramesSignal`. This is the basic type we need to play a `Frames`.
// We can create the most basic `FramesSignal` like this:
let basic_signal: oddio::FramesSignal<_> = oddio::FramesSignal::from(boop);
// or we could start 5 seconds in like this:
// let basic_signal = oddio::FramesSignal::new(boop, 5.0);
// We can also add filters around our `FramesSignal` to make our sound more controllable.
// A common one is `Gain`, which lets us modulate the gain of the `Signal` (how loud it is)
let (mut gain_control, gain) = oddio::Gain::new(basic_signal);
// the speed at which we'll be moving around
const SPEED: f32 = 50.0;
// `play_buffered` is used because the dynamically adjustable `Gain` filter makes sample values
// non-deterministic. For immutable signals like a bare `FramesSignal`, the regular `play` is
// more efficient.
let mut spatial_control = scene_handle.play_buffered(
gain,
oddio::SpatialOptions {
position: [-SPEED, 10.0, 0.0].into(),
velocity: [SPEED, 0.0, 0.0].into(),
radius: 0.1,
},
1000.0,
sample_rate.0,
0.1,
);
let start = Instant::now();
loop {
thread::sleep(Duration::from_millis(50));
let dt = start.elapsed();
if dt >= Duration::from_secs(DURATION_SECS as u64) {
break;
}
// This has no noticable effect because it matches the initial velocity, but serves to
// demonstrate that `Spatial` can smooth over the inevitable small timing inconsistencies
// between the main thread and the audio thread without glitching.
spatial_control.set_motion(
[-SPEED + SPEED * dt.as_secs_f32(), 10.0, 0.0].into(),
[SPEED, 0.0, 0.0].into(),
false,
);
// We also could adjust the Gain here in the same way:
gain_control.set_gain(1.0);
}
}