1use serde::{Deserialize, Serialize};
18
19#[non_exhaustive]
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24pub enum SampleFormat {
25 S16,
27 S24,
29 S32,
31 F32,
33 U8,
35}
36
37impl SampleFormat {
38 pub const fn bytes_per_sample(self) -> usize {
40 match self {
41 Self::U8 => 1,
42 Self::S16 => 2,
43 Self::S24 => 3,
44 Self::S32 | Self::F32 => 4,
45 }
46 }
47
48 pub const fn bits_per_sample(self) -> u32 {
50 #[allow(
53 clippy::as_conversions,
54 clippy::arithmetic_side_effects,
55 reason = "bytes_per_sample() <= 4 (usize); usize → u32 cast is safe here; product <= 32, fits in u32"
56 )]
57 {
58 self.bytes_per_sample() as u32 * 8
59 }
60 }
61
62 pub const fn is_float(self) -> bool {
64 matches!(self, Self::F32)
65 }
66
67 pub const fn is_integer(self) -> bool {
69 !self.is_float()
70 }
71
72 pub const fn name(self) -> &'static str {
74 match self {
75 Self::U8 => "U8",
76 Self::S16 => "S16",
77 Self::S24 => "S24",
78 Self::S32 => "S32",
79 Self::F32 => "F32",
80 }
81 }
82}
83
84impl std::fmt::Display for SampleFormat {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(f, "{}", self.name())
87 }
88}
89
90#[non_exhaustive]
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
95pub enum SampleRate {
96 Hz8000,
98 Hz11025,
100 Hz16000,
102 Hz22050,
104 Hz44100,
106 Hz48000,
108 Hz88200,
110 Hz96000,
112 Hz176400,
114 Hz192000,
116 Custom(u32),
118}
119
120impl SampleRate {
121 pub const fn hz(self) -> u32 {
123 match self {
124 Self::Hz8000 => 8000,
125 Self::Hz11025 => 11025,
126 Self::Hz16000 => 16000,
127 Self::Hz22050 => 22050,
128 Self::Hz44100 => 44100,
129 Self::Hz48000 => 48000,
130 Self::Hz88200 => 88200,
131 Self::Hz96000 => 96000,
132 Self::Hz176400 => 176_400,
133 Self::Hz192000 => 192_000,
134 Self::Custom(hz) => hz,
135 }
136 }
137
138 pub const fn from_hz(hz: u32) -> Self {
140 match hz {
141 8000 => Self::Hz8000,
142 11025 => Self::Hz11025,
143 16000 => Self::Hz16000,
144 22050 => Self::Hz22050,
145 44100 => Self::Hz44100,
146 48000 => Self::Hz48000,
147 88200 => Self::Hz88200,
148 96000 => Self::Hz96000,
149 176_400 => Self::Hz176400,
150 192_000 => Self::Hz192000,
151 other => Self::Custom(other),
152 }
153 }
154
155 pub const fn is_standard(self) -> bool {
157 !matches!(self, Self::Custom(_))
158 }
159
160 pub fn period_us(self) -> f64 {
162 1_000_000.0 / f64::from(self.hz())
163 }
164}
165
166impl std::fmt::Display for SampleRate {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 let hz = self.hz();
169 if hz >= 1000 {
170 write!(f, "{}.{}kHz", hz / 1000, (hz % 1000) / 100)
171 } else {
172 write!(f, "{hz}Hz")
173 }
174 }
175}
176
177#[non_exhaustive]
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
182pub enum ChannelLayout {
183 Mono,
185 Stereo,
187 Surround21,
189 Surround51,
191 Surround71,
193 Custom(u16),
195}
196
197impl ChannelLayout {
198 pub const fn channels(self) -> u16 {
200 match self {
201 Self::Mono => 1,
202 Self::Stereo => 2,
203 Self::Surround21 => 3,
204 Self::Surround51 => 6,
205 Self::Surround71 => 8,
206 Self::Custom(n) => n,
207 }
208 }
209
210 pub const fn from_channels(n: u16) -> Self {
212 match n {
213 1 => Self::Mono,
214 2 => Self::Stereo,
215 3 => Self::Surround21,
216 6 => Self::Surround51,
217 8 => Self::Surround71,
218 other => Self::Custom(other),
219 }
220 }
221
222 pub const fn is_standard(self) -> bool {
224 !matches!(self, Self::Custom(_))
225 }
226}
227
228impl std::fmt::Display for ChannelLayout {
229 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230 match self {
231 Self::Mono => write!(f, "Mono"),
232 Self::Stereo => write!(f, "Stereo"),
233 Self::Surround21 => write!(f, "2.1"),
234 Self::Surround51 => write!(f, "5.1"),
235 Self::Surround71 => write!(f, "7.1"),
236 Self::Custom(n) => write!(f, "{n}ch"),
237 }
238 }
239}
240
241#[non_exhaustive]
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
246pub struct AudioSpec {
247 pub format: SampleFormat,
249 pub rate: SampleRate,
251 pub layout: ChannelLayout,
253}
254
255impl AudioSpec {
256 pub const fn new(format: SampleFormat, rate: SampleRate, layout: ChannelLayout) -> Self {
258 Self {
259 format,
260 rate,
261 layout,
262 }
263 }
264
265 pub const fn cd_quality() -> Self {
267 Self::new(
268 SampleFormat::S16,
269 SampleRate::Hz44100,
270 ChannelLayout::Stereo,
271 )
272 }
273
274 pub const fn dvd_quality() -> Self {
276 Self::new(
277 SampleFormat::S24,
278 SampleRate::Hz48000,
279 ChannelLayout::Surround51,
280 )
281 }
282
283 pub const fn voice_quality() -> Self {
285 Self::new(SampleFormat::S16, SampleRate::Hz16000, ChannelLayout::Mono)
286 }
287
288 pub const fn float_stereo() -> Self {
290 Self::new(
291 SampleFormat::F32,
292 SampleRate::Hz48000,
293 ChannelLayout::Stereo,
294 )
295 }
296
297 pub const fn bytes_per_frame(self) -> usize {
299 #[allow(
302 clippy::arithmetic_side_effects,
303 clippy::as_conversions,
304 reason = "channels() is u16 cast to usize; product bounded by 4 * 65535 < usize::MAX"
305 )]
306 {
307 self.format.bytes_per_sample() * self.layout.channels() as usize
308 }
309 }
310
311 pub const fn bytes_per_second(self) -> usize {
313 #[allow(
316 clippy::arithmetic_side_effects,
317 clippy::as_conversions,
318 reason = "hz() is u32 cast to usize; product bounded and fits in usize on 64-bit platforms"
319 )]
320 {
321 self.bytes_per_frame() * self.rate.hz() as usize
322 }
323 }
324
325 pub fn duration_secs(self, bytes: usize) -> f64 {
327 let bps = self.bytes_per_second();
328 if bps == 0 {
329 return 0.0;
330 }
331 #[allow(
333 clippy::as_conversions,
334 reason = "usize → f64 cast; precision sufficient for audio duration calculations"
335 )]
336 {
337 bytes as f64 / bps as f64
338 }
339 }
340
341 pub fn bytes_for_duration(self, seconds: f64) -> usize {
343 #[allow(
345 clippy::as_conversions,
346 clippy::arithmetic_side_effects,
347 reason = "bytes_per_second() as f64 is exact for typical audio rates; f64 result cast to usize is bounded by valid duration inputs"
348 )]
349 {
350 (self.bytes_per_second() as f64 * seconds) as usize
351 }
352 }
353}
354
355impl std::fmt::Display for AudioSpec {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 write!(f, "{} {} {}", self.format, self.rate, self.layout)
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn sample_format_bytes() {
367 assert_eq!(SampleFormat::U8.bytes_per_sample(), 1);
368 assert_eq!(SampleFormat::S16.bytes_per_sample(), 2);
369 assert_eq!(SampleFormat::S24.bytes_per_sample(), 3);
370 assert_eq!(SampleFormat::S32.bytes_per_sample(), 4);
371 assert_eq!(SampleFormat::F32.bytes_per_sample(), 4);
372 }
373
374 #[test]
375 fn sample_format_bits() {
376 assert_eq!(SampleFormat::S16.bits_per_sample(), 16);
377 assert_eq!(SampleFormat::S24.bits_per_sample(), 24);
378 assert_eq!(SampleFormat::F32.bits_per_sample(), 32);
379 }
380
381 #[test]
382 fn sample_format_float_vs_int() {
383 assert!(SampleFormat::F32.is_float());
384 assert!(!SampleFormat::S16.is_float());
385 assert!(SampleFormat::S16.is_integer());
386 }
387
388 #[test]
389 fn sample_format_display() {
390 assert_eq!(SampleFormat::S16.to_string(), "S16");
391 assert_eq!(SampleFormat::F32.to_string(), "F32");
392 }
393
394 #[test]
395 fn sample_rate_hz() {
396 assert_eq!(SampleRate::Hz44100.hz(), 44100);
397 assert_eq!(SampleRate::Hz48000.hz(), 48000);
398 assert_eq!(SampleRate::Custom(32000).hz(), 32000);
399 }
400
401 #[test]
402 fn sample_rate_from_hz() {
403 assert_eq!(SampleRate::from_hz(44100), SampleRate::Hz44100);
404 assert_eq!(SampleRate::from_hz(12345), SampleRate::Custom(12345));
405 }
406
407 #[test]
408 fn sample_rate_standard() {
409 assert!(SampleRate::Hz44100.is_standard());
410 assert!(!SampleRate::Custom(32000).is_standard());
411 }
412
413 #[test]
414 fn sample_rate_period() {
415 let period = SampleRate::Hz48000.period_us();
416 assert!((period - 20.833).abs() < 0.1);
418 }
419
420 #[test]
421 fn sample_rate_display() {
422 assert_eq!(SampleRate::Hz44100.to_string(), "44.1kHz");
423 assert_eq!(SampleRate::Hz48000.to_string(), "48.0kHz");
424 }
425
426 #[test]
427 fn channel_layout_count() {
428 assert_eq!(ChannelLayout::Mono.channels(), 1);
429 assert_eq!(ChannelLayout::Stereo.channels(), 2);
430 assert_eq!(ChannelLayout::Surround51.channels(), 6);
431 assert_eq!(ChannelLayout::Surround71.channels(), 8);
432 assert_eq!(ChannelLayout::Custom(4).channels(), 4);
433 }
434
435 #[test]
436 fn channel_layout_from_channels() {
437 assert_eq!(ChannelLayout::from_channels(1), ChannelLayout::Mono);
438 assert_eq!(ChannelLayout::from_channels(2), ChannelLayout::Stereo);
439 assert_eq!(ChannelLayout::from_channels(4), ChannelLayout::Custom(4));
440 }
441
442 #[test]
443 fn channel_layout_display() {
444 assert_eq!(ChannelLayout::Stereo.to_string(), "Stereo");
445 assert_eq!(ChannelLayout::Surround51.to_string(), "5.1");
446 assert_eq!(ChannelLayout::Custom(4).to_string(), "4ch");
447 }
448
449 #[test]
450 fn audio_spec_cd_quality() {
451 let spec = AudioSpec::cd_quality();
452 assert_eq!(spec.format, SampleFormat::S16);
453 assert_eq!(spec.rate, SampleRate::Hz44100);
454 assert_eq!(spec.layout, ChannelLayout::Stereo);
455 }
456
457 #[test]
458 fn audio_spec_bytes_per_frame() {
459 let spec = AudioSpec::cd_quality();
460 assert_eq!(spec.bytes_per_frame(), 4);
462 }
463
464 #[test]
465 fn audio_spec_bytes_per_second() {
466 let spec = AudioSpec::cd_quality();
467 assert_eq!(spec.bytes_per_second(), 176_400);
469 }
470
471 #[test]
472 fn audio_spec_duration() {
473 let spec = AudioSpec::cd_quality();
474 let one_sec_bytes = spec.bytes_per_second();
475 let dur = spec.duration_secs(one_sec_bytes);
476 assert!((dur - 1.0).abs() < 0.001);
477 }
478
479 #[test]
480 fn audio_spec_bytes_for_duration() {
481 let spec = AudioSpec::cd_quality();
482 let bytes = spec.bytes_for_duration(1.0);
483 assert_eq!(bytes, 176_400);
484 }
485
486 #[test]
487 fn audio_spec_display() {
488 let spec = AudioSpec::cd_quality();
489 let s = spec.to_string();
490 assert!(s.contains("S16"));
491 assert!(s.contains("44.1kHz"));
492 assert!(s.contains("Stereo"));
493 }
494
495 #[test]
496 fn voice_quality_spec() {
497 let spec = AudioSpec::voice_quality();
498 assert_eq!(spec.format, SampleFormat::S16);
499 assert_eq!(spec.rate, SampleRate::Hz16000);
500 assert_eq!(spec.layout, ChannelLayout::Mono);
501 assert_eq!(spec.bytes_per_second(), 32000);
503 }
504
505 #[test]
506 fn dvd_quality_spec() {
507 let spec = AudioSpec::dvd_quality();
508 assert_eq!(spec.layout, ChannelLayout::Surround51);
509 assert_eq!(spec.bytes_per_second(), 864_000);
511 }
512
513 #[test]
514 fn float_stereo_spec() {
515 let spec = AudioSpec::float_stereo();
516 assert!(spec.format.is_float());
517 assert_eq!(spec.bytes_per_second(), 384_000);
519 }
520}