peter288's picture
Upload 5 files
64ddb35 verified
"use client";
import { useEffect, useRef, useState } from 'react';
import MidiPlayer from 'midi-player-js';
import Soundfont from 'soundfont-player';
// 這是一段 C 大調音階的 MIDI 檔案,使用 Base64 編碼寫死在程式碼中
// 這樣就不需要使用者上傳檔案了
const C_MAJOR_SCALE_BASE64 = 'data:audio/midi;base64,TVRoZAAAAAYAAQABAIxNVHJrAAAAGwD/BAcCBQNkgEN/AP8vAE1UcmsAAAAqAP8DBFNrYWxhAP8EDgIFA2SAQ38AkEIAAACQQwAAAJBFAACQSQAAAP8vAA==';
export default function Home() {
const [isReady, setIsReady] = useState(false);
const player = useRef<MidiPlayer.Player | null>(null);
useEffect(() => {
// 整個初始化過程都在 useEffect 中,確保只在瀏覽器端執行
const audioContext = new AudioContext();
// 1. 載入樂器 (Soundfont)
Soundfont.instrument(audioContext, 'acoustic_grand_piano').then(instrument => {
// 2. 初始化 MIDI 播放器
player.current = new MidiPlayer.Player(event => {
if (event.name === 'Note on' && event.velocity > 0) {
instrument.play(event.noteName);
}
});
// 3. 載入我們寫死的 MIDI 資料
player.current.loadDataUri(C_MAJOR_SCALE_BASE64);
// 4. 一切準備就緒,更新 UI
setIsReady(true);
});
// 元件卸載時清理 AudioContext
return () => {
if (audioContext.state !== 'closed') {
audioContext.close();
}
};
}, []); // 空依賴陣列確保這個 effect 只執行一次
const handlePlay = () => {
player.current?.play();
};
return (
<main style={{ fontFamily: 'sans-serif', textAlign: 'center', paddingTop: '5rem' }}>
<h1>MIDI Player: Hello World!</h1>
<p>{isReady ? '播放器已就緒!' : '正在載入樂器...'}</p>
<button
onClick={handlePlay}
disabled={!isReady}
style={{ fontSize: '1.2rem', padding: '10px 20px', cursor: 'pointer' }}
>
🎵 播放 "Hello World" 音階
</button>
</main>
);
}