peter288 commited on
Commit
c911c4d
·
verified ·
1 Parent(s): 135fb97

Upload 10 files

Browse files
Files changed (10) hide show
  1. .dockerignore +5 -0
  2. Dockerfile +13 -0
  3. app/globals.css +7 -0
  4. app/layout.tsx +17 -0
  5. app/page.tsx +125 -0
  6. next.config.js +7 -0
  7. package.json +24 -0
  8. postcss.config.js +7 -0
  9. tailwind.config.js +11 -0
  10. tsconfig.json +26 -0
.dockerignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ node_modules
2
+ .next
3
+ .git
4
+ .gitignore
5
+ README.md
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package.json package-lock.json* ./
6
+ RUN npm install
7
+
8
+ COPY . .
9
+
10
+ RUN npm run build
11
+
12
+ EXPOSE 7860
13
+ CMD ["npm", "start"]
app/globals.css ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ html, body {
6
+ @apply bg-gray-50;
7
+ }
app/layout.tsx ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const metadata = {
2
+ title: "MidiPlayer Pro",
3
+ description: "Next.js MIDI player using MidiPlayerJS and Soundfont-player"
4
+ }
5
+
6
+ export default function RootLayout({
7
+ children,
8
+ }: {
9
+ children: React.ReactNode
10
+ }) {
11
+ return (
12
+ <html lang="en">
13
+ <body>{children}</body>
14
+ </html>
15
+ )
16
+ }
17
+
app/page.tsx ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import MidiPlayer from 'midi-player-js';
5
+ import Soundfont from 'soundfont-player';
6
+
7
+ export default function HomePage() {
8
+ const audioCtxRef = useRef<AudioContext | null>(null);
9
+ const instrumentRef = useRef<any>(null);
10
+ const playerRef = useRef<any>(null);
11
+
12
+ const [soundfontLoading, setSoundfontLoading] = useState(true);
13
+ const [fileLoaded, setFileLoaded] = useState(false);
14
+ const [isPlaying, setIsPlaying] = useState(false);
15
+ const [tempo, setTempo] = useState(120);
16
+ const [fileFormat, setFileFormat] = useState<number | null>(null);
17
+ const [progress, setProgress] = useState(0);
18
+
19
+ useEffect(() => {
20
+ const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
21
+ const audioCtx = new AudioContext();
22
+ audioCtxRef.current = audioCtx;
23
+
24
+ Soundfont.instrument(audioCtx, 'acoustic_grand_piano').then(instrument => {
25
+ instrumentRef.current = instrument;
26
+
27
+ const player = new MidiPlayer.Player((event: any) => {
28
+ if (!instrumentRef.current) return;
29
+ if (event.name === 'Note on' && event.velocity > 0) {
30
+ instrumentRef.current.play(event.noteName, audioCtx.currentTime, { gain: 1 });
31
+ } else if ((event.name === 'Note on' && event.velocity === 0) || event.name === 'Note off') {
32
+ instrumentRef.current.stop(event.noteName, audioCtx.currentTime);
33
+ }
34
+ });
35
+
36
+ player.on('endOfFile', () => {
37
+ setIsPlaying(false);
38
+ setProgress(0);
39
+ });
40
+
41
+ player.on('fileLoaded', () => {
42
+ try {
43
+ setFileFormat(player.getFormat());
44
+ } catch {
45
+ setFileFormat(null);
46
+ }
47
+ setTempo(120);
48
+ setFileLoaded(true);
49
+ setProgress(0);
50
+ });
51
+
52
+ playerRef.current = player;
53
+ setSoundfontLoading(false);
54
+ });
55
+
56
+ return () => {
57
+ audioCtx.close();
58
+ };
59
+ }, []);
60
+
61
+ useEffect(() => {
62
+ if (!playerRef.current) return;
63
+ let interval: any;
64
+ if (isPlaying) {
65
+ interval = setInterval(() => {
66
+ const remaining = playerRef.current.getSongPercentRemaining();
67
+ setProgress(100 - (remaining || 0));
68
+ }, 200);
69
+ }
70
+ return () => clearInterval(interval);
71
+ }, [isPlaying]);
72
+
73
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
74
+ const file = e.target.files?.[0];
75
+ if (file && playerRef.current) {
76
+ const reader = new FileReader();
77
+ reader.onload = () => {
78
+ const arrayBuffer = reader.result;
79
+ if (!arrayBuffer) return;
80
+ playerRef.current.stop();
81
+ playerRef.current.loadArrayBuffer(arrayBuffer);
82
+ };
83
+ reader.readAsArrayBuffer(file);
84
+ }
85
+ };
86
+
87
+ const handlePlayPause = () => {
88
+ if (isPlaying) {
89
+ playerRef.current.pause();
90
+ } else {
91
+ playerRef.current.play();
92
+ }
93
+ setIsPlaying(!isPlaying);
94
+ };
95
+
96
+ const handleStop = () => {
97
+ playerRef.current.stop();
98
+ setIsPlaying(false);
99
+ setProgress(0);
100
+ };
101
+
102
+ const handleTempoChange = (newTempo: number) => {
103
+ playerRef.current.pause();
104
+ playerRef.current.setTempo(newTempo);
105
+ playerRef.current.play();
106
+ setTempo(newTempo);
107
+ setIsPlaying(true);
108
+ };
109
+
110
+ return (
111
+ <main className="max-w-xl mx-auto p-4">
112
+ <h1 className="text-3xl font-bold">♬ MidiPlayerJS</h1>
113
+ {soundfontLoading ? "Loading soundfont..." : (
114
+ <input type="file" accept=".mid,.midi" onChange={handleFileChange} />
115
+ )}
116
+ <button onClick={handlePlayPause} disabled={!fileLoaded}>
117
+ {isPlaying ? "Pause" : "Play"}
118
+ </button>
119
+ <button onClick={handleStop}>Stop</button>
120
+ <input type="range" min="50" max="200" value={tempo} onChange={(e) => handleTempoChange(Number(e.target.value))} />
121
+ <div>MIDI Format: {fileFormat}</div>
122
+ <div>Progress: {progress}%</div>
123
+ </main>
124
+ );
125
+ }
next.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true
4
+ }
5
+
6
+ module.exports = nextConfig;
7
+
package.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "midiplayer-pro-hf",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start -p 7860"
9
+ },
10
+ "dependencies": {
11
+ "next": "13.4.6",
12
+ "react": "18.2.0",
13
+ "react-dom": "18.2.0",
14
+ "midi-player-js": "^1.1.5",
15
+ "soundfont-player": "^0.8.5"
16
+ },
17
+ "devDependencies": {
18
+ "typescript": "5.0.4",
19
+ "autoprefixer": "10.4.14",
20
+ "postcss": "8.4.24",
21
+ "tailwindcss": "3.3.2"
22
+ }
23
+ }
24
+
postcss.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {}
5
+ }
6
+ }
7
+
tailwind.config.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ "./app/**/*.{js,ts,jsx,tsx}"
5
+ ],
6
+ theme: {
7
+ extend: {}
8
+ },
9
+ plugins: []
10
+ }
11
+
tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "incremental": true,
17
+ "baseUrl": ".",
18
+ "paths": {
19
+ "@/*": ["./*"]
20
+ },
21
+ "plugins": [{ "name": "next" }]
22
+ },
23
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
24
+ "exclude": ["node_modules"]
25
+ }
26
+