quadio_core/
reader.rs

1use hound::SampleFormat;
2use std::io::{Read, Seek};
3use std::num::TryFromIntError;
4
5#[derive(Debug, Clone, Copy, Eq, PartialEq)]
6pub struct Metadata {
7    pub sample_rate: u32,
8    pub sample_count: u32,
9    pub loop_start: Option<u32>,
10    pub end: Option<u32>,
11    pub bits_per_sample: u16,
12}
13
14pub struct QWaveReader<R: Read> {
15    reader: hound::WavReader<R>,
16    loop_start: Option<u32>,
17    loop_length: Option<u32>,
18}
19
20impl<R: Read + Seek> QWaveReader<R> {
21    pub fn new(reader: R) -> Result<Self, String> {
22        let mut chunk_reader =
23            cuet::ChunkReader::new(reader).map_err(|e| e.to_string())?;
24
25        let cue_chunk = chunk_reader
26            .read_next_chunk(Some(*b"cue "))
27            .map_err(|e| e.to_string())?;
28
29        let loop_start = cue_chunk.and_then(|(_, bytes)| {
30            let pts = cuet::parse_cue_points(&bytes[..]);
31
32            if pts.is_empty() {
33                None
34            } else {
35                Some(pts[0].sample_offset)
36            }
37        });
38
39        let loop_length = if loop_start.is_some() {
40            let list_chunk = chunk_reader
41                .read_next_chunk(Some(*b"LIST"))
42                .map_err(|e| e.to_string())?;
43
44            list_chunk.and_then(|(_, bytes)| {
45                let labeled_texts =
46                    cuet::extract_labeled_text_from_list(&bytes);
47                labeled_texts
48                    .first()
49                    .filter(|ltxt| ltxt.purpose_id == *b"mark")
50                    .map(|ltxt| ltxt.sample_length)
51            })
52        } else {
53            None
54        };
55
56        let reader = hound::WavReader::new(
57            chunk_reader.restore_cursor().map_err(|e| e.to_string())?,
58        )
59        .map_err(|e| e.to_string())?;
60
61        Ok(QWaveReader {
62            reader,
63            loop_start,
64            loop_length,
65        })
66    }
67}
68
69impl<R: Read> QWaveReader<R> {
70    pub fn metadata(&self) -> Metadata {
71        let sample_count = self.reader.duration();
72
73        let end = if let (Some(start), Some(length)) =
74            (self.loop_start, self.loop_length)
75        {
76            start.checked_add(length)
77        } else {
78            None
79        };
80
81        Metadata {
82            sample_rate: self.reader.spec().sample_rate,
83            sample_count,
84            loop_start: self.loop_start,
85            end,
86            bits_per_sample: self.reader.spec().bits_per_sample,
87        }
88    }
89
90    pub fn collect_samples(&mut self) -> Result<Vec<i16>, String> {
91        let mut error = Option::<String>::None;
92        let spec = self.reader.spec();
93        let duration = self
94            .reader
95            .duration()
96            .try_into()
97            .map_err(|e: TryFromIntError| e.to_string())?;
98
99        if spec.channels != 1 {
100            return Err("Too many channels".into());
101        }
102
103        if spec.sample_format != SampleFormat::Int {
104            return Err("Float samples are unsupported".into());
105        }
106
107        let samp_to_i16 = if spec.bits_per_sample == 8 {
108            |s| s << 8
109        } else if spec.bits_per_sample == 16 {
110            |s| s
111        } else {
112            return Err("Samples must be 8- or 16-bits".into());
113        };
114
115        let samples = self
116            .reader
117            .samples::<i16>()
118            .take(duration)
119            .map_while(|s| match s {
120                Ok(s) => Some(samp_to_i16(s)),
121                Err(e) => {
122                    error = Some(e.to_string());
123                    None
124                }
125            })
126            .collect();
127
128        if let Some(e) = error {
129            Err(e)
130        } else {
131            Ok(samples)
132        }
133    }
134}