Skip to main content

atmos/os_lib/
mml.rs

1// mml.rs - AtmOS MML (Music Macro Language) Parser
2#![allow(dead_code)]
3
4extern crate alloc;
5use alloc::vec::Vec;
6
7#[derive(Debug, Clone, Copy)]
8pub struct Note {
9    pub freq: u32,
10    pub duration_ms: u32,
11}
12
13/// MML 文字列を解析し、音符と休符の配列にデコードします
14pub fn parse_mml(mml: &str) -> Vec<Note> {
15    let mut notes = Vec::new();
16    let chars: Vec<char> = mml.to_ascii_uppercase().chars().collect();
17    let mut idx = 0;
18
19    let mut tempo = 120u32;
20    let mut octave = 4i32;
21    let mut default_len = 4u32; // デフォルトは4分音符
22
23    // 基準となるオクターブ4(C4 〜 B4)の周波数テーブル (Hz)
24    let note_freqs = [
25        261.63f32, // C   (0)
26        277.18f32, // C#  (1)
27        293.66f32, // D   (2)
28        311.13f32, // D#  (3)
29        329.63f32, // E   (4)
30        349.23f32, // F   (5)
31        369.99f32, // F#  (6)
32        392.00f32, // G   (7)
33        415.30f32, // G#  (8)
34        440.00f32, // A   (9)
35        466.16f32, // A#  (10)
36        493.88f32, // B   (11)
37    ];
38
39    while idx < chars.len() {
40        let c = chars[idx];
41        if c.is_whitespace() {
42            idx += 1;
43            continue;
44        }
45
46        match c {
47            'T' => {
48                idx += 1;
49                let mut num = 0;
50                while idx < chars.len() && chars[idx].is_ascii_digit() {
51                    num = num * 10 + (chars[idx] as u32 - '0' as u32);
52                    idx += 1;
53                }
54                if num > 0 {
55                    tempo = num;
56                }
57            }
58            'O' => {
59                idx += 1;
60                if idx < chars.len() && chars[idx].is_ascii_digit() {
61                    octave = chars[idx] as i32 - '0' as i32;
62                    idx += 1;
63                }
64            }
65            '>' => {
66                octave += 1;
67                if octave > 8 {
68                    octave = 8;
69                }
70                idx += 1;
71            }
72            '<' => {
73                octave -= 1;
74                if octave < 1 {
75                    octave = 1;
76                }
77                idx += 1;
78            }
79            'L' => {
80                idx += 1;
81                let mut num = 0;
82                while idx < chars.len() && chars[idx].is_ascii_digit() {
83                    num = num * 10 + (chars[idx] as u32 - '0' as u32);
84                    idx += 1;
85                }
86                if num > 0 {
87                    default_len = num;
88                }
89            }
90            'C' | 'D' | 'E' | 'F' | 'G' | 'A' | 'B' | 'R' => {
91                let note_char = c;
92                idx += 1;
93
94                // シャープ (# / +) または フラット (-)
95                let mut sharp = 0i32;
96                if idx < chars.len() {
97                    let next = chars[idx];
98                    if next == '+' || next == '#' {
99                        sharp = 1;
100                        idx += 1;
101                    } else if next == '-' {
102                        sharp = -1;
103                        idx += 1;
104                    }
105                }
106
107                // 音符ごとの長さ指定 (例: C8)
108                let mut len = 0u32;
109                while idx < chars.len() && chars[idx].is_ascii_digit() {
110                    len = len * 10 + (chars[idx] as u32 - '0' as u32);
111                    idx += 1;
112                }
113                let final_len = if len > 0 { len } else { default_len };
114
115                // 付点 (.) の解析
116                let mut has_dot = false;
117                if idx < chars.len() && chars[idx] == '.' {
118                    has_dot = true;
119                    idx += 1;
120                }
121
122                // 演奏持続時間 (ms) の計算
123                // 全音符の基準時間 (ms) = 240,000 / テンポ
124                let mut duration = 240_000u32 / (tempo * final_len);
125                if has_dot {
126                    duration = (duration * 3) / 2;
127                }
128
129                if note_char == 'R' {
130                    notes.push(Note {
131                        freq: 0,
132                        duration_ms: duration,
133                    });
134                } else {
135                    let base_idx = match note_char {
136                        'C' => 0,
137                        'D' => 2,
138                        'E' => 4,
139                        'F' => 5,
140                        'G' => 7,
141                        'A' => 9,
142                        'B' => 11,
143                        _ => 0,
144                    };
145
146                    let mut final_idx = base_idx + sharp;
147                    let mut final_octave = octave;
148                    while final_idx < 0 {
149                        final_idx += 12;
150                        final_octave -= 1;
151                    }
152                    while final_idx >= 12 {
153                        final_idx -= 12;
154                        final_octave += 1;
155                    }
156
157                    final_octave = final_octave.clamp(1, 8);
158
159                    let base_freq = note_freqs[final_idx as usize];
160                    let freq = if final_octave >= 4 {
161                        (base_freq * (1 << (final_octave - 4)) as f32) as u32
162                    } else {
163                        (base_freq / (1 << (4 - final_octave)) as f32) as u32
164                    };
165
166                    notes.push(Note {
167                        freq,
168                        duration_ms: duration,
169                    });
170                }
171            }
172            _ => {
173                // 解析不能な不正な文字は無視して次の文字へ進める
174                idx += 1;
175            }
176        }
177    }
178
179    notes
180}