mugen_snd/
decoder.rs

1use std::{
2    borrow::Cow,
3    error::Error,
4    fmt::Display,
5    io::{self, Cursor},
6    str::{self, Utf8Error},
7};
8
9use byteorder::{LittleEndian, ReadBytesExt};
10
11use crate::SampleId;
12
13#[derive(Debug, Clone)]
14pub struct Decoder<'a> {
15    samples_count: u32,
16    comments: Comments<'a>,
17    samples: Vec<Sample<'a>>,
18}
19
20#[derive(Debug)]
21pub enum DecodeError {
22    InvalidData(io::Error),
23    InvalidSignature,
24    SampleCountMismatch {
25        expected_count: u32,
26        found_count: u32,
27    },
28}
29
30impl Display for DecodeError {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            DecodeError::InvalidData(err) => err.fmt(f),
34            DecodeError::InvalidSignature => write!(f, "invalid signature"),
35            DecodeError::SampleCountMismatch {
36                expected_count,
37                found_count,
38            } => write!(f, "expected {expected_count} samples, found {found_count}"),
39        }
40    }
41}
42
43impl Error for DecodeError {
44    fn source(&self) -> Option<&(dyn Error + 'static)> {
45        match self {
46            DecodeError::InvalidData(ref err) => Some(err),
47            _ => None,
48        }
49    }
50}
51
52impl From<io::Error> for DecodeError {
53    fn from(error: io::Error) -> Self {
54        DecodeError::InvalidData(error)
55    }
56}
57
58impl<'a> Decoder<'a> {
59    pub fn decode(data: &'a [u8]) -> Result<Self, DecodeError> {
60        if &data[0..12] != b"ElecbyteSnd\0" {
61            return Err(DecodeError::InvalidSignature);
62        }
63
64        let mut bytes = Cursor::new(&data);
65        bytes.set_position(16);
66
67        let samples_count = bytes.read_u32::<LittleEndian>()?;
68        let first_subfile_offset = bytes.read_u32::<LittleEndian>()?;
69        let subfile_header_size = 15;
70        let comments = Comments(&data[24..511]);
71
72        let mut samples = Vec::with_capacity(samples_count as usize);
73        let mut next_subfile_offset = Some(first_subfile_offset);
74        let total_size = data.len() as u32;
75        while let Some(offset) = next_subfile_offset.take() {
76            bytes.set_position(offset.into());
77
78            next_subfile_offset = bytes.read_u32::<LittleEndian>().ok().and_then(|n| match n {
79                n if n > 0 && n + subfile_header_size <= total_size => Some(n),
80                _ => None,
81            });
82
83            let size = bytes.read_u32::<LittleEndian>()? as usize;
84            let group = bytes.read_u32::<LittleEndian>()?;
85            let number = bytes.read_u32::<LittleEndian>()?;
86
87            let data_offset = bytes.position() as usize;
88
89            samples.push(Sample {
90                id: SampleId { group, number },
91                data: Cow::Borrowed(&data[data_offset..data_offset + size]),
92            });
93        }
94
95        if samples.len() != samples_count as usize {
96            return Err(DecodeError::SampleCountMismatch {
97                expected_count: samples_count,
98                found_count: samples.len() as u32,
99            });
100        }
101
102        Ok(Decoder {
103            samples_count,
104            comments,
105            samples,
106        })
107    }
108
109    pub fn comments(&self) -> &Comments {
110        &self.comments
111    }
112
113    pub fn samples_count(&self) -> u32 {
114        self.samples_count
115    }
116
117    pub fn samples(&self) -> impl Iterator<Item = &Sample> {
118        self.samples.iter()
119    }
120}
121
122impl<'a> IntoIterator for Decoder<'a> {
123    type Item = Sample<'a>;
124    type IntoIter = std::vec::IntoIter<Self::Item>;
125
126    fn into_iter(self) -> Self::IntoIter {
127        self.samples.into_iter()
128    }
129}
130
131#[derive(Debug, Clone)]
132pub struct Sample<'a> {
133    id: SampleId,
134    data: Cow<'a, [u8]>,
135}
136
137impl<'a> Sample<'a> {
138    pub fn id(&self) -> SampleId {
139        self.id
140    }
141
142    pub fn raw_data(&self) -> &[u8] {
143        &self.data
144    }
145
146    pub fn to_owned(&self) -> Sample<'static> {
147        Sample {
148            id: self.id,
149            data: Cow::Owned(self.raw_data().to_owned()),
150        }
151    }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct Comments<'a>(&'a [u8]);
156
157impl<'a> Comments<'a> {
158    pub fn as_str(&self) -> Result<&str, Utf8Error> {
159        Ok(str::from_utf8(self.0)?.trim_end_matches('\u{0}'))
160    }
161}