1 extern crate anyhow;
2 extern crate clap;
3 extern crate cpal;
4
5 use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
6
7 #[derive(Debug)]
8 struct Opt {
9 #[cfg(all(
10 any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
11 feature = "jack"
12 ))]
13 jack: bool,
14
15 device: String,
16 }
17
18 impl Opt {
from_args() -> Self19 fn from_args() -> Self {
20 let app = clap::App::new("beep").arg_from_usage("[DEVICE] 'The audio device to use'");
21 #[cfg(all(
22 any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
23 feature = "jack"
24 ))]
25 let app = app.arg_from_usage("-j, --jack 'Use the JACK host");
26 let matches = app.get_matches();
27 let device = matches.value_of("DEVICE").unwrap_or("default").to_string();
28
29 #[cfg(all(
30 any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
31 feature = "jack"
32 ))]
33 return Opt {
34 jack: matches.is_present("jack"),
35 device,
36 };
37
38 #[cfg(any(
39 not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
40 not(feature = "jack")
41 ))]
42 Opt { device }
43 }
44 }
45
main() -> anyhow::Result<()>46 fn main() -> anyhow::Result<()> {
47 let opt = Opt::from_args();
48
49 // Conditionally compile with jack if the feature is specified.
50 #[cfg(all(
51 any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
52 feature = "jack"
53 ))]
54 // Manually check for flags. Can be passed through cargo with -- e.g.
55 // cargo run --release --example beep --features jack -- --jack
56 let host = if opt.jack {
57 cpal::host_from_id(cpal::available_hosts()
58 .into_iter()
59 .find(|id| *id == cpal::HostId::Jack)
60 .expect(
61 "make sure --features jack is specified. only works on OSes where jack is available",
62 )).expect("jack host unavailable")
63 } else {
64 cpal::default_host()
65 };
66
67 #[cfg(any(
68 not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
69 not(feature = "jack")
70 ))]
71 let host = cpal::default_host();
72
73 let device = if opt.device == "default" {
74 host.default_output_device()
75 } else {
76 host.output_devices()?
77 .find(|x| x.name().map(|y| y == opt.device).unwrap_or(false))
78 }
79 .expect("failed to find output device");
80 println!("Output device: {}", device.name()?);
81
82 let config = device.default_output_config().unwrap();
83 println!("Default output config: {:?}", config);
84
85 match config.sample_format() {
86 cpal::SampleFormat::F32 => run::<f32>(&device, &config.into()),
87 cpal::SampleFormat::I16 => run::<i16>(&device, &config.into()),
88 cpal::SampleFormat::U16 => run::<u16>(&device, &config.into()),
89 }
90 }
91
run<T>(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error> where T: cpal::Sample,92 pub fn run<T>(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error>
93 where
94 T: cpal::Sample,
95 {
96 let sample_rate = config.sample_rate.0 as f32;
97 let channels = config.channels as usize;
98
99 // Produce a sinusoid of maximum amplitude.
100 let mut sample_clock = 0f32;
101 let mut next_value = move || {
102 sample_clock = (sample_clock + 1.0) % sample_rate;
103 (sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
104 };
105
106 let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
107
108 let stream = device.build_output_stream(
109 config,
110 move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
111 write_data(data, channels, &mut next_value)
112 },
113 err_fn,
114 )?;
115 stream.play()?;
116
117 std::thread::sleep(std::time::Duration::from_millis(1000));
118
119 Ok(())
120 }
121
write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32) where T: cpal::Sample,122 fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
123 where
124 T: cpal::Sample,
125 {
126 for frame in output.chunks_mut(channels) {
127 let value: T = cpal::Sample::from::<f32>(&next_sample());
128 for sample in frame.iter_mut() {
129 *sample = value;
130 }
131 }
132 }
133