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    fn read_buf(&self) -> Result<Vec<u8>, AudioFileError> {
51        let f = File::open(&self.path)?;
52        let mut rdr = csv::ReaderBuilder::new()
53            .has_headers(false)
54            .delimiter(self.option.delimiter)
55            .from_reader(f);
56        Ok(rdr
57            .records()
58            .map(|r| {
59                let record = r?;
60                csv::Result::Ok(
61                    record
62                        .iter()
63                        .map(|x| x.trim().to_owned())
64                        .collect::<Vec<_>>(),
65                )
66            })
67            .collect::<csv::Result<Vec<_>>>()?
68            .into_iter()
69            .flatten()
70            .map(|s| s.parse::<u8>())
71            .collect::<Result<Vec<u8>, _>>()?)
72    }
73}
74
75impl<P, Config> Modulation for Csv<P, Config>
76where
77    P: AsRef<Path> + Clone + Debug,
78    Config: Into<SamplingConfig> + Debug + Copy,
79{
80    fn calc(self, _: &FirmwareLimits) -> Result<Vec<u8>, ModulationError> {
81        Ok(self.read_buf()?)
82    }
83
84    fn sampling_config(&self) -> SamplingConfig {
85        self.sampling_config.into()
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use autd3_core::common::{Freq, Hz};
92
93    use super::*;
94    use std::io::Write;
95
96    fn create_csv(path: impl AsRef<Path>, data: &[u8]) -> anyhow::Result<()> {
97        let mut f = File::create(path)?;
98        data.iter().try_for_each(|d| writeln!(f, "{d}"))?;
99        Ok(())
100    }
101
102    #[rstest::rstest]
103    #[test]
104    #[case(vec![0xFF, 0x7F, 0x00], 4000. * Hz)]
105    fn new(#[case] data: Vec<u8>, #[case] sample_rate: Freq<f32>) -> anyhow::Result<()> {
106        let dir = tempfile::tempdir().unwrap();
107        let path = dir.path().join("tmp.csv");
108        create_csv(&path, &data)?;
109
110        let m = Csv::new(path, sample_rate, CsvOption::default());
111        assert_eq!(sample_rate.hz(), m.sampling_config().freq()?.hz());
112        assert_eq!(data, *m.calc(&FirmwareLimits::unused())?);
113
114        Ok(())
115    }
116
117    #[test]
118    fn not_exists() -> anyhow::Result<()> {
119        let m = Csv {
120            path: Path::new("not_exists.csv"),
121            sampling_config: 4000. * Hz,
122            option: CsvOption::default(),
123        };
124        assert!(m.calc(&FirmwareLimits::unused()).is_err());
125        Ok(())
126    }
127}