-
Notifications
You must be signed in to change notification settings - Fork 496
Expand file tree
/
Copy pathbeep.rs
More file actions
163 lines (144 loc) · 5.65 KB
/
beep.rs
File metadata and controls
163 lines (144 loc) · 5.65 KB
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Plays a simple 440 Hz sine wave (beep) tone.
//!
//! This example demonstrates:
//! - Selecting audio hosts (with optional JACK support on Linux)
//! - Selecting devices by ID or using the default output device
//! - Querying the default output configuration
//! - Building and running an output stream with typed samples
//! - Generating audio data in the stream callback
//!
//! Run with: `cargo run --example beep`
//! With JACK (Linux): `cargo run --example beep --features jack -- --jack`
//! With specific device: `cargo run --example beep -- --device "wasapi:device_id"`
use clap::Parser;
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
FromSample, HostUnavailable, Sample, SizedSample, I24,
};
#[derive(Parser, Debug)]
#[command(version, about = "CPAL beep example", long_about = None)]
struct Opt {
/// The audio device to use
#[arg(short, long)]
device: Option<String>,
/// Use the JACK host. Requires `--features jack`.
#[arg(long, default_value_t = false)]
jack: bool,
/// Use the PulseAudio host. Requires `--features pulseaudio`.
#[arg(long, default_value_t = false)]
pulseaudio: bool,
/// Use the Pipewire host. Requires `--features pipewire`
#[arg(long, default_value_t = false)]
pipewire: bool,
}
fn main() -> anyhow::Result<()> {
let opt = Opt::parse();
// Jack/PulseAudio support must be enabled at compile time, and is
// only available on some platforms.
#[allow(unused_mut, unused_assignments)]
let mut jack_host_id = Err(HostUnavailable);
#[allow(unused_mut, unused_assignments)]
let mut pulseaudio_host_id = Err(HostUnavailable);
#[allow(unused_mut, unused_assignments)]
let mut pipewire_host_id = Err(HostUnavailable);
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd"
))]
{
#[cfg(feature = "jack")]
{
jack_host_id = Ok(cpal::HostId::Jack);
}
#[cfg(feature = "pulseaudio")]
{
pulseaudio_host_id = Ok(cpal::HostId::PulseAudio);
}
#[cfg(feature = "pipewire")]
{
pipewire_host_id = Ok(cpal::HostId::PipeWire);
}
}
// Manually check for flags. Can be passed through cargo with -- e.g.
// cargo run --release --example beep --features jack -- --jack
let host = if opt.jack {
jack_host_id
.and_then(cpal::host_from_id)
.expect("make sure `--features jack` is specified, and the platform is supported")
} else if opt.pulseaudio {
pulseaudio_host_id
.and_then(cpal::host_from_id)
.expect("make sure `--features pulseaudio` is specified, and the platform is supported")
} else if opt.pipewire {
pipewire_host_id
.and_then(cpal::host_from_id)
.expect("make sure `--features pipewire` is specified, and the platform is supported")
} else {
cpal::default_host()
};
let device = if let Some(device) = opt.device {
let id = &device.parse().expect("failed to parse device id");
host.device_by_id(id)
} else {
host.default_output_device()
}
.expect("failed to find output device");
println!("Output device: {}", device.id()?);
let config = device.default_output_config().unwrap();
println!("Default output config: {config:?}");
match config.sample_format() {
cpal::SampleFormat::I8 => run::<i8>(&device, config.into()),
cpal::SampleFormat::I16 => run::<i16>(&device, config.into()),
cpal::SampleFormat::I24 => run::<I24>(&device, config.into()),
cpal::SampleFormat::I32 => run::<i32>(&device, config.into()),
// cpal::SampleFormat::I48 => run::<I48>(&device, config.into()),
cpal::SampleFormat::I64 => run::<i64>(&device, config.into()),
cpal::SampleFormat::U8 => run::<u8>(&device, config.into()),
cpal::SampleFormat::U16 => run::<u16>(&device, config.into()),
// cpal::SampleFormat::U24 => run::<U24>(&device, config.into()),
cpal::SampleFormat::U32 => run::<u32>(&device, config.into()),
// cpal::SampleFormat::U48 => run::<U48>(&device, config.into()),
cpal::SampleFormat::U64 => run::<u64>(&device, config.into()),
cpal::SampleFormat::F32 => run::<f32>(&device, config.into()),
cpal::SampleFormat::F64 => run::<f64>(&device, config.into()),
sample_format => panic!("Unsupported sample format '{sample_format}'"),
}
}
pub fn run<T>(device: &cpal::Device, config: cpal::StreamConfig) -> Result<(), anyhow::Error>
where
T: SizedSample + FromSample<f32>,
{
let sample_rate = config.sample_rate as f32;
let channels = config.channels as usize;
// Produce a sinusoid of maximum amplitude.
let mut sample_clock = 0f32;
let mut next_value = move || {
sample_clock = (sample_clock + 1.0) % sample_rate;
(sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
};
let err_fn = |err| eprintln!("an error occurred on stream: {err}");
let stream = device.build_output_stream(
config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
write_data(data, channels, &mut next_value)
},
err_fn,
None,
)?;
stream.play()?;
std::thread::sleep(std::time::Duration::from_millis(1000));
Ok(())
}
fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
where
T: Sample + FromSample<f32>,
{
for frame in output.chunks_mut(channels) {
let value: T = T::from_sample(next_sample());
for sample in frame.iter_mut() {
*sample = value;
}
}
}