Spaces:
Build error
Build error
"use client"; | |
import { useEffect, useRef, useState } from 'react'; | |
import MidiPlayer from 'midi-player-js'; | |
import Soundfont from 'soundfont-player'; | |
export default function HomePage() { | |
const audioCtxRef = useRef<AudioContext | null>(null); | |
const instrumentRef = useRef<any>(null); | |
const playerRef = useRef<any>(null); | |
const [soundfontLoading, setSoundfontLoading] = useState(true); | |
const [fileLoaded, setFileLoaded] = useState(false); | |
const [isPlaying, setIsPlaying] = useState(false); | |
const [tempo, setTempo] = useState(120); | |
const [fileFormat, setFileFormat] = useState<number | null>(null); | |
const [progress, setProgress] = useState(0); | |
useEffect(() => { | |
const AudioContext = window.AudioContext || (window as any).webkitAudioContext; | |
const audioCtx = new AudioContext(); | |
audioCtxRef.current = audioCtx; | |
Soundfont.instrument(audioCtx, 'acoustic_grand_piano').then(instrument => { | |
instrumentRef.current = instrument; | |
const player = new MidiPlayer.Player((event: any) => { | |
if (!instrumentRef.current) return; | |
if (event.name === 'Note on' && event.velocity > 0) { | |
instrumentRef.current.play(event.noteName, audioCtx.currentTime, { gain: 1 }); | |
} else if ((event.name === 'Note on' && event.velocity === 0) || event.name === 'Note off') { | |
instrumentRef.current.stop(event.noteName, audioCtx.currentTime); | |
} | |
}); | |
player.on('endOfFile', () => { | |
setIsPlaying(false); | |
setProgress(0); | |
}); | |
player.on('fileLoaded', () => { | |
try { | |
setFileFormat(player.getFormat()); | |
} catch { | |
setFileFormat(null); | |
} | |
setTempo(120); | |
setFileLoaded(true); | |
setProgress(0); | |
}); | |
playerRef.current = player; | |
setSoundfontLoading(false); | |
}); | |
return () => { | |
audioCtx.close(); | |
}; | |
}, []); | |
useEffect(() => { | |
if (!playerRef.current) return; | |
let interval: any; | |
if (isPlaying) { | |
interval = setInterval(() => { | |
const remaining = playerRef.current.getSongPercentRemaining(); | |
setProgress(100 - (remaining || 0)); | |
}, 200); | |
} | |
return () => clearInterval(interval); | |
}, [isPlaying]); | |
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
const file = e.target.files?.[0]; | |
if (file && playerRef.current) { | |
const reader = new FileReader(); | |
reader.onload = () => { | |
const arrayBuffer = reader.result; | |
if (!arrayBuffer) return; | |
playerRef.current.stop(); | |
playerRef.current.loadArrayBuffer(arrayBuffer); | |
}; | |
reader.readAsArrayBuffer(file); | |
} | |
}; | |
const handlePlayPause = () => { | |
if (isPlaying) { | |
playerRef.current.pause(); | |
} else { | |
playerRef.current.play(); | |
} | |
setIsPlaying(!isPlaying); | |
}; | |
const handleStop = () => { | |
playerRef.current.stop(); | |
setIsPlaying(false); | |
setProgress(0); | |
}; | |
const handleTempoChange = (newTempo: number) => { | |
playerRef.current.pause(); | |
playerRef.current.setTempo(newTempo); | |
playerRef.current.play(); | |
setTempo(newTempo); | |
setIsPlaying(true); | |
}; | |
return ( | |
<main className="max-w-xl mx-auto p-4"> | |
<h1 className="text-3xl font-bold">♬ MidiPlayerJS</h1> | |
{soundfontLoading ? "Loading soundfont..." : ( | |
<input type="file" accept=".mid,.midi" onChange={handleFileChange} /> | |
)} | |
<button onClick={handlePlayPause} disabled={!fileLoaded}> | |
{isPlaying ? "Pause" : "Play"} | |
</button> | |
<button onClick={handleStop}>Stop</button> | |
<input type="range" min="50" max="200" value={tempo} onChange={(e) => handleTempoChange(Number(e.target.value))} /> | |
<div>MIDI Format: {fileFormat}</div> | |
<div>Progress: {progress}%</div> | |
</main> | |
); | |
} | |