1use std::fmt::Debug;
2use std::str::FromStr;
3
4use alsa::pcm::{self, Access, HwParams, IoFormat};
5use alsa::{Direction, ValueOr, PCM};
6use derive_more::Display;
7use forr::forr;
8use num::{Num, ToPrimitive};
9use serde::{Deserialize, Serialize};
10
11use crate::{Audio, F};
12
13#[derive(Clone, Copy, Display, Deserialize, Serialize, Debug, PartialEq, Eq)]
14#[serde(rename_all = "lowercase")]
15pub enum Format {
16 S8,
17 U8,
18 S16,
19 U16,
20 S32,
21 U32,
22 F32,
23 F64,
24}
25impl FromStr for Format {
26 type Err = String;
27
28 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
29 Ok(match s.to_ascii_lowercase().as_str() {
30 "s8" => Format::S8,
31 "u8" => Format::U8,
32 "s16" => Format::S16,
33 "u16" => Format::U16,
34 "s32" => Format::S32,
35 "u32" => Format::U32,
36 "f32" => Format::F32,
37 "f64" => Format::F64,
38 e => return Err(format!("Unsupported audio format {e:?}")),
39 })
40 }
41}
42
43impl From<Format> for pcm::Format {
44 fn from(value: Format) -> Self {
45 match value {
46 Format::S8 => Self::S8,
47 Format::U8 => Self::U8,
48 Format::S16 => Self::s16(),
49 Format::U16 => Self::u16(),
50 Format::S32 => Self::s32(),
51 Format::U32 => Self::u32(),
52 Format::F32 => Self::float(),
53 Format::F64 => Self::float64(),
54 }
55 }
56}
57
58impl PartialEq<str> for Format {
59 fn eq(&self, other: &str) -> bool {
60 matches!(
61 (other, self),
62 ("s8" | "S8", Format::S8)
63 | ("u8" | "U8", Format::U8)
64 | ("s16" | "S16", Format::S16)
65 | ("u16" | "U16", Format::U16)
66 | ("s32" | "S32", Format::S32)
67 | ("u32" | "U32", Format::U32)
68 | ("f32" | "F32", Format::F32)
69 | ("f64" | "F64", Format::F64)
70 )
71 }
72}
73
74impl Format {
75 pub fn supported<'a>(params: &'a HwParams) -> impl Iterator<Item = Format> + 'a {
77 [
78 Format::S8,
79 Format::U8,
80 Format::S16,
81 Format::U16,
82 Format::S32,
83 Format::U32,
84 Format::F32,
85 Format::F64,
86 ]
87 .into_iter()
88 .filter(|&format| params.test_format(format.into()).is_ok())
89 }
90}
91
92pub struct AudioRecorder<T> {
93 pcm: PCM,
94 channels: usize,
95 rate: u32,
96 buffer: Vec<T>,
97 }
99
100pub trait Normalize {
101 fn normalize(self) -> F;
102}
103
104impl Normalize for f32 {
105 fn normalize(self) -> F {
106 self.into()
107 }
108}
109
110impl Normalize for F {
111 fn normalize(self) -> F {
112 self
113 }
114}
115
116forr! { $signed:ty, $unsigned:ty in [i8, u8, i16, u16, i32, u32] $*
117 impl Normalize for $signed {
118 fn normalize(self) -> F {
119 self as F / (Self::MAX as F - 1.)
120 }
121 }
122 impl Normalize for $unsigned {
123 fn normalize(self) -> F {
124 (self as F - $signed::MAX as F) / ($signed::MAX as F - 1.)
125 }
126 }
127}
128
129impl<T: Copy + IoFormat + Num + ToPrimitive + Normalize> AudioRecorder<T> {
130 #[allow(clippy::missing_errors_doc)]
131 pub fn new(
132 name: impl AsRef<str>,
133 channels: usize,
134 rate: u32,
135 format: Format,
136 duration: f64,
137 ) -> Result<Self, alsa::Error> {
138 let pcm = PCM::new(name.as_ref(), Direction::Capture, false)?;
139 {
140 let hwp = HwParams::any(&pcm)?;
141 #[allow(clippy::cast_possible_truncation)]
142 hwp.set_channels(channels as u32)?;
143 hwp.set_rate(rate, ValueOr::Nearest)?;
144 hwp.set_format(format.into())?;
145 hwp.set_access(Access::RWInterleaved)?;
146 pcm.hw_params(&hwp)?;
147 }
148 pcm.start()?;
149 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
150 Ok(AudioRecorder {
151 pcm,
152 channels,
153 rate,
154 buffer: vec![T::zero(); (duration * f64::from(rate)) as usize * channels],
156 })
158 }
159
160 #[allow(clippy::missing_errors_doc)]
162 pub fn record(&mut self) -> Result<Audio, alsa::Error> {
163 _ = self.pcm.prepare();
164 let io = self.pcm.io_checked()?;
165
166 let len = io.readi(&mut self.buffer)? * self.channels;
168 Ok(Audio::from_interleaved(
169 self.rate.into(),
170 self.channels,
171 self.buffer[..len].iter().copied().map(Normalize::normalize),
172 ))
173 }
174}
175
176#[macro_export]
177macro_rules! for_format {
178 ($format:expr, $expr:expr) => {
179 match $format {
180 Format::S8 => {
181 type FORMAT = i8;
182 $expr
183 }
184 Format::U8 => {
185 type FORMAT = u8;
186 $expr
187 }
188 Format::S16 => {
189 type FORMAT = i16;
190 $expr
191 }
192 Format::U16 => {
193 type FORMAT = u16;
194 $expr
195 }
196 Format::S32 => {
197 type FORMAT = i32;
198 $expr
199 }
200 Format::U32 => {
201 type FORMAT = u32;
202 $expr
203 }
204 Format::F32 => {
205 type FORMAT = f32;
206 $expr
207 }
208 Format::F64 => {
209 type FORMAT = f64;
210 $expr
211 }
212 }
213 };
214}