nandi/jolt-nativepublic Fork 0
cfd3e3677bed92e80cfe447469a249cfdc0b1401
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.

custom.rs · 356 lines · 10.9 KBRust Blame HistoryRaw
Lift freeq's AV media plane out of sleek 90f8b89 nandi 19d ago1use std::sync::{
2 atomic::{AtomicBool, Ordering},
3 Arc,
4};
5
6use cpal::{
7 traits::{DeviceTrait, HostTrait, StreamTrait},
8 DeviceDescription, DeviceDescriptionBuilder,
9};
10use cpal::{FromSample, Sample};
11
12#[allow(dead_code)]
13#[derive(Clone)] // Clone, Send+Sync are required
14struct MyHost;
15
16#[derive(Clone)] // Clone, Send+Sync are required
17struct MyDevice;
18
19// Only Send+Sync is needed
20struct MyStream {
21 controls: Arc<StreamControls>,
22 // option is needed since joining a thread takes ownership,
23 // and we want to do that on drop (gives us &mut self, not self)
24 handle: Option<std::thread::JoinHandle<()>>,
25}
26
27struct StreamControls {
28 exit: AtomicBool,
29 pause: AtomicBool,
30}
31
32impl HostTrait for MyHost {
33 type Device = MyDevice;
34 type Devices = std::iter::Once<MyDevice>;
35
36 fn is_available() -> bool {
37 true
38 }
39
40 fn devices(&self) -> Result<Self::Devices, cpal::DevicesError> {
41 Ok(std::iter::once(MyDevice))
42 }
43
44 fn default_input_device(&self) -> Option<Self::Device> {
45 None
46 }
47
48 fn default_output_device(&self) -> Option<Self::Device> {
49 Some(MyDevice)
50 }
51}
52
53impl DeviceTrait for MyDevice {
54 type SupportedInputConfigs = std::iter::Empty<cpal::SupportedStreamConfigRange>;
55 type SupportedOutputConfigs = std::iter::Once<cpal::SupportedStreamConfigRange>;
56 type Stream = MyStream;
57
58 fn name(&self) -> Result<String, cpal::DeviceNameError> {
59 Ok(String::from("custom"))
60 }
61
62 fn description(&self) -> Result<DeviceDescription, cpal::DeviceNameError> {
63 Ok(DeviceDescriptionBuilder::new("Custom Device".to_string()).build())
64 }
65
66 fn id(&self) -> Result<cpal::DeviceId, cpal::DeviceIdError> {
67 Err(cpal::DeviceIdError::UnsupportedPlatform)
68 }
69
70 fn supported_input_configs(
71 &self,
72 ) -> Result<Self::SupportedInputConfigs, cpal::SupportedStreamConfigsError> {
73 Ok(std::iter::empty())
74 }
75
76 fn supported_output_configs(
77 &self,
78 ) -> Result<Self::SupportedOutputConfigs, cpal::SupportedStreamConfigsError> {
79 Ok(std::iter::once(cpal::SupportedStreamConfigRange::new(
80 2,
81 44100,
82 44100,
83 cpal::SupportedBufferSize::Unknown,
84 cpal::SampleFormat::F32,
85 )))
86 }
87
88 fn default_input_config(
89 &self,
90 ) -> Result<cpal::SupportedStreamConfig, cpal::DefaultStreamConfigError> {
91 Err(cpal::DefaultStreamConfigError::StreamTypeNotSupported)
92 }
93
94 fn default_output_config(
95 &self,
96 ) -> Result<cpal::SupportedStreamConfig, cpal::DefaultStreamConfigError> {
97 Ok(cpal::SupportedStreamConfig::new(
98 2,
99 44100,
100 cpal::SupportedBufferSize::Unknown,
101 cpal::SampleFormat::I16,
102 ))
103 }
104
105 fn build_input_stream_raw<D, E>(
106 &self,
107 _: cpal::StreamConfig,
108 _: cpal::SampleFormat,
109 _: D,
110 _: E,
111 _: Option<std::time::Duration>,
112 ) -> Result<Self::Stream, cpal::BuildStreamError>
113 where
114 D: FnMut(&cpal::Data, &cpal::InputCallbackInfo) + Send + 'static,
115 E: FnMut(cpal::StreamError) + Send + 'static,
116 {
117 Err(cpal::BuildStreamError::StreamConfigNotSupported)
118 }
119
120 // this is the meat of a custom device impl.
121 // you're expected to repeatedly call `data_callback` and provide it with a buffer of samples,
122 // as well as a stream timestamp.
123 // a proper impl would also check the stream config and sample format, as well as handle errors
124 fn build_output_stream_raw<D, E>(
125 &self,
126 _: cpal::StreamConfig,
127 _: cpal::SampleFormat,
128 mut data_callback: D,
129 _: E,
130 _: Option<std::time::Duration>,
131 ) -> Result<Self::Stream, cpal::BuildStreamError>
132 where
133 D: FnMut(&mut cpal::Data, &cpal::OutputCallbackInfo) + Send + 'static,
134 E: FnMut(cpal::StreamError) + Send + 'static,
135 {
136 let controls = Arc::new(StreamControls {
137 exit: AtomicBool::new(false),
138 pause: AtomicBool::new(true), // streams are expected to start out paused by default
139 });
140
141 let thread_controls = controls.clone();
142 let handle = std::thread::spawn(move || {
143 let start = std::time::Instant::now();
144 let mut buffer = [0.0_f32; 4096];
145 while !thread_controls.exit.load(Ordering::Relaxed) {
146 std::thread::sleep(std::time::Duration::from_secs_f32(
147 buffer.len() as f32 / 44100.0,
148 ));
149 // continue if paused
150 if thread_controls.pause.load(Ordering::Relaxed) {
151 continue;
152 }
153
154 // data is cpal's way of having a type erased buffer.
155 // you're expected to provide a raw pointer, the amount of samples, and the sample format of the buffer
156 let mut data = unsafe {
157 cpal::Data::from_parts(
158 buffer.as_mut_ptr().cast(),
159 buffer.len(),
160 cpal::SampleFormat::F32,
161 )
162 };
163
164 let duration = std::time::Instant::now().duration_since(start);
165 let secs = duration.as_nanos() / 1_000_000_000;
166 let subsec_nanos = duration.as_nanos() - secs * 1_000_000_000;
167 let stream_instant = cpal::StreamInstant::new(secs as _, subsec_nanos as _);
168 let timestamp = cpal::OutputStreamTimestamp {
169 callback: stream_instant,
170 playback: stream_instant,
171 };
172 data_callback(&mut data, &cpal::OutputCallbackInfo::new(timestamp));
173
174 let avg = buffer.iter().sum::<f32>() / buffer.len() as f32;
175 println!("avg: {avg}");
176 }
177 });
178
179 Ok(MyStream {
180 controls,
181 handle: Some(handle),
182 })
183 }
184}
185
186impl StreamTrait for MyStream {
187 fn play(&self) -> Result<(), cpal::PlayStreamError> {
188 self.controls.pause.store(false, Ordering::Relaxed);
189 Ok(())
190 }
191
192 fn pause(&self) -> Result<(), cpal::PauseStreamError> {
193 self.controls.pause.store(true, Ordering::Relaxed);
194 Ok(())
195 }
196}
197
198// streams are expected to stop when dropped
199impl Drop for MyStream {
200 fn drop(&mut self) {
201 self.controls.exit.store(true, Ordering::Relaxed);
202 let _ = self.handle.take().unwrap().join();
203 }
204}
205
206#[cfg(feature = "custom")]
207fn main() {
208 let custom_host = cpal::platform::CustomHost::from_host(MyHost);
209 // alternatively, use cpal::platform::CustomDevice and skip enumerating devices
210 let host = cpal::Host::from(custom_host); // this host can be passed to rodio or any other crate that uses cpal
211
212 let device = host.default_output_device().unwrap();
213 let config = device.default_output_config().unwrap();
214
215 let stream = make_stream(&device, config.into()).unwrap();
216 stream.play().unwrap();
217 std::thread::sleep(std::time::Duration::from_millis(4000));
218}
219
220#[cfg(not(feature = "custom"))]
221fn main() {
222 panic!("please run with -F custom to try this example")
223}
224
225// rest of this example is mostly based off of synth_tones.rs
226
227pub enum Waveform {
228 Sine,
229 Square,
230 Saw,
231 Triangle,
232}
233
234pub struct Oscillator {
235 pub sample_rate: f32,
236 pub waveform: Waveform,
237 pub current_sample_index: f32,
238 pub frequency_hz: f32,
239}
240
241impl Oscillator {
242 fn advance_sample(&mut self) {
243 self.current_sample_index = (self.current_sample_index + 1.0) % self.sample_rate;
244 }
245
246 fn set_waveform(&mut self, waveform: Waveform) {
247 self.waveform = waveform;
248 }
249
250 fn calculate_sine_output_from_freq(&self, freq: f32) -> f32 {
251 let two_pi = 2.0 * std::f32::consts::PI;
252 (self.current_sample_index * freq * two_pi / self.sample_rate).sin()
253 }
254
255 fn is_multiple_of_freq_above_nyquist(&self, multiple: f32) -> bool {
256 self.frequency_hz * multiple > self.sample_rate / 2.0
257 }
258
259 fn sine_wave(&mut self) -> f32 {
260 self.advance_sample();
261 self.calculate_sine_output_from_freq(self.frequency_hz)
262 }
263
264 fn generative_waveform(&mut self, harmonic_index_increment: i32, gain_exponent: f32) -> f32 {
265 self.advance_sample();
266 let mut output = 0.0;
267 let mut i = 1;
268 while !self.is_multiple_of_freq_above_nyquist(i as f32) {
269 let gain = 1.0 / (i as f32).powf(gain_exponent);
270 output += gain * self.calculate_sine_output_from_freq(self.frequency_hz * i as f32);
271 i += harmonic_index_increment;
272 }
273 output
274 }
275
276 fn square_wave(&mut self) -> f32 {
277 self.generative_waveform(2, 1.0)
278 }
279
280 fn saw_wave(&mut self) -> f32 {
281 self.generative_waveform(1, 1.0)
282 }
283
284 fn triangle_wave(&mut self) -> f32 {
285 self.generative_waveform(2, 2.0)
286 }
287
288 fn tick(&mut self) -> f32 {
289 match self.waveform {
290 Waveform::Sine => self.sine_wave(),
291 Waveform::Square => self.square_wave(),
292 Waveform::Saw => self.saw_wave(),
293 Waveform::Triangle => self.triangle_wave(),
294 }
295 }
296}
297
298pub fn make_stream(
299 device: &cpal::Device,
300 config: cpal::StreamConfig,
301) -> Result<cpal::Stream, anyhow::Error> {
302 let num_channels = config.channels as usize;
303 let mut oscillator = Oscillator {
304 waveform: Waveform::Sine,
305 sample_rate: config.sample_rate as f32,
306 current_sample_index: 0.0,
307 frequency_hz: 440.0,
308 };
309 let err_fn = |err| eprintln!("Error building output sound stream: {err}");
310
311 let time_at_start = std::time::Instant::now();
312 println!("Time at start: {time_at_start:?}");
313
314 let stream = device.build_output_stream(
315 config,
316 move |output: &mut [f32], _: &cpal::OutputCallbackInfo| {
317 // for 0-1s play sine, 1-2s play square, 2-3s play saw, 3-4s play triangle_wave
318 let time_since_start = std::time::Instant::now()
319 .duration_since(time_at_start)
320 .as_secs_f32();
321 if time_since_start < 1.0 {
322 oscillator.set_waveform(Waveform::Sine);
323 } else if time_since_start < 2.0 {
324 oscillator.set_waveform(Waveform::Triangle);
325 } else if time_since_start < 3.0 {
326 oscillator.set_waveform(Waveform::Square);
327 } else if time_since_start < 4.0 {
328 oscillator.set_waveform(Waveform::Saw);
329 } else {
330 oscillator.set_waveform(Waveform::Sine);
331 }
332 process_frame(output, &mut oscillator, num_channels)
333 },
334 err_fn,
335 None,
336 )?;
337
338 Ok(stream)
339}
340
341fn process_frame<SampleType>(
342 output: &mut [SampleType],
343 oscillator: &mut Oscillator,
344 num_channels: usize,
345) where
346 SampleType: Sample + FromSample<f32>,
347{
348 for frame in output.chunks_mut(num_channels) {
349 let value: SampleType = SampleType::from_sample(oscillator.tick());
350
351 // copy the same value to all channels
352 for sample in frame.iter_mut() {
353 *sample = value;
354 }
355 }
356}