1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub struct Tone {
13 pub hz: u32,
14 pub ms: u32,
15}
16
17const MAX_TOTAL_MS: u32 = 5 * 60 * 1000;
20
21#[derive(Clone, Copy)]
22enum Style {
23 Normal,
24 Legato,
25 Staccato,
26}
27
28pub fn parse(s: &[u8]) -> Vec<Tone> {
30 let mut p = Parser { s, i: 0 };
31 let (mut octave, mut length, mut tempo, mut style) = (4u32, 4u32, 120u32, Style::Normal);
32 let mut out = Vec::new();
33 let mut total = 0u32;
34 while let Some(c) = p.next() {
35 let tone_of = |key: Option<u32>, len: u32, dots: u32, style: Style, tempo: u32| {
36 let mut ms = 240_000 / (tempo * len.max(1)) as u64;
38 let mut extra = ms;
39 for _ in 0..dots {
40 extra /= 2;
41 ms += extra;
42 }
43 let ms = ms as u32;
44 let Some(key) = key else { return vec![Tone { hz: 0, ms }] };
45 let on = match style {
46 Style::Normal => ms * 7 / 8,
47 Style::Legato => ms,
48 Style::Staccato => ms * 3 / 4,
49 };
50 let mut v = vec![Tone { hz: frequency(key), ms: on }];
51 if on < ms {
52 v.push(Tone { hz: 0, ms: ms - on });
53 }
54 v
55 };
56 let tones = match c.to_ascii_uppercase() {
57 n @ b'A'..=b'G' => {
58 let mut key = octave as i32 * 12 + semitone(n);
59 match p.peek() {
60 Some(b'#' | b'+') => {
61 p.i += 1;
62 key += 1;
63 }
64 Some(b'-') => {
65 p.i += 1;
66 key -= 1;
67 }
68 _ => {}
69 }
70 let len = p.number().filter(|&n| (1..=64).contains(&n)).unwrap_or(length);
71 let dots = p.dots();
72 tone_of(Some(key.clamp(0, 83) as u32), len, dots, style, tempo)
73 }
74 b'N' => match p.number() {
75 Some(0) => tone_of(None, length, p.dots(), style, tempo),
76 Some(n @ 1..=84) => tone_of(Some(n - 1), length, p.dots(), style, tempo),
77 _ => Vec::new(),
78 },
79 b'P' => {
80 let len = p.number().filter(|&n| (1..=64).contains(&n)).unwrap_or(length);
81 tone_of(None, len, p.dots(), style, tempo)
82 }
83 b'O' => {
84 if let Some(n) = p.number().filter(|&n| n <= 6) {
85 octave = n;
86 }
87 Vec::new()
88 }
89 b'>' => {
90 octave = (octave + 1).min(6);
91 Vec::new()
92 }
93 b'<' => {
94 octave = octave.saturating_sub(1);
95 Vec::new()
96 }
97 b'L' => {
98 if let Some(n) = p.number().filter(|&n| (1..=64).contains(&n)) {
99 length = n;
100 }
101 Vec::new()
102 }
103 b'T' => {
104 if let Some(n) = p.number().filter(|&n| (32..=255).contains(&n)) {
105 tempo = n;
106 }
107 Vec::new()
108 }
109 b'M' => {
110 match p.next().map(|c| c.to_ascii_uppercase()) {
111 Some(b'N') => style = Style::Normal,
112 Some(b'L') => style = Style::Legato,
113 Some(b'S') => style = Style::Staccato,
114 _ => {}
115 }
116 Vec::new()
117 }
118 _ => Vec::new(),
119 };
120 for t in tones {
121 total = total.saturating_add(t.ms);
122 if total > MAX_TOTAL_MS {
123 return out;
124 }
125 match out.last_mut() {
127 Some(Tone { hz: 0, ms }) if t.hz == 0 => *ms += t.ms,
128 _ => out.push(t),
129 }
130 }
131 }
132 out
133}
134
135fn semitone(note: u8) -> i32 {
137 match note {
138 b'C' => 0,
139 b'D' => 2,
140 b'E' => 4,
141 b'F' => 5,
142 b'G' => 7,
143 b'A' => 9,
144 _ => 11,
145 }
146}
147
148fn frequency(key: u32) -> u32 {
151 (440.0 * 2f64.powf((key as f64 - 45.0) / 12.0)).round() as u32
152}
153
154struct Parser<'a> {
155 s: &'a [u8],
156 i: usize,
157}
158
159impl Parser<'_> {
160 fn peek(&self) -> Option<u8> {
161 self.s.get(self.i).copied()
162 }
163
164 fn next(&mut self) -> Option<u8> {
166 while let Some(c) = self.peek() {
167 self.i += 1;
168 if !c.is_ascii_whitespace() {
169 return Some(c);
170 }
171 }
172 None
173 }
174
175 fn number(&mut self) -> Option<u32> {
176 let start = self.i;
177 while self.peek().is_some_and(|c| c.is_ascii_digit()) {
178 self.i += 1;
179 }
180 std::str::from_utf8(&self.s[start..self.i]).ok()?.parse().ok()
181 }
182
183 fn dots(&mut self) -> u32 {
184 let mut n = 0;
185 while self.peek() == Some(b'.') {
186 self.i += 1;
187 n += 1;
188 }
189 n
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 fn t(hz: u32, ms: u32) -> Tone {
198 Tone { hz, ms }
199 }
200
201 #[test]
202 fn notes_lengths_and_tempo() {
203 assert_eq!(parse(b"O3A"), [t(440, 437), t(0, 63)]);
205 assert_eq!(parse(b"ML O3 A8"), [t(440, 250)]);
206 assert_eq!(parse(b"MS T60 O3 A"), [t(440, 750), t(0, 250)]);
207 assert_eq!(parse(b"MLO3A4."), [t(440, 750)]);
209 assert_eq!(parse(b"MLO3C"), [t(262, 500)]);
211 assert_eq!(parse(b"MLO3C#D-"), [t(277, 500), t(277, 500)]);
212 assert_eq!(parse(b"MLO3A>A<<A"), [t(440, 500), t(880, 500), t(220, 500)]);
213 }
214
215 #[test]
216 fn rests_numbers_and_junk() {
217 assert_eq!(parse(b"L8P4"), [t(0, 500)]);
218 assert_eq!(parse(b"MLN46N0P8"), [t(440, 500), t(0, 750)]);
220 assert_eq!(parse(b"ml o3 a xyz O9 L99 T5"), [t(440, 500)]);
222 assert!(parse(b"MFMB").is_empty());
223 }
224
225 #[test]
226 fn long_strings_are_cut_off() {
227 let s = "T32L1".to_string() + &"C".repeat(1000);
228 let total: u32 = parse(s.as_bytes()).iter().map(|t| t.ms).sum();
229 assert!(total <= MAX_TOTAL_MS);
230 }
231}