Spaces:
Sleeping
Sleeping
File size: 2,094 Bytes
64ddb35 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
"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>
);
} |