Spaces:
Build error
Build error
Upload 10 files
Browse files- .dockerignore +5 -0
- Dockerfile +13 -0
- app/globals.css +7 -0
- app/layout.tsx +17 -0
- app/page.tsx +125 -0
- next.config.js +7 -0
- package.json +24 -0
- postcss.config.js +7 -0
- tailwind.config.js +11 -0
- 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 |
+
|