autd3_modulation_audio_file/
csv.rs

1use autd3_core::derive::*;
2
3use std::{fmt::Debug, fs::File, path::Path};
4
5use crate::error::AudioFileError;
6
7/// The option of [`Csv`].
8#[derive(Debug, Clone)]
9pub struct CsvOption {
10    /// The delimiter of CSV file.
11    pub delimiter: u8,
12}
13
14impl Default for CsvOption {
15    fn default() -> Self {
16        Self { delimiter: b',' }
17    }
18}
19
20/// [`Modulation`] from CSV data.
21#[derive(Modulation, Debug, Clone)]
22pub struct Csv<P, Config>
23where
24    P: AsRef<Path> + Clone + Debug,
25    Config: Into<SamplingConfig> + Debug + Copy,
26{
27    /// The path to the CSV file.
28    pub path: P,
29    /// The sampling configuration of the CSV file.
30    pub sampling_config: Config,
31    /// The option of [`Csv`].
32    pub option: CsvOption,
33}
34
35impl<P, Config> Csv<P, Config>
36where
37    P: AsRef<Path> + Clone + Debug,
38    Config: Into<SamplingConfig> + Debug + Copy,
39{
40    /// Create a new [`Csv`].
41    #[must_use]
42    pub const fn new(path: P, sampling_config: Config, option: CsvOption) -> Self {
43        Self {
44            path,
45            sampling_config,
46            option,
47        }
48    }
49
50    #[tracing::instrument]
51    fn read_buf(&self) -> Result<Vec<u8>, AudioFileError> {
52        let f = File::open(&self.path)?;
53        let mut rdr = csv::ReaderBuilder::new()
54            .has_headers(false)
55            .delimiter(self.option.delimiter)
56            .from_reader(f);
57        Ok(rdr
58            .records()
59            .map(|r| {
60                let record = r?;
61                csv::Result::Ok(
62                    record
63                        .iter()
64                        .map(|x| x.trim().to_owned())
65                        .collect::<Vec<_>>(),
66                )
67            })
68            .collect::<csv::Result<Vec<_>>>()?
69            .into_iter()
70            .flatten()
71            .map(|s| s.parse::<u8>())
72            .collect::<Result<Vec<u8>, _>>()?)
73    }
74}
75
76impl<P, Config> Modulation for Csv<P, Config>
77where
78    P: AsRef<Path> + Clone + Debug,
79    Config: Into<SamplingConfig> + Debug + Copy,
80{
81    fn calc(self) -> Result<Vec<u8>, ModulationError> {
82        let buffer = self.read_buf()?;
83        tracing::debug!("Read buffer: {:?}", buffer);
84        Ok(buffer)
85    }
86
87    fn sampling_config(&self) -> SamplingConfig {
88        self.sampling_config.into()
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use autd3_core::common::{Freq, Hz};
95
96    use super::*;
97    use std::io::Write;
98
99    fn create_csv(path: impl AsRef<Path>, data: &[u8]) -> anyhow::Result<()> {
100        let mut f = File::create(path)?;
101        data.iter().try_for_each(|d| writeln!(f, "{}", d))?;
102        Ok(())
103    }
104
105    #[rstest::rstest]
106    #[test]
107    #[case(vec![0xFF, 0x7F, 0x00], 4000. * Hz)]
108    fn new(#[case] data: Vec<u8>, #[case] sample_rate: Freq<f32>) -> anyhow::Result<()> {
109        let dir = tempfile::tempdir().unwrap();
110        let path = dir.path().join("tmp.csv");
111        create_csv(&path, &data)?;
112
113        let m = Csv::new(path, sample_rate, CsvOption::default());
114        assert_eq!(sample_rate.hz(), m.sampling_config().freq()?.hz());
115        assert_eq!(data, *m.calc()?);
116
117        Ok(())
118    }
119
120    #[test]
121    fn not_exists() -> anyhow::Result<()> {
122        let m = Csv {
123            path: Path::new("not_exists.csv"),
124            sampling_config: 4000. * Hz,
125            option: CsvOption::default(),
126        };
127        assert!(m.calc().is_err());
128        Ok(())
129    }
130}