lsdj/song/
mod.rs

1//! Unparsed LSDJ song memory
2
3pub(crate) mod instrument;
4pub(crate) mod wave;
5
6use std::io::{self, Read, Write};
7use thiserror::Error;
8
9/// A contiguous block of memory that represents unparsed song data
10///
11/// Future versions of this create might parse [`SongMemory`] into different formatted versions
12/// of songs, but for now this suffices to import and export songs from [`SRam`](crate::sram).
13pub struct SongMemory {
14    /// The bytes that make up the song
15    bytes: [u8; Self::LEN],
16}
17
18impl SongMemory {
19    /// The number of bytes taken up by a single LSDJ song
20    pub const LEN: usize = 0x8000;
21
22    /// Construct a new, empty song, ready for use
23    ///
24    /// This sets all the necessary verification bytes that LSDJ uses to check for memory corruption.
25    pub fn new() -> Self {
26        Self {
27            bytes: *include_bytes!("92L_empty.raw"),
28        }
29    }
30
31    /// Deserialize [`SongMemory`] from bytes
32    pub fn from_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
33        let bytes: [u8; Self::LEN] = bytes
34            .try_into()
35            .map_err(|_| FromBytesError::IncorrectSize)?;
36
37        let check = |offset| bytes[offset] == 0x72 && bytes[offset + 1] == 0x62;
38
39        if check(0x1E78) || check(0x3E80) || check(0x7FF0) {
40            Ok(Self { bytes })
41        } else {
42            Err(FromBytesError::InitializationCheckIncorrect)
43        }
44    }
45
46    /// Deserialize [`SongMemory`] from an arbitrary I/O reader
47    pub fn from_reader<R>(mut reader: R) -> Result<Self, FromReaderError>
48    where
49        R: Read,
50    {
51        let mut bytes = [0; Self::LEN];
52        reader.read_exact(bytes.as_mut_slice())?;
53
54        let song = Self::from_bytes(&bytes)?;
55
56        Ok(song)
57    }
58
59    /// Serialize [`SongMemory`] to an arbitrary I/O writer
60    pub fn to_writer<W>(&self, mut writer: W) -> Result<(), io::Error>
61    where
62        W: Write,
63    {
64        writer.write_all(&self.bytes)
65    }
66
67    /// The version of the format the song is encoded in
68    pub fn format_version(&self) -> u8 {
69        self.bytes[0x7FFF]
70    }
71
72    /// Access the bytes that make up the song
73    pub fn as_slice(&self) -> &[u8] {
74        &self.bytes
75    }
76
77    /// Access the bytes that make up the song
78    pub fn as_mut_slice(&mut self) -> &mut [u8] {
79        &mut self.bytes
80    }
81}
82
83impl Default for SongMemory {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89/// Errors that might be returned from [`SongMemory::from_bytes()`]
90#[derive(Debug, Error)]
91pub enum FromBytesError {
92    /// The passed in number of bytes isn't correct
93    #[error("The slice isn't of the correct size")]
94    IncorrectSize,
95
96    /// All correctly initialized song memory has certain bytes set for
97    /// verification against memory corruption.
98    ///
99    /// This error is returned when that those bytes are faulty during a read.
100    #[error("The initialization check failed")]
101    InitializationCheckIncorrect,
102}
103
104/// Errors that might be returned from [`SongMemory::from_reader()`]
105#[derive(Debug, Error)]
106pub enum FromReaderError {
107    /// Reading the bytes failed
108    #[error("Something failed with I/O")]
109    Read(#[from] io::Error),
110
111    /// Deserialization from the read bytes failed
112    #[error("Deserialiazation from the read bytes failed")]
113    FromBytes(#[from] FromBytesError),
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn empty_92l() {
122        use std::io::Cursor;
123
124        let song = {
125            let bytes = Cursor::new(include_bytes!("../../test/92L_empty.sav"));
126            SongMemory::from_reader(bytes).expect("could not parse song")
127        };
128
129        assert_eq!(song.format_version(), 0x16);
130    }
131}