changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / rust/lib/audio/src/lib.rs

changeset 698: 96958d3eb5b0
parent: 920ded613f45
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 //! audio modules
2 use std::{
3  fs::File,
4  io::BufWriter,
5  sync::{Arc, Mutex},
6 };
7 
8 pub use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
9 pub use hound::{Sample, SampleFormat, WavReader, WavSpec};
10 
11 /// convert between `cpal::SampleFormat` and `hound::SampleFormat`
12 pub fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {
13  match format {
14  cpal::SampleFormat::U16 => hound::SampleFormat::Int,
15  cpal::SampleFormat::I16 => hound::SampleFormat::Int,
16  cpal::SampleFormat::F32 => hound::SampleFormat::Float,
17  }
18 }
19 
20 /// Return `hound::WavSpec` from `cpal::SupportedStreamconfig`
21 pub fn wav_spec_from_config(
22  config: &cpal::SupportedStreamConfig,
23 ) -> hound::WavSpec {
24  hound::WavSpec {
25  channels: config.channels() as _,
26  sample_rate: config.sample_rate().0 as _,
27  bits_per_sample: (config.sample_format().sample_size() * 8) as _,
28  sample_format: sample_format(config.sample_format()),
29  }
30 }
31 
32 /// A thread-safe handle to the WavWriter file
33 type WavWriterHandle = Arc<Mutex<Option<hound::WavWriter<BufWriter<File>>>>>;
34 
35 /// Write incoming data to an owned `WavWriterHandle`
36 pub fn write_input_data<T, U>(input: &[T], writer: &WavWriterHandle)
37 where
38  T: cpal::Sample,
39  U: cpal::Sample + hound::Sample,
40 {
41  if let Ok(mut guard) = writer.try_lock() {
42  if let Some(writer) = guard.as_mut() {
43  for &sample in input.iter() {
44  let sample: U = cpal::Sample::from(&sample);
45  writer.write_sample(sample).ok();
46  }
47  }
48  }
49 }
50 
51 /// Compute the RMS of either integers or float samples.
52 pub fn compute_rms<S, R>(reader: &mut WavReader<R>) -> f64
53 where
54  f64: From<S>,
55  S: hound::Sample,
56  R: std::io::Read,
57 {
58  let sqr_sum = reader.samples::<S>().fold(0.0, |sqr_sum, s| {
59  let sample = f64::from(s.unwrap());
60  sqr_sum + sample * sample
61  });
62  (sqr_sum / reader.len() as f64).sqrt()
63 }
64 
65 /// Run a max volume sinusoid through the default audio output device
66 pub fn run<T: cpal::Sample>(
67  device: &cpal::Device,
68  config: &cpal::StreamConfig,
69 ) -> Result<(), Box<dyn std::error::Error + 'static>> {
70  let sample_rate = config.sample_rate.0 as f32;
71  let channels = config.channels as usize;
72 
73  // Produce a sinusoid of maximum amplitude.
74  let mut sample_clock = 0f32;
75  let mut next_value = move || {
76  sample_clock = (sample_clock + 1.0) % sample_rate;
77  (sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin()
78  };
79 
80  let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
81 
82  let stream = device.build_output_stream(
83  config,
84  move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
85  write_data(data, channels, &mut next_value)
86  },
87  err_fn,
88  )?;
89  stream.play()?;
90 
91  std::thread::sleep(std::time::Duration::from_millis(1000));
92 
93  Ok(())
94 }
95 
96 /// write a stream of data
97 fn write_data<T>(
98  output: &mut [T],
99  channels: usize,
100  next_sample: &mut dyn FnMut() -> f32,
101 ) where
102  T: cpal::Sample,
103 {
104  for frame in output.chunks_mut(channels) {
105  let value: T = cpal::Sample::from::<f32>(&next_sample());
106  for sample in frame.iter_mut() {
107  *sample = value;
108  }
109  }
110 }
111 
112 #[cfg(test)]
113 #[test]
114 fn default_device() {
115  use cpal::traits::HostTrait;
116  let device = cpal::default_host().default_output_device();
117  assert!(&device.is_some());
118  let config = device.unwrap().default_output_config();
119  assert!(&config.is_ok());
120 }