File size: 3,952 Bytes
c911c4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"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>
  );
}