nandi/jolt-nativepublic Fork 0
c2d912f670b7a2e5808970d17cecf51c6c87cfbf
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

beep.rs · 163 lines · 5.7 KBRust Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 19d ago1//! Plays a simple 440 Hz sine wave (beep) tone.
2//!
3//! This example demonstrates:
4//! - Selecting audio hosts (with optional JACK support on Linux)
5//! - Selecting devices by ID or using the default output device
6//! - Querying the default output configuration
7//! - Building and running an output stream with typed samples
8//! - Generating audio data in the stream callback
9//!
10//! Run with: `cargo run --example beep`
11//! With JACK (Linux): `cargo run --example beep --features jack -- --jack`
12//! With specific device: `cargo run --example beep -- --device "wasapi:device_id"`
13
14use clap::Parser;
15use cpal::{
16 traits::{DeviceTrait, HostTrait, StreamTrait},
17 FromSample, HostUnavailable, Sample, SizedSample, I24,
18};
19
20#[derive(Parser, Debug)]
21#[command(version, about = "CPAL beep example", long_about = None)]
22struct Opt {
23 /// The audio device to use
24 #[arg(short, long)]
25 device: Option<String>,
26
27 /// Use the JACK host. Requires `--features jack`.
28 #[arg(long, default_value_t = false)]
29 jack: bool,
30
31 /// Use the PulseAudio host. Requires `--features pulseaudio`.
32 #[arg(long, default_value_t = false)]
33 pulseaudio: bool,
34
35 /// Use the Pipewire host. Requires `--features pipewire`
36 #[arg(long, default_value_t = false)]
37 pipewire: bool,
38}
39
40fn main() -> anyhow::Result<()> {
41 let opt = Opt::parse();
42
43 // Jack/PulseAudio support must be enabled at compile time, and is
44 // only available on some platforms.
45 #[allow(unused_mut, unused_assignments)]
46 let mut jack_host_id = Err(HostUnavailable);
47 #[allow(unused_mut, unused_assignments)]
48 let mut pulseaudio_host_id = Err(HostUnavailable);
49 #[allow(unused_mut, unused_assignments)]
50 let mut pipewire_host_id = Err(HostUnavailable);
51 #[cfg(any(
52 target_os = "linux",
53 target_os = "dragonfly",
54 target_os = "freebsd",
55 target_os = "netbsd"
56 ))]
57 {
58 #[cfg(feature = "jack")]
59 {
60 jack_host_id = Ok(cpal::HostId::Jack);
61 }
62
63 #[cfg(feature = "pulseaudio")]
64 {
65 pulseaudio_host_id = Ok(cpal::HostId::PulseAudio);
66 }
67 #[cfg(feature = "pipewire")]
68 {
69 pipewire_host_id = Ok(cpal::HostId::PipeWire);
70 }
71 }
72
73 // Manually check for flags. Can be passed through cargo with -- e.g.
74 // cargo run --release --example beep --features jack -- --jack
75 let host = if opt.jack {
76 jack_host_id
77 .and_then(cpal::host_from_id)
78 .expect("make sure `--features jack` is specified, and the platform is supported")
79 } else if opt.pulseaudio {
80 pulseaudio_host_id
81 .and_then(cpal::host_from_id)
82 .expect("make sure `--features pulseaudio` is specified, and the platform is supported")
83 } else if opt.pipewire {
84 pipewire_host_id
85 .and_then(cpal::host_from_id)
86 .expect("make sure `--features pipewire` is specified, and the platform is supported")
87 } else {
88 cpal::default_host()
89 };
90
91 let device = if let Some(device) = opt.device {
92 let id = &device.parse().expect("failed to parse device id");
93 host.device_by_id(id)
94 } else {
95 host.default_output_device()
96 }
97 .expect("failed to find output device");
98 println!("Output device: {}", device.id()?);
99
100 let config = device.default_output_config().unwrap();
101 println!("Default output config: {config:?}");
102
103 match config.sample_format() {
104 cpal::SampleFormat::I8 => run::<i8>(&device, config.into()),
105 cpal::SampleFormat::I16 => run::<i16>(&device, config.into()),
106 cpal::SampleFormat::I24 => run::<I24>(&device, config.into()),
107 cpal::SampleFormat::I32 => run::<i32>(&device, config.into()),
108 // cpal::SampleFormat::I48 => run::<I48>(&device, config.into()),
109 cpal::SampleFormat::I64 => run::<i64>(&device, config.into()),
110 cpal::SampleFormat::U8 => run::<u8>(&device, config.into()),
111 cpal::SampleFormat::U16 => run::<u16>(&device, config.into()),
112 // cpal::SampleFormat::U24 => run::<U24>(&device, config.into()),
113 cpal::SampleFormat::U32 => run::<u32>(&device, config.into()),
114 // cpal::SampleFormat::U48 => run::<U48>(&device, config.into()),
115 cpal::SampleFormat::U64 => run::<u64>(&device, config.into()),
116 cpal::SampleFormat::F32 => run::<f32>(&device, config.into()),
117 cpal::SampleFormat::F64 => run::<f64>(&device, config.into()),
118 sample_format => panic!("Unsupported sample format '{sample_format}'"),
119 }
120}
121
122pub fn run<T>(device: &cpal::Device, config: cpal::StreamConfig) -> Result<(), anyhow::Error>
123where
124 T: SizedSample + FromSample<f32>,
125{
126 let sample_rate = config.sample_rate as f32;
127 let channels = config.channels as usize;
128
129 // Produce a sinusoid of maximum amplitude.
130 let mut sample_clock = 0f32;
131 let mut next_value = move || {
132 sample_clock = (sample_clock + 1.0) % sample_rate;
133 (sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
134 };
135
136 let err_fn = |err| eprintln!("an error occurred on stream: {err}");
137
138 let stream = device.build_output_stream(
139 config,
140 move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
141 write_data(data, channels, &mut next_value)
142 },
143 err_fn,
144 None,
145 )?;
146 stream.play()?;
147
148 std::thread::sleep(std::time::Duration::from_millis(1000));
149
150 Ok(())
151}
152
153fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32)
154where
155 T: Sample + FromSample<f32>,
156{
157 for frame in output.chunks_mut(channels) {
158 let value: T = T::from_sample(next_sample());
159 for sample in frame.iter_mut() {
160 *sample = value;
161 }
162 }
163}