pyxel/
mml_parser.rs

1use std::array;
2use std::iter::Peekable;
3
4use crate::channel::{Note, Volume};
5use crate::oscillator::ToneIndex;
6use crate::settings::{
7    EFFECT_FADEOUT, EFFECT_HALF_FADEOUT, EFFECT_NONE, EFFECT_QUARTER_FADEOUT, EFFECT_VIBRATO,
8};
9use crate::sound::Sound;
10
11type EnvIndex = u32;
12type EnvData = Vec<Volume>;
13
14enum VolEnv {
15    Constant(Volume),
16    Envelope(EnvIndex),
17}
18
19#[derive(Default)]
20struct NoteInfo {
21    length: u32,
22    quantize: u32,
23    tone: ToneIndex,
24    env_start: u32,
25    env_data: EnvData,
26    vibrato: bool,
27    note: Note,
28    is_tied: bool,
29}
30
31impl Sound {
32    pub fn mml(&mut self, mml_str: &str) {
33        let mut chars = mml_str.chars().peekable();
34        let mut length = 4;
35        let mut quantize = 7;
36        let mut octave = 2;
37        let mut tone = 0;
38        let mut vol_env = VolEnv::Constant(7);
39        let mut envelopes: [EnvData; 8] = array::from_fn(|_| vec![7]);
40        let mut note_info = NoteInfo::default();
41        self.speed = 9; // T=100
42
43        while chars.peek().is_some() {
44            if let Some(value) = Self::parse_command(&mut chars, 't') {
45                self.speed = (900 / value).max(1);
46            } else if Self::parse_char(&mut chars, 'l') {
47                length = Self::parse_note_length(&mut chars, length);
48            } else if let Some(value) = Self::parse_command(&mut chars, '@') {
49                if value <= 3 {
50                    tone = value as ToneIndex;
51                } else {
52                    panic!("Invalid tone value '{value}' in MML");
53                }
54            } else if let Some(value) = Self::parse_command(&mut chars, 'o') {
55                if value <= 4 {
56                    octave = value as Note;
57                } else {
58                    panic!("Invalid octave value '{value}' in MML");
59                }
60            } else if Self::parse_char(&mut chars, '>') {
61                if octave < 4 {
62                    octave += 1;
63                } else {
64                    panic!("Octave exceeded maximum in MML");
65                }
66            } else if Self::parse_char(&mut chars, '<') {
67                if octave > 0 {
68                    octave -= 1;
69                } else {
70                    panic!("Octave exceeded minimum in MML");
71                }
72            } else if let Some(value) = Self::parse_command(&mut chars, 'q') {
73                if (1..=8).contains(&value) {
74                    quantize = value;
75                } else {
76                    panic!("Invalid quantize value '{value}' in MML");
77                }
78            } else if let Some(value) = Self::parse_command(&mut chars, 'v') {
79                if value <= 7 {
80                    vol_env = VolEnv::Constant(value as Volume);
81                } else {
82                    panic!("Invalid volume value '{value}' in MML");
83                }
84            } else if let Some((env_index, env_data)) = Self::parse_envelope(&mut chars) {
85                vol_env = VolEnv::Envelope(env_index);
86                if !env_data.is_empty() {
87                    envelopes[env_index as usize] = env_data;
88                }
89            } else if let Some((note, length)) = Self::parse_note(&mut chars, length) {
90                self.add_note(&note_info);
91
92                let note = note + octave * 12;
93                let env_data = match vol_env {
94                    VolEnv::Constant(volume) => vec![volume],
95                    VolEnv::Envelope(envelope) => envelopes[envelope as usize].clone(),
96                };
97                let env_start = if note_info.is_tied && note_info.note == note {
98                    note_info.length + note_info.env_start
99                } else {
100                    0
101                };
102
103                note_info = NoteInfo {
104                    length,
105                    quantize,
106                    tone,
107                    env_start,
108                    env_data,
109                    vibrato: false,
110                    note,
111                    is_tied: false,
112                };
113            } else if let Some(length) = Self::parse_rest(&mut chars, length) {
114                self.add_note(&note_info);
115
116                note_info = NoteInfo {
117                    length,
118                    quantize,
119                    tone,
120                    env_start: 0,
121                    env_data: vec![0],
122                    vibrato: false,
123                    note: -1,
124                    is_tied: false,
125                };
126            } else if Self::parse_char(&mut chars, '~') {
127                note_info.vibrato = true;
128            } else if Self::parse_char(&mut chars, '&') {
129                note_info.quantize = 8;
130                note_info.is_tied = true;
131            } else {
132                let c = chars.peek().unwrap();
133                panic!("Invalid command '{c}' in MML");
134            }
135        }
136
137        self.add_note(&note_info);
138    }
139
140    fn skip_whitespace<T: Iterator<Item = char>>(chars: &mut Peekable<T>) {
141        while let Some(&c) = chars.peek() {
142            if c.is_whitespace() {
143                chars.next();
144            } else {
145                break;
146            }
147        }
148    }
149
150    fn parse_number<T: Iterator<Item = char>>(chars: &mut Peekable<T>) -> Option<u32> {
151        Self::skip_whitespace(chars);
152
153        let mut number_str = String::new();
154        while let Some(&c) = chars.peek() {
155            if c.is_ascii_digit() {
156                number_str.push(chars.next().unwrap());
157            } else {
158                break;
159            }
160        }
161
162        if number_str.is_empty() {
163            None
164        } else {
165            number_str.parse().ok()
166        }
167    }
168
169    fn parse_char<T: Iterator<Item = char>>(chars: &mut Peekable<T>, target: char) -> bool {
170        Self::skip_whitespace(chars);
171
172        if let Some(&c) = chars.peek() {
173            if c.eq_ignore_ascii_case(&target) {
174                chars.next();
175                return true;
176            }
177        }
178
179        false
180    }
181
182    fn parse_command<T: Iterator<Item = char>>(
183        chars: &mut Peekable<T>,
184        target: char,
185    ) -> Option<u32> {
186        if Self::parse_char(chars, target) {
187            if let Some(number) = Self::parse_number(chars) {
188                return Some(number);
189            }
190
191            panic!("Missing value after '{target}' in MML");
192        }
193
194        None
195    }
196
197    fn parse_envelope<T: Iterator<Item = char>>(
198        chars: &mut Peekable<T>,
199    ) -> Option<(EnvIndex, EnvData)> {
200        let envelope = Self::parse_command(chars, 'x');
201        envelope?;
202
203        let envelope = envelope.unwrap();
204        assert!(envelope <= 7, "Invalid envelope value '{envelope}' in MML");
205
206        let mut env_data = Vec::new();
207        if !Self::parse_char(chars, ':') {
208            return Some((envelope, env_data));
209        }
210
211        Self::skip_whitespace(chars);
212        while let Some(&c) = chars.peek() {
213            if c.is_ascii_digit() {
214                let volume = chars.next().unwrap().to_string().parse().unwrap();
215                if volume <= 7 {
216                    env_data.push(volume);
217                } else {
218                    panic!("Invalid envlope volume '{volume}' in MML");
219                }
220            } else {
221                break;
222            }
223
224            Self::skip_whitespace(chars);
225        }
226
227        assert!(!env_data.is_empty(), "Missing envelope volumes in MML");
228        Some((envelope, env_data))
229    }
230
231    fn parse_note<T: Iterator<Item = char>>(
232        chars: &mut Peekable<T>,
233        length: u32,
234    ) -> Option<(Note, u32)> {
235        Self::skip_whitespace(chars);
236
237        let mut note = match chars.peek()?.to_ascii_lowercase() {
238            'c' => 0,
239            'd' => 2,
240            'e' => 4,
241            'f' => 5,
242            'g' => 7,
243            'a' => 9,
244            'b' => 11,
245            _ => return None,
246        };
247        chars.next();
248
249        if Self::parse_char(chars, '#') || Self::parse_char(chars, '+') {
250            note += 1;
251        } else if Self::parse_char(chars, '-') {
252            note -= 1;
253        }
254
255        Some((note, Self::parse_note_length(chars, length)))
256    }
257
258    fn parse_note_length<T: Iterator<Item = char>>(
259        chars: &mut Peekable<T>,
260        cur_length: u32,
261    ) -> u32 {
262        let mut length = cur_length;
263
264        if let Some(temp_length) = Self::parse_number(chars) {
265            if temp_length <= 32 && 32 % temp_length == 0 {
266                length = 32 / temp_length;
267            } else {
268                panic!("Invalid note length '{temp_length}' in MML");
269            }
270        }
271
272        let mut target_length = length;
273        while Self::parse_char(chars, '.') {
274            if target_length >= 2 {
275                target_length /= 2;
276                length += target_length;
277            } else {
278                panic!("Length added by dot is too short in MML");
279            }
280        }
281
282        length
283    }
284
285    fn parse_rest<T: Iterator<Item = char>>(
286        chars: &mut Peekable<T>,
287        cur_length: u32,
288    ) -> Option<u32> {
289        if !Self::parse_char(chars, 'r') {
290            return None;
291        }
292
293        Some(Self::parse_note_length(chars, cur_length))
294    }
295
296    fn add_note(&mut self, note_info: &NoteInfo) {
297        // Hnadle empty note
298        if note_info.length == 0 {
299            return;
300        }
301
302        // Add tones and volumes
303        repeat_extend!(&mut self.tones, note_info.tone, note_info.length);
304        for i in 0..note_info.length {
305            let env_start = ((note_info.env_start + i) as usize).min(note_info.env_data.len() - 1);
306            self.volumes.push(note_info.env_data[env_start]);
307        }
308
309        // Handle rest note
310        if note_info.note == -1 {
311            repeat_extend!(&mut self.notes, -1, note_info.length);
312            repeat_extend!(&mut self.effects, EFFECT_NONE, note_info.length);
313            return;
314        }
315
316        // Add full-length notes
317        let duration = note_info.length * note_info.quantize;
318        let num_notes = duration / 8;
319        let note_effect = if note_info.vibrato {
320            EFFECT_VIBRATO
321        } else {
322            EFFECT_NONE
323        };
324
325        repeat_extend!(&mut self.notes, note_info.note, num_notes);
326        repeat_extend!(&mut self.effects, note_effect, num_notes);
327        if num_notes == note_info.length {
328            return;
329        }
330
331        // Add fade-out note
332        self.notes.push(note_info.note);
333        if num_notes > 0 {
334            self.effects.push(EFFECT_FADEOUT);
335        } else if duration >= 6 {
336            self.effects.push(EFFECT_QUARTER_FADEOUT);
337        } else if duration >= 4 {
338            self.effects.push(EFFECT_HALF_FADEOUT);
339        } else {
340            self.effects.push(EFFECT_FADEOUT);
341        }
342
343        // Add rests
344        let num_rests = note_info.length - num_notes - 1;
345        repeat_extend!(&mut self.notes, -1, num_rests);
346        repeat_extend!(&mut self.effects, EFFECT_NONE, num_rests);
347    }
348}