Skip to main content

atmos/apps/music/
mod.rs

1//! MML(Music Macro Language)音楽プレイヤーアプリ。
2//!
3//! [`PRESET_SONGS`] のプリセット曲を [`crate::os_lib::mml`] でパースし、
4//! [`crate::kernel::audio`](GPIO PWM 出力)で再生する。
5// src/music_app.rs - Visual Music Player for AtmOS
6#![allow(dead_code)]
7
8extern crate alloc;
9use alloc::string::String;
10
11use crate::kernel::audio::Audio;
12use crate::kernel::draw::{Color, Screen};
13use crate::kernel::window_mgr::App;
14
15struct PresetSong {
16    title: &'static str,
17    mml: &'static str,
18}
19
20static PRESET_SONGS: &[PresetSong] = &[
21    PresetSong {
22        title: "KiraKira Star",
23        mml: "T120 L4 O4 C C G G A A G2 F F E E D D C2 G G F F E E D2 G G F F E E D2 C C G G A A G2 F F E E D D C2",
24    },
25    PresetSong {
26        title: "Fur Elise",
27        mml: "T150 L8 O5 E D# E D# E O4 B O5 D C O4 A2 A C E A B2 B E G# B O5 C2 O5 E D# E D# E O4 B O5 D C O4 A2",
28    },
29    PresetSong {
30        title: "Atmos Symphony",
31        mml: "T140 L8 O4 E A O5 C2 O4 B A O5 C O4 B2 B O5 D F2 E D F E2 E A O5 C2 O4 B A O5 E O5 D2 O5 D C O4 B O5 C O4 A2",
32    },
33];
34
35pub struct MusicApp {
36    selected_song: usize,
37    last_win_w: u32,
38    last_win_h: u32,
39}
40
41impl Default for MusicApp {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl MusicApp {
48    pub fn new() -> Self {
49        Self {
50            selected_song: 0,
51            last_win_w: 450,
52            last_win_h: 300,
53        }
54    }
55}
56
57impl App for MusicApp {
58    fn name(&self) -> &str {
59        "music"
60    }
61
62    fn draw(&mut self, screen: &Screen, win_x: u32, win_y: u32, win_w: u32, win_h: u32) {
63        self.last_win_w = win_w;
64        self.last_win_h = win_h;
65
66        let config = crate::kernel::config::get_config();
67
68        // 1. 背景
69        screen.boxfill(
70            win_x,
71            win_y,
72            win_x + win_w,
73            win_y + win_h,
74            Color(config.theme.terminal_bg),
75        );
76
77        // 2. ヘッダータイトル
78        screen.draw_string_vector(
79            win_x + 15,
80            win_y + 15,
81            "ATMOS SOUND SYSTEM",
82            Color(config.theme.success_fg),
83            16,
84        );
85        screen.boxfill(
86            win_x + 15,
87            win_y + 35,
88            win_x + win_w - 15,
89            win_y + 36,
90            Color(config.theme.tab_area_border),
91        );
92
93        // 3. 曲リストの描画
94        screen.draw_string_vector(
95            win_x + 15,
96            win_y + 45,
97            "Select Song:",
98            Color(config.theme.fn_bar_dim_fg),
99            14,
100        );
101        let list_y = win_y + 65;
102        let line_h = 24u32;
103
104        for (i, song) in PRESET_SONGS.iter().enumerate() {
105            let y = list_y + i as u32 * line_h;
106            let is_selected = i == self.selected_song;
107
108            let bg_color = if is_selected {
109                Color(config.theme.button_active_bg)
110            } else {
111                Color(config.theme.ui_overlay_bg)
112            };
113            let fg_color = if is_selected {
114                Color(config.theme.button_active_fg)
115            } else {
116                Color(config.theme.terminal_fg)
117            };
118
119            screen.boxfill(win_x + 15, y, win_x + 240, y + line_h - 2, bg_color);
120            let label = alloc::format!("{}. {}", i + 1, song.title);
121            screen.draw_string_vector(win_x + 22, y + 4, &label, fg_color, 14);
122        }
123
124        // 4. ボタン描画 (Play / Stop)
125        let btn_y = win_y + 155;
126
127        // Play ボタン
128        screen.boxfill(
129            win_x + 15,
130            btn_y,
131            win_x + 120,
132            btn_y + 32,
133            Color(config.theme.success_fg),
134        );
135        screen.draw_string_vector(win_x + 48, btn_y + 8, "Play", Color(config.theme.browser_fg), 14);
136
137        // Stop ボタン
138        screen.boxfill(
139            win_x + 135,
140            btn_y,
141            win_x + 240,
142            btn_y + 32,
143            Color(config.theme.error_fg),
144        );
145        screen.draw_string_vector(win_x + 168, btn_y + 8, "Stop", Color(config.theme.browser_fg), 14);
146
147        // 5. リアルタイム・ビジュアルイコライザー描画
148        let eq_x0 = win_x + 265;
149        let eq_y0 = win_y + 55;
150        let eq_w = 150u32;
151        let eq_h = 135u32;
152
153        // イコライザーエリア背景
154        screen.boxfill(eq_x0, eq_y0, eq_x0 + eq_w, eq_y0 + eq_h, Color(config.theme.ui_overlay_bg));
155
156        let is_playing = Audio::is_playing();
157        let ticks = crate::kernel::timer::get_ticks();
158
159        // 5本のイコライザーバーの描画
160        let num_bars = 5;
161        let bar_width = 18u32;
162        let bar_gap = 8u32;
163
164        for bar in 0..num_bars {
165            let bx0 = eq_x0 + 15 + bar * (bar_width + bar_gap);
166            let bx1 = bx0 + bar_width;
167
168            // 再生中は ticks とハッシュミキサーを用いて自然かつ高速にバーを上下させる
169            let bar_height = if is_playing {
170                let seed = ticks.wrapping_add(bar as usize * 47);
171                let mut hash = seed ^ (seed >> 13);
172                hash = hash.wrapping_mul(0x85ebca6b);
173                hash ^= hash >> 15;
174                // 10px 〜 115px の間で変動
175                10 + (hash % 105) as u32
176            } else {
177                // 停止時はフラット (5px)
178                5
179            };
180
181            let by1 = eq_y0 + eq_h - 10;
182            let by0 = by1.saturating_sub(bar_height);
183
184            // バーの縦方向グラデーション描画 (プレミアム感の演出)
185            for row_y in by0..by1 {
186                // 上に行くほどシアン(水色)、下に行くほど緑
187                let ratio = (by1 - row_y) as f32 / eq_h as f32;
188                let r = (ratio * 16.0) as u32;
189                let g = (185.0 - ratio * 80.0) as u32;
190                let b = (129.0 + ratio * 120.0) as u32;
191                let grad_color = Color(0xFF000000 | (r << 16) | (g << 8) | b);
192
193                screen.boxfill(bx0, row_y, bx1, row_y + 1, grad_color);
194            }
195        }
196
197        // ステータス表示
198        let status_y = win_y + win_h - 26;
199        screen.boxfill(
200            win_x,
201            status_y,
202            win_x + win_w,
203            win_y + win_h,
204            Color(config.theme.ui_overlay_bg),
205        );
206
207        let status_text = if is_playing {
208            alloc::format!("Now Playing: {}", PRESET_SONGS[self.selected_song].title)
209        } else {
210            String::from("Status: Stopped")
211        };
212        screen.draw_string_vector(
213            win_x + 15,
214            status_y + 6,
215            &status_text,
216            Color(config.theme.fn_bar_dim_fg),
217            13,
218        );
219    }
220
221    fn on_mouse(
222        &mut self,
223        local_x: i32,
224        local_y: i32,
225        btn_left: bool,
226        _btn_right: bool,
227        _wheel: i32,
228    ) {
229        if !btn_left {
230            return;
231        }
232
233        // A. 曲選択の判定 (Y: 65..137付近)
234        let list_y = 65;
235        let line_h = 24;
236        if (15..=240).contains(&local_x) && local_y >= list_y {
237            let row = (local_y - list_y) / line_h;
238            if row >= 0 && row < PRESET_SONGS.len() as i32 {
239                self.selected_song = row as usize;
240                return;
241            }
242        }
243
244        // B. ボタン判定
245        let btn_y = 155;
246        // Play ボタン (X: 15..120, Y: 155..187)
247        if (15..=120).contains(&local_x) && local_y >= btn_y && local_y <= btn_y + 32 {
248            // 現在再生中のものを一旦停止
249            Audio::stop_mml();
250            // 選択された曲を再生
251            Audio::play_mml(PRESET_SONGS[self.selected_song].mml);
252            crate::info!(
253                "MusicPlayer: Started playing {}",
254                PRESET_SONGS[self.selected_song].title
255            );
256            return;
257        }
258
259        // Stop ボタン (X: 135..240, Y: 155..187)
260        if (135..=240).contains(&local_x) && local_y >= btn_y && local_y <= btn_y + 32 {
261            Audio::stop_mml();
262            crate::info!("[AUD] MusicPlayer: Music stopped.");
263        }
264    }
265
266    fn on_key(&mut self, _keycode: u8, _pressed: bool, _ascii: Option<char>) {
267        // 音楽プレイヤーは主にマウスクリックで操作するため、キーボード入力は無視
268    }
269}