m8_file_parser/
scale.rs

1use crate::reader::*;
2use crate::version::*;
3
4use std::fmt;
5
6use arr_macro::arr;
7use byteorder::{ByteOrder, LittleEndian};
8
9#[derive(PartialEq, Clone)]
10pub struct Scale {
11    pub number: u8,
12    pub name: String,
13    pub notes: [NoteOffset; 12], // Offsets for notes C-B
14}
15
16impl Scale {
17    const SIZE: usize = 32;
18
19    pub fn read(reader: &mut impl std::io::Read) -> M8Result<Self> {
20        let mut buf: Vec<u8> = vec![];
21        reader.read_to_end(&mut buf).unwrap();
22        let len = buf.len();
23        let mut reader = Reader::new(buf);
24
25        if len < Self::SIZE + Version::SIZE {
26            return Err(ParseError(
27                "File is not long enough to be a M8 Scale".to_string(),
28            ));
29        }
30        Version::from_reader(&mut reader)?;
31        Self::from_reader(&mut reader, 0)
32    }
33
34    pub(crate) fn from_reader(reader: &mut Reader, number: u8) -> M8Result<Self> {
35        let map = LittleEndian::read_u16(reader.read_bytes(2));
36        let mut notes = arr![NoteOffset::default(); 12];
37
38        for (i, note) in notes.iter_mut().enumerate() {
39            note.enabled = ((map >> i) & 0x1) == 1;
40            let offset = f32::from(reader.read()) + (f32::from(reader.read()) / 100.0);
41            note.semitones = offset;
42        }
43
44        let name = reader.read_string(16);
45        Ok(Self {
46            number,
47            name,
48            notes,
49        })
50    }
51}
52
53impl Default for Scale {
54    fn default() -> Self {
55        Self {
56            number: 0,
57            name: "CHROMATIC".to_string(),
58            notes: arr![NoteOffset::default(); 12],
59        }
60    }
61}
62
63impl fmt::Display for Scale {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        let notes = vec![
66            "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
67        ];
68        let offsets = self
69            .notes
70            .iter()
71            .zip(notes.iter())
72            .map(|(offset, note)| -> String {
73                let s = if offset.enabled {
74                    let sign = if offset.semitones < 0.0 { "-" } else { " " };
75                    format!(" ON{}{:02.2}", sign, offset.semitones.abs())
76                } else {
77                    " -- -- --".to_string()
78                };
79                format!("{:<2}{}", note, &s)
80            })
81            .collect::<Vec<String>>()
82            .join("\n");
83
84        write!(
85            f,
86            "Scale {}\nKEY   C\n\n   EN OFFSET\n{}\n\nNAME  {}",
87            self.number, offsets, &self.name
88        )
89    }
90}
91impl fmt::Debug for Scale {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "{}", &self)
94    }
95}
96
97#[derive(PartialEq, Debug, Clone, Copy)]
98pub struct NoteOffset {
99    pub enabled: bool,
100    pub semitones: f32, // Semitones.cents: -24.0-24.0
101}
102impl NoteOffset {
103    fn default() -> Self {
104        Self {
105            enabled: true,
106            semitones: 0.0,
107        }
108    }
109}