testing-web / index.html
NeoPy's picture
Add 3 files
ab2d87f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon MIDI Player</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/Tone.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@magenta/[email protected]/es6/core.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@magenta/[email protected]/es6/midi_visualizer.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600;700&display=swap');
:root {
--primary: #6366f1;
--primary-dark: #4f46e5;
--secondary: #f43f5e;
--dark: #1e293b;
--light: #f8fafc;
}
body {
font-family: 'Montserrat', sans-serif;
background-color: #0f172a;
color: var(--light);
}
.glass-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.2);
}
.neon-text {
text-shadow: 0 0 5px var(--primary), 0 0 10px var(--primary);
}
.piano-roll {
height: 300px;
overflow-y: auto;
background-color: rgba(15, 23, 42, 0.7);
}
.progress-bar {
height: 6px;
background: rgba(255, 255, 255, 0.1);
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--secondary));
transition: width 0.1s linear;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
}
.btn-secondary {
background: linear-gradient(135deg, var(--secondary), #e11d48);
transition: all 0.3s ease;
}
.btn-secondary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(244, 63, 94, 0.4);
}
.keyboard {
display: flex;
height: 120px;
margin-top: 20px;
}
.white-key {
flex: 1;
background: white;
border: 1px solid #ccc;
border-radius: 0 0 5px 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 1;
position: relative;
}
.black-key {
width: 0.6;
height: 60%;
background: #333;
margin-left: -0.3;
margin-right: -0.3;
z-index: 2;
position: relative;
border-radius: 0 0 3px 3px;
}
.active-key {
background: linear-gradient(180deg, var(--primary), var(--secondary));
}
.visualizer {
width: 100%;
height: 100%;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
}
::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 4px;
}
/* Animation for loading */
@keyframes pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
.pulse {
animation: pulse 1.5s infinite;
}
</style>
</head>
<body class="min-h-screen flex flex-col">
<div class="container mx-auto px-4 py-8 flex-1">
<header class="text-center mb-10">
<h1 class="text-4xl md:text-5xl font-bold mb-2 neon-text">Neon MIDI Player</h1>
<p class="text-lg text-gray-300">Play MIDI files with custom SoundFonts</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Controls Section -->
<div class="glass-card p-6 lg:col-span-1">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-sliders-h mr-2"></i> Controls
</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">MIDI File</label>
<input type="file" id="midi-file" accept=".mid,.midi" class="hidden">
<button id="load-midi-btn" class="w-full btn-primary text-white py-2 px-4 rounded-lg flex items-center justify-center">
<i class="fas fa-file-import mr-2"></i> Load MIDI File
</button>
</div>
<div>
<label class="block text-sm font-medium mb-1">SoundFont</label>
<select id="soundfont-select" class="w-full bg-gray-800 border border-gray-700 rounded-lg py-2 px-3 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="acoustic_grand_piano">Acoustic Grand Piano</option>
<option value="electric_piano_1">Electric Piano 1</option>
<option value="electric_piano_2">Electric Piano 2</option>
<option value="honkytonk_piano">Honky-tonk Piano</option>
<option value="electric_guitar_clean">Electric Guitar (Clean)</option>
<option value="electric_guitar_jazz">Electric Guitar (Jazz)</option>
<option value="electric_bass_finger">Electric Bass (Finger)</option>
<option value="synth_bass_1">Synth Bass 1</option>
<option value="violin">Violin</option>
<option value="cello">Cello</option>
</select>
</div>
<div class="grid grid-cols-3 gap-2">
<button id="play-btn" class="btn-primary text-white py-2 px-4 rounded-lg flex items-center justify-center">
<i class="fas fa-play mr-2"></i> Play
</button>
<button id="pause-btn" class="bg-gray-700 text-white py-2 px-4 rounded-lg flex items-center justify-center">
<i class="fas fa-pause mr-2"></i> Pause
</button>
<button id="stop-btn" class="btn-secondary text-white py-2 px-4 rounded-lg flex items-center justify-center">
<i class="fas fa-stop mr-2"></i> Stop
</button>
</div>
<div>
<label class="block text-sm font-medium mb-1">Volume</label>
<input type="range" id="volume-slider" min="0" max="1" step="0.01" value="0.8" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
</div>
<div>
<label class="block text-sm font-medium mb-1">Tempo</label>
<input type="range" id="tempo-slider" min="20" max="200" step="1" value="120" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-400">
<span>Slow</span>
<span id="tempo-value">120 BPM</span>
<span>Fast</span>
</div>
</div>
<div class="pt-4 border-t border-gray-700">
<div class="flex justify-between text-sm mb-1">
<span>Status:</span>
<span id="status-text" class="text-indigo-300">Ready</span>
</div>
<div class="progress-bar rounded-full">
<div id="progress-fill" class="progress-fill rounded-full" style="width: 0%"></div>
</div>
<div class="flex justify-between text-xs text-gray-400 mt-1">
<span id="current-time">0:00</span>
<span id="total-time">0:00</span>
</div>
</div>
</div>
</div>
<!-- Visualizer Section -->
<div class="glass-card p-6 lg:col-span-2">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-music mr-2"></i> Visualizer
</h2>
<div class="piano-roll glass-card p-4 mb-4">
<div id="visualizer" class="visualizer">
<div class="flex items-center justify-center h-full text-gray-500">
<div class="text-center">
<i class="fas fa-music text-4xl mb-2"></i>
<p>No MIDI file loaded</p>
</div>
</div>
</div>
</div>
<div class="keyboard hidden" id="keyboard-preview">
<!-- White keys -->
<div class="white-key" data-note="C"></div>
<div class="white-key" data-note="D"></div>
<div class="white-key" data-note="E"></div>
<div class="white-key" data-note="F"></div>
<div class="white-key" data-note="G"></div>
<div class="white-key" data-note="A"></div>
<div class="white-key" data-note="B"></div>
<!-- Black keys -->
<div class="black-key" data-note="C#"></div>
<div class="black-key" data-note="D#"></div>
<div class="black-key" data-note="F#"></div>
<div class="black-key" data-note="G#"></div>
<div class="black-key" data-note="A#"></div>
</div>
</div>
</div>
</div>
<footer class="text-center py-4 text-gray-400 text-sm">
<p>Neon MIDI Player &copy; 2023 | Powered by Tone.js and Magenta.js</p>
</footer>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Initialize Tone.js
Tone.start();
// Elements
const loadMidiBtn = document.getElementById('load-midi-btn');
const midiFileInput = document.getElementById('midi-file');
const soundfontSelect = document.getElementById('soundfont-select');
const playBtn = document.getElementById('play-btn');
const pauseBtn = document.getElementById('pause-btn');
const stopBtn = document.getElementById('stop-btn');
const volumeSlider = document.getElementById('volume-slider');
const tempoSlider = document.getElementById('tempo-slider');
const tempoValue = document.getElementById('tempo-value');
const statusText = document.getElementById('status-text');
const progressFill = document.getElementById('progress-fill');
const currentTime = document.getElementById('current-time');
const totalTime = document.getElementById('total-time');
const visualizer = document.getElementById('visualizer');
const keyboardPreview = document.getElementById('keyboard-preview');
// Variables
let player;
let midiData;
let sequence;
let isPlaying = false;
let currentSoundfont = 'acoustic_grand_piano';
let visualizerSVG;
// Initialize player
async function initPlayer() {
player = new mm.SoundFontPlayer(
'https://storage.googleapis.com/magentadata/js/soundfonts/sgm_plus',
undefined,
undefined,
undefined,
Tone.context
);
await player.loadSoundFont(currentSoundfont);
player.setVolume(volumeSlider.value);
// Set up callbacks
player.onLoad = () => {
statusText.textContent = 'Ready';
statusText.classList.remove('text-indigo-300');
statusText.classList.add('text-green-400');
};
player.onError = (error) => {
statusText.textContent = `Error: ${error}`;
statusText.classList.remove('text-indigo-300');
statusText.classList.add('text-red-400');
};
player.onPlaybackStarted = () => {
isPlaying = true;
statusText.textContent = 'Playing';
statusText.classList.remove('text-indigo-300');
statusText.classList.add('text-green-400');
updatePlaybackControls();
};
player.onPlaybackPaused = () => {
isPlaying = false;
statusText.textContent = 'Paused';
statusText.classList.remove('text-green-400');
statusText.classList.add('text-yellow-400');
updatePlaybackControls();
};
player.onPlaybackStopped = () => {
isPlaying = false;
statusText.textContent = 'Stopped';
statusText.classList.remove('text-green-400');
statusText.classList.add('text-indigo-300');
progressFill.style.width = '0%';
currentTime.textContent = '0:00';
updatePlaybackControls();
};
player.onPlaybackProgress = (progress) => {
progressFill.style.width = `${progress * 100}%`;
if (player.currentTime && player.totalTime) {
currentTime.textContent = formatTime(player.currentTime);
totalTime.textContent = formatTime(player.totalTime);
}
};
}
// Initialize visualizer
function initVisualizer() {
visualizerSVG = new mm.PianoRollSVGVisualizer(
null,
visualizer,
{
noteHeight: 8,
pixelsPerTimeStep: 30,
noteSpacing: 1,
noteRGB: '125, 211, 252',
activeNoteRGB: '236, 72, 153',
minPitch: 21,
maxPitch: 108
}
);
}
// Format time (seconds to MM:SS)
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
}
// Update playback controls
function updatePlaybackControls() {
if (isPlaying) {
playBtn.disabled = true;
playBtn.classList.remove('btn-primary');
playBtn.classList.add('bg-gray-600');
pauseBtn.disabled = false;
pauseBtn.classList.remove('bg-gray-700');
pauseBtn.classList.add('btn-secondary');
stopBtn.disabled = false;
} else {
playBtn.disabled = !midiData;
playBtn.classList.add('btn-primary');
playBtn.classList.remove('bg-gray-600');
pauseBtn.disabled = true;
pauseBtn.classList.add('bg-gray-700');
pauseBtn.classList.remove('btn-secondary');
stopBtn.disabled = !midiData;
}
}
// Load MIDI file
loadMidiBtn.addEventListener('click', () => {
midiFileInput.click();
});
midiFileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
statusText.textContent = 'Loading MIDI...';
statusText.classList.add('pulse');
try {
const arrayBuffer = await file.arrayBuffer();
midiData = new mm.MidiFile(arrayBuffer);
// Update visualizer
visualizerSVG = new mm.PianoRollSVGVisualizer(
midiData,
visualizer,
{
noteHeight: 8,
pixelsPerTimeStep: 30,
noteSpacing: 1,
noteRGB: '125, 211, 252',
activeNoteRGB: '236, 72, 153',
minPitch: 21,
maxPitch: 108
}
);
// Calculate total time
const totalSeconds = midiData.totalTime;
totalTime.textContent = formatTime(totalSeconds);
// Show keyboard preview
keyboardPreview.classList.remove('hidden');
statusText.textContent = 'Ready';
statusText.classList.remove('pulse');
updatePlaybackControls();
} catch (error) {
console.error('Error loading MIDI:', error);
statusText.textContent = `Error: ${error.message}`;
statusText.classList.remove('pulse');
statusText.classList.add('text-red-400');
}
});
// Play MIDI
playBtn.addEventListener('click', () => {
if (!midiData) return;
player.loadSamples(midiData).then(() => {
player.start(midiData);
// Update visualizer to follow playback
if (visualizerSVG) {
visualizerSVG = new mm.PianoRollSVGVisualizer(
midiData,
visualizer,
{
noteHeight: 8,
pixelsPerTimeStep: 30,
noteSpacing: 1,
noteRGB: '125, 211, 252',
activeNoteRGB: '236, 72, 153',
minPitch: 21,
maxPitch: 108
}
);
visualizerSVG.redraw(player.currentTime);
}
});
});
// Pause MIDI
pauseBtn.addEventListener('click', () => {
if (!isPlaying) return;
player.pause();
});
// Stop MIDI
stopBtn.addEventListener('click', () => {
if (!isPlaying) return;
player.stop();
});
// Change volume
volumeSlider.addEventListener('input', () => {
if (player) {
player.setVolume(volumeSlider.value);
}
});
// Change tempo
tempoSlider.addEventListener('input', () => {
const tempo = parseInt(tempoSlider.value);
tempoValue.textContent = `${tempo} BPM`;
if (player) {
player.setTempo(tempo);
}
});
// Change soundfont
soundfontSelect.addEventListener('change', async () => {
currentSoundfont = soundfontSelect.value;
statusText.textContent = 'Loading SoundFont...';
statusText.classList.add('pulse');
try {
await player.loadSoundFont(currentSoundfont);
statusText.textContent = 'Ready';
statusText.classList.remove('pulse');
} catch (error) {
console.error('Error loading SoundFont:', error);
statusText.textContent = `Error: ${error.message}`;
statusText.classList.remove('pulse');
statusText.classList.add('text-red-400');
}
});
// Keyboard preview interaction
keyboardPreview.addEventListener('mousedown', (e) => {
if (e.target.classList.contains('white-key') || e.target.classList.contains('black-key')) {
e.target.classList.add('active-key');
// Play the note
const note = e.target.dataset.note;
const synth = new Tone.Synth().toDestination();
synth.triggerAttackRelease(`${note}4`, "8n");
}
});
keyboardPreview.addEventListener('mouseup', (e) => {
if (e.target.classList.contains('white-key') || e.target.classList.contains('black-key')) {
e.target.classList.remove('active-key');
}
});
keyboardPreview.addEventListener('mouseleave', () => {
const activeKeys = keyboardPreview.querySelectorAll('.active-key');
activeKeys.forEach(key => key.classList.remove('active-key'));
});
// Initialize everything
initPlayer();
initVisualizer();
updatePlaybackControls();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=NeoPy/testing-web" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>