Skip to main content

atmos/kernel/
audio.rs

1// audio.rs - AtmOS PWM Audio Driver & MML Playback Engine
2#![allow(dead_code)]
3
4extern crate alloc;
5use crate::os_lib::mml::Note;
6use alloc::vec::Vec;
7use spin::Mutex;
8
9const GPIO_BASE: usize = 0x3F20_0000;
10const GPFSEL4: *mut u32 = (GPIO_BASE + 0x10) as *mut u32;
11
12const CM_BASE: usize = 0x3F10_1000;
13const CM_PWMCTL: *mut u32 = (CM_BASE + 0x10A0) as *mut u32;
14const CM_PWMDIV: *mut u32 = (CM_BASE + 0x10A4) as *mut u32;
15
16const PWM_BASE: usize = 0x3F20_C000;
17const PWM_CTL: *mut u32 = PWM_BASE as *mut u32;
18const PWM_STA: *mut u32 = (PWM_BASE + 0x04) as *mut u32;
19const PWM_DMAC: *mut u32 = (PWM_BASE + 0x08) as *mut u32;
20const PWM_RNG1: *mut u32 = (PWM_BASE + 0x10) as *mut u32;
21const PWM_DAT1: *mut u32 = (PWM_BASE + 0x14) as *mut u32;
22const PWM_FIF1: *mut u32 = (PWM_BASE + 0x18) as *mut u32;
23const PWM_RNG2: *mut u32 = (PWM_BASE + 0x20) as *mut u32;
24const PWM_DAT2: *mut u32 = (PWM_BASE + 0x24) as *mut u32;
25
26// I2S (HDMI Audio Output Destination) Registers
27const I2S_BASE: usize = 0x3F20_3000;
28const I2S_CS: *mut u32 = I2S_BASE as *mut u32;
29const I2S_FIFO: *mut u32 = (I2S_BASE + 0x04) as *mut u32;
30const I2S_MODE: *mut u32 = (I2S_BASE + 0x08) as *mut u32;
31const I2S_RXC: *mut u32 = (I2S_BASE + 0x0C) as *mut u32;
32const I2S_TXC: *mut u32 = (I2S_BASE + 0x10) as *mut u32;
33const I2S_DREQ: *mut u32 = (I2S_BASE + 0x14) as *mut u32;
34
35// DMA 3 (for PWM) and DMA 4 (for I2S) Registers
36const DMA3_BASE: usize = 0x3F00_7300;
37const DMA3_CS: *mut u32 = DMA3_BASE as *mut u32;
38const DMA3_CONBLK_AD: *mut u32 = (DMA3_BASE + 0x04) as *mut u32;
39
40const DMA4_BASE: usize = 0x3F00_7400;
41const DMA4_CS: *mut u32 = DMA4_BASE as *mut u32;
42const DMA4_CONBLK_AD: *mut u32 = (DMA4_BASE + 0x04) as *mut u32;
43
44// PWM CLOCK: 19.2MHz / 16 = 1.2MHz (1,200,000 Hz)
45const PWM_CLOCK_FREQ: u32 = 1_200_000;
46
47// 非同期演奏用グローバルステート
48static PLAY_QUEUE: Mutex<Vec<Note>> = Mutex::new(Vec::new());
49static PLAY_INDEX: Mutex<usize> = Mutex::new(0);
50static IS_PLAYING: Mutex<bool> = Mutex::new(false);
51static THREAD_STARTED: Mutex<bool> = Mutex::new(false);
52
53// PCMバッファと再生用静的領域
54static mut PCM_BUFFER_PWM: [u8; 65536] = [0; 65536];
55static mut PCM_BUFFER_I2S: [i16; 65536] = [0; 65536];
56static mut DMA_CB_PWM: crate::kernel::dma::DmaControlBlock = crate::kernel::dma::DmaControlBlock {
57    ti: 0,
58    source_ad: 0,
59    dest_ad: 0,
60    txfr_len: 0,
61    stride: 0,
62    nextconbk: 0,
63    _reserved: [0; 2],
64};
65static mut DMA_CB_I2S: crate::kernel::dma::DmaControlBlock = crate::kernel::dma::DmaControlBlock {
66    ti: 0,
67    source_ad: 0,
68    dest_ad: 0,
69    txfr_len: 0,
70    stride: 0,
71    nextconbk: 0,
72    _reserved: [0; 2],
73};
74
75pub struct Audio;
76
77impl Audio {
78    /// PWMおよびGPIO40/45の初期化処理、I2S(HDMI)の初期化、DMAの設定を行います
79    pub fn init() {
80        unsafe {
81            // 1. GPIO40 (PWM0) / GPIO45 (PWM1) のオルタネート機能を ALT0 (100) に設定
82            let mut val = core::ptr::read_volatile(GPFSEL4);
83            val &= !0b111; // GPIO 40
84            val |= 0b100; // ALT0 (PWM0)
85            val &= !(0b111 << 15); // GPIO 45
86            val |= 0b100 << 15; // ALT0 (PWM1)
87            core::ptr::write_volatile(GPFSEL4, val);
88
89            // 2. PWM クロックの初期化
90            // 停止させて設定をクリーンにする
91            core::ptr::write_volatile(CM_PWMCTL, 0x5A00_0000);
92            for _ in 0..1000 {
93                core::arch::asm!("nop");
94            }
95
96            // 分分周比 16 (19.2MHz / 16 = 1.2MHz)
97            core::ptr::write_volatile(CM_PWMDIV, 0x5A00_0000 | (16 << 12));
98            for _ in 0..1000 {
99                core::arch::asm!("nop");
100            }
101
102            // クロックを起動 (ENAB=0x10, SRC=0x01: OSC 19.2MHz)
103            core::ptr::write_volatile(CM_PWMCTL, 0x5A00_0010 | 0x01);
104            for _ in 0..1000 {
105                core::arch::asm!("nop");
106            }
107
108            // 3. PWM コントローラの初期化
109            // 一度全体をクリア
110            core::ptr::write_volatile(PWM_CTL, 0);
111            for _ in 0..1000 {
112                core::arch::asm!("nop");
113            }
114
115            // 周期の初期値を 1024 に
116            core::ptr::write_volatile(PWM_RNG1, 1024);
117            core::ptr::write_volatile(PWM_RNG2, 1024);
118
119            // 出力データを 0 (Mute) に
120            core::ptr::write_volatile(PWM_DAT1, 0);
121            core::ptr::write_volatile(PWM_DAT2, 0);
122
123            // Mark-Space Mode で両方のチャンネルを有効化
124            core::ptr::write_volatile(PWM_CTL, (1 << 0) | (1 << 1) | (1 << 8) | (1 << 9));
125
126            // 4. I2S (HDMI Audio) の初期化
127            // I2Sクロックおよびレジスタの初期設定
128            core::ptr::write_volatile(I2S_CS, 0); // Disable I2S first
129            core::ptr::write_volatile(I2S_MODE, 0);
130            // 16-bit stereo transmit control
131            core::ptr::write_volatile(I2S_TXC, (1 << 30) | (1 << 29) | (8 << 16) | (1 << 15)); // CH1EN | CH1WEX | CH1POS
132            core::ptr::write_volatile(I2S_DREQ, (8 << 24) | (8 << 8)); // TX DREQ, RX DREQ threshold
133            core::ptr::write_volatile(I2S_CS, 1 << 2); // TX Enable
134
135            // 5. DMA 3 / 4 のイネーブル
136            // DMAコントロール全体のENABLEレジスタ (0x3F007FF0)
137            let dma_en_ptr = 0x3F007FF0 as *mut u32;
138            let current_en = core::ptr::read_volatile(dma_en_ptr);
139            core::ptr::write_volatile(dma_en_ptr, current_en | (1 << 3) | (1 << 4));
140            // Enable DMA 3 and 4
141        }
142
143        crate::info!(
144            "[AUD] Hardware PWM (Analog) and I2S (HDMI) audio engines initialized successfully."
145        );
146    }
147
148    /// 常駐再生スレッドを必要なら起動する(プロセス全体で一度だけ spawn される)。
149    /// 以前は効果音ごとにスレッドを spawn→終了していたため、ESC 連打などで
150    /// 頻繁なスレッド生成・破棄が起き、スタック確保/解放でヒープ破損を招いていた。
151    /// Audio::init() は scheduler::init() より前に呼ばれるため、ここで遅延起動する。
152    fn ensure_playback_thread() {
153        let mut started = THREAD_STARTED.lock();
154        if !*started {
155            *started = true;
156            // 【2026-08-25 バグ修正】`spawn` はヒープ不足で **0 を返す**。
157            // `started` を先に立てているので、失敗したまま戻さないと
158            // 「再生スレッドは居ないのに起動済み」になり、
159            // **以後まったく音が鳴らなくなる**(再試行の機会が二度と来ない)。
160            let pid = crate::kernel::scheduler::spawn(
161                Self::playback_thread_entry,
162                crate::kernel::scheduler::Priority::High,
163                "mml_player",
164            );
165            if pid == 0 {
166                crate::warn!("[AUDIO] 再生スレッドを起動できません(ヒープ不足)");
167                *started = false;
168            }
169        }
170    }
171
172    /// 指定された周波数 (Hz) で自律発振を開始します (0 の場合は Mute)
173    pub fn set_tone(freq: u32) {
174        if freq == 0 {
175            Self::mute();
176            return;
177        }
178
179        let rng = PWM_CLOCK_FREQ / freq;
180        let dat = rng / 2; // デューティ比 50%
181
182        unsafe {
183            core::ptr::write_volatile(PWM_RNG1, rng);
184            core::ptr::write_volatile(PWM_RNG2, rng);
185            core::ptr::write_volatile(PWM_DAT1, dat);
186            core::ptr::write_volatile(PWM_DAT2, dat);
187        }
188    }
189
190    /// 音声を消音します
191    pub fn mute() {
192        unsafe {
193            core::ptr::write_volatile(PWM_DAT1, 0);
194            core::ptr::write_volatile(PWM_DAT2, 0);
195        }
196    }
197
198    /// MML 文字列をバックグラウンド(ノンブロッキング)で演奏開始します
199    pub fn play_mml(mml_str: &str) {
200        let notes = crate::os_lib::mml::parse_mml(mml_str);
201        if notes.is_empty() {
202            return;
203        }
204
205        Self::ensure_playback_thread();
206        {
207            let mut queue = PLAY_QUEUE.lock();
208            let mut idx = PLAY_INDEX.lock();
209            let mut playing = IS_PLAYING.lock();
210
211            // 常駐再生スレッドにキューを渡すだけ(スレッドは spawn しない)
212            *queue = notes;
213            *idx = 0;
214            *playing = true;
215        }
216    }
217
218    /// 現在MMLを演奏中かどうかを判定します
219    pub fn is_playing() -> bool {
220        *IS_PLAYING.lock()
221    }
222
223    /// MML演奏を強制停止します
224    pub fn stop_mml() {
225        let mut queue = PLAY_QUEUE.lock();
226        let mut idx = PLAY_INDEX.lock();
227        queue.clear();
228        *idx = 0;
229        Self::mute();
230    }
231
232    /// 単音をバックグラウンド(ノンブロッキング)で指定ミリ秒間演奏します
233    pub fn play_tone_async(freq: u32, duration_ms: u32) {
234        let notes = alloc::vec![Note { freq, duration_ms }];
235
236        Self::ensure_playback_thread();
237        {
238            let mut queue = PLAY_QUEUE.lock();
239            let mut idx = PLAY_INDEX.lock();
240            let mut playing = IS_PLAYING.lock();
241
242            // 常駐再生スレッドにキューを渡すだけ(スレッドは spawn しない)
243            *queue = notes;
244            *idx = 0;
245            *playing = true;
246        }
247    }
248
249    /// 再生スレッドの実行エントリポイント
250    fn playback_thread_entry() {
251        loop {
252            let note_opt = {
253                let mut queue = PLAY_QUEUE.lock();
254                let mut idx = PLAY_INDEX.lock();
255                if *idx < queue.len() {
256                    let note = queue[*idx];
257                    *idx += 1;
258                    Some(note)
259                } else {
260                    // 全ての音符の再生が終了
261                    queue.clear();
262                    *idx = 0;
263                    let mut playing = IS_PLAYING.lock();
264                    *playing = false;
265                    None
266                }
267            };
268
269            if let Some(note) = note_opt {
270                if note.freq == 0 {
271                    Self::mute();
272                } else {
273                    Self::set_tone(note.freq);
274                }
275
276                // tick 単位 (10ms) に変換
277                let ticks = (note.duration_ms / 10) as usize;
278                if ticks > 0 {
279                    crate::kernel::scheduler::sleep(ticks);
280                } else {
281                    crate::kernel::scheduler::sleep(1);
282                }
283
284                // 同音の連続を綺麗に分離するため、一瞬消音する (10ms)
285                Self::mute();
286                crate::kernel::scheduler::sleep(1);
287            } else {
288                // キューが空の間はアイドル待機(スレッドは終了せず常駐し続ける)
289                crate::kernel::scheduler::sleep(2);
290            }
291        }
292    }
293
294    /// デコードされた 16-bit PCM ステレオ音声データを、HDMI (I2S) と PWM (イヤホンジャック) に供給してDMA再生します
295    pub fn play_pcm_stream(pcm_data: &[i16]) {
296        if pcm_data.is_empty() {
297            return;
298        }
299
300        unsafe {
301            // 静的バッファサイズに合わせてコピー(サイズ上限 65536)
302            let len = pcm_data.len().min(65536);
303
304            // 1. HDMI (I2S) 用バッファの構築
305            core::ptr::copy_nonoverlapping(pcm_data.as_ptr(), PCM_BUFFER_I2S.as_mut_ptr(), len);
306
307            // 2. PWM (アナログ) 用バッファの構築 (16-bit ステレオ -> 8-bit モノラル/ステレオ変換)
308            for i in 0..len {
309                // -32768..32767 を 0..255 にマッピング
310                let sample_8 = (((pcm_data[i] as i32 + 32768) >> 8) & 0xFF) as u8;
311                // PWMの出力範囲 (RNG=1024) に合わせてスケーリング (0..255 を 0..1023 へ)
312                PCM_BUFFER_PWM[i] = ((sample_8 as u32 * 4).min(1023)) as u8;
313            }
314
315            // 3. DMA コントロールブロックの設定 (キャッシュクリーン)
316            let pwm_buf_ptr = PCM_BUFFER_PWM.as_ptr() as usize;
317            let i2s_buf_ptr = PCM_BUFFER_I2S.as_ptr() as usize;
318
319            crate::kernel::mmu::clean_dcache_range(pwm_buf_ptr, len);
320            crate::kernel::mmu::clean_dcache_range(i2s_buf_ptr, len * 2);
321
322            // 4. DMA 3 (PWM) 転送ブロックの構築
323            DMA_CB_PWM = crate::kernel::dma::DmaControlBlock {
324                ti: (5 << 16) | (1 << 8) | (1 << 4) | (1 << 3), // PERMAP=5 (PWM), SRC_INC, DEST_DREQ, WAIT_RESP
325                source_ad: crate::kernel::dma::to_bus_address(pwm_buf_ptr as u32),
326                dest_ad: crate::kernel::dma::to_bus_address(PWM_FIF1 as u32),
327                txfr_len: len as u32,
328                stride: 0,
329                nextconbk: 0,
330                _reserved: [0; 2],
331            };
332            crate::kernel::mmu::clean_dcache_range(
333                &DMA_CB_PWM as *const _ as usize,
334                core::mem::size_of::<crate::kernel::dma::DmaControlBlock>(),
335            );
336
337            // 5. DMA 4 (I2S / HDMI) 転送ブロックの構築
338            DMA_CB_I2S = crate::kernel::dma::DmaControlBlock {
339                ti: (6 << 16) | (1 << 8) | (1 << 4) | (1 << 3), // PERMAP=6 (I2S), SRC_INC, DEST_DREQ, WAIT_RESP
340                source_ad: crate::kernel::dma::to_bus_address(i2s_buf_ptr as u32),
341                dest_ad: crate::kernel::dma::to_bus_address(I2S_FIFO as u32),
342                txfr_len: (len * 2) as u32,
343                stride: 0,
344                nextconbk: 0,
345                _reserved: [0; 2],
346            };
347            crate::kernel::mmu::clean_dcache_range(
348                &DMA_CB_I2S as *const _ as usize,
349                core::mem::size_of::<crate::kernel::dma::DmaControlBlock>(),
350            );
351
352            if !crate::kernel::dma::is_qemu_env() {
353                // 実機の場合: DMAレジスタを設定して転送開始
354                // DMA 3 (PWM)
355                core::ptr::write_volatile(
356                    DMA3_CONBLK_AD,
357                    crate::kernel::dma::to_bus_address(&DMA_CB_PWM as *const _ as *const _ as u32),
358                );
359                core::ptr::write_volatile(DMA3_CS, (1 << 0) | (8 << 16)); // ACTIVE | PRIORITY=8
360
361                // DMA 4 (I2S / HDMI)
362                core::ptr::write_volatile(
363                    DMA4_CONBLK_AD,
364                    crate::kernel::dma::to_bus_address(&DMA_CB_I2S as *const _ as *const _ as u32),
365                );
366                core::ptr::write_volatile(DMA4_CS, (1 << 0) | (8 << 16)); // ACTIVE | PRIORITY=8
367            }
368        }
369    }
370}
371
372#[derive(Clone, Copy, Debug)]
373pub enum SeType {
374    Apply,  // 設定適用時のきらびやかなチャイム
375    Save,   // 保存時の安心なピピッという完了音
376    Delete, // 削除時の少し低めのドシュッという音
377    Close,  // ウィンドウを閉じる時のフェード音
378}
379
380/// システム効果音を再生します。サウンド設定がOFFの場合は完全に静音化されます。
381pub fn play_system_se(se: SeType) {
382    // グローバルサウンド設定の確認
383    if !crate::kernel::config::get_config().enable_sound {
384        return;
385    }
386
387    let mml = match se {
388        SeType::Apply => "T180 L16 O5 C E G",
389        SeType::Save => "T240 L16 O5 G O6 C",
390        SeType::Delete => "T150 L12 O4 C O3 G",
391        SeType::Close => "T240 L16 O5 F D",
392    };
393
394    Audio::play_mml(mml);
395}