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; 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(¬e_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(¬e_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(¬e_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 if note_info.length == 0 {
299 return;
300 }
301
302 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 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 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 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 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}