music-player / index.html
Parthiban97's picture
Add 2 files
cdfd33a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Yamaha Keyboard Player Simulator</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">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Poppins', sans-serif;
background: linear-gradient(135deg, #1a1a2e, #16213e);
color: white;
min-height: 100vh;
}
.key {
transition: all 0.07s ease;
position: relative;
cursor: pointer;
user-select: none;
}
.white-key {
background: white;
border: 1px solid #ccc;
z-index: 1;
}
.black-key {
background: #333;
z-index: 2;
margin-left: -15px;
margin-right: -15px;
height: 60%;
}
.active {
transform: scale(0.98);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.white-key.active {
background: #f0f0f0;
}
.black-key.active {
background: #222;
}
.recording {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(255, 0, 0, 0); }
100% { box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); }
}
.waveform {
height: 80px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
position: relative;
overflow: hidden;
}
.knob {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #555, #222);
border: 2px solid #444;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
position: relative;
cursor: pointer;
}
.knob::after {
content: '';
position: absolute;
top: 5px;
left: 50%;
width: 3px;
height: 15px;
background: #ffcc00;
transform-origin: bottom center;
transform: translateX(-50%) rotate(0deg);
}
.display {
background: linear-gradient(135deg, #2c3e50, #4ca1af);
border-radius: 10px;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
font-family: 'Courier New', monospace;
text-shadow: 0 0 5px rgba(0, 255, 255, 0.7);
}
.slider {
-webkit-appearance: none;
width: 100%;
height: 8px;
border-radius: 4px;
background: #555;
outline: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #ffcc00;
cursor: pointer;
}
#waveform-visualizer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body class="flex flex-col items-center py-8">
<div class="w-full max-w-6xl px-4">
<h1 class="text-4xl font-bold text-center mb-2 text-yellow-400">Yamaha PSR-SX900 Keyboard Simulator</h1>
<p class="text-center text-gray-300 mb-8">Professional Arranger Workstation with Premium Sounds</p>
<!-- Display Panel -->
<div class="display p-4 mb-6 flex justify-between items-center">
<div class="text-xl">
<span id="current-preset">Grand Piano</span>
<span id="current-octave" class="ml-4">Octave: 4</span>
</div>
<div class="text-right">
<div id="bpm-display">BPM: 120</div>
<div id="recording-status">Ready</div>
</div>
</div>
<!-- Controls -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<!-- Instrument Selection -->
<div class="bg-gray-800 p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-4 text-yellow-300">Instrument Presets</h2>
<div class="grid grid-cols-2 gap-3">
<button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="piano">Grand Piano</button>
<button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="electric">Electric Piano</button>
<button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="organ">Church Organ</button>
<button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="strings">Orchestral Strings</button>
<button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="synth">Synth Lead</button>
<button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="guitar">Acoustic Guitar</button>
<button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="bass">Electric Bass</button>
<button class="preset-btn bg-blue-600 hover:bg-blue-700 py-2 px-3 rounded" data-preset="drums">Drum Kit</button>
</div>
</div>
<!-- Effects -->
<div class="bg-gray-800 p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-4 text-yellow-300">Effects</h2>
<div class="space-y-4">
<div>
<label class="block mb-1">Reverb</label>
<input type="range" min="0" max="100" value="30" class="slider" id="reverb-slider">
</div>
<div>
<label class="block mb-1">Chorus</label>
<input type="range" min="0" max="100" value="20" class="slider" id="chorus-slider">
</div>
<div>
<label class="block mb-1">Delay</label>
<input type="range" min="0" max="100" value="15" class="slider" id="delay-slider">
</div>
<div class="flex items-center space-x-4">
<div class="knob" id="eq-knob"></div>
<span>EQ</span>
</div>
</div>
</div>
<!-- Recording & Playback -->
<div class="bg-gray-800 p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-4 text-yellow-300">Recording</h2>
<div class="waveform mb-4" id="waveform">
<canvas id="waveform-visualizer"></canvas>
</div>
<div class="flex space-x-3">
<button id="record-btn" class="flex-1 bg-red-600 hover:bg-red-700 py-2 px-4 rounded flex items-center justify-center">
<i class="fas fa-circle mr-2"></i> Record
</button>
<button id="play-btn" class="flex-1 bg-green-600 hover:bg-green-700 py-2 px-4 rounded flex items-center justify-center" disabled>
<i class="fas fa-play mr-2"></i> Play
</button>
<button id="stop-btn" class="flex-1 bg-gray-600 hover:bg-gray-700 py-2 px-4 rounded flex items-center justify-center" disabled>
<i class="fas fa-stop mr-2"></i> Stop
</button>
</div>
<div class="mt-4">
<button id="save-btn" class="bg-blue-600 hover:bg-blue-700 py-2 px-4 rounded w-full">
<i class="fas fa-save mr-2"></i> Save Recording
</button>
</div>
</div>
</div>
<!-- Keyboard -->
<div class="relative mb-8" style="height: 200px;">
<!-- White Keys -->
<div class="flex h-full">
<div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="C">C</div>
<div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="D">D</div>
<div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="E">E</div>
<div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="F">F</div>
<div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="G">G</div>
<div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="A">A</div>
<div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="B">B</div>
<div class="white-key key w-12 border rounded-b-lg flex items-end justify-center pb-2" data-note="C5">C</div>
</div>
<!-- Black Keys -->
<div class="flex absolute top-0" style="left: 30px;">
<div class="black-key key w-8 rounded-b-lg" data-note="C#"></div>
<div class="black-key key w-8 rounded-b-lg" data-note="D#"></div>
<div style="width: 48px;"></div> <!-- Space for F-G -->
<div class="black-key key w-8 rounded-b-lg" data-note="F#"></div>
<div class="black-key key w-8 rounded-b-lg" data-note="G#"></div>
<div class="black-key key w-8 rounded-b-lg" data-note="A#"></div>
</div>
</div>
<!-- Octave Controls -->
<div class="flex justify-center mb-8">
<button id="octave-down" class="bg-gray-700 hover:bg-gray-600 py-2 px-4 rounded-l">
<i class="fas fa-arrow-down"></i> Octave Down
</button>
<button id="octave-up" class="bg-gray-700 hover:bg-gray-600 py-2 px-4 rounded-r">
Octave Up <i class="fas fa-arrow-up"></i>
</button>
</div>
<!-- Metronome -->
<div class="bg-gray-800 p-4 rounded-lg mb-8">
<h2 class="text-xl font-semibold mb-4 text-yellow-300">Metronome</h2>
<div class="flex items-center space-x-4">
<button id="metronome-btn" class="bg-purple-600 hover:bg-purple-700 py-2 px-4 rounded">
<i class="fas fa-music mr-2"></i> Toggle Metronome
</button>
<div class="flex-1">
<label class="block mb-1">BPM</label>
<input type="range" min="40" max="240" value="120" class="slider" id="bpm-slider">
</div>
<div class="w-24 text-center">
<div class="text-2xl font-bold" id="metronome-display">♩ = 120</div>
</div>
</div>
</div>
<!-- Practice Tools -->
<div class="bg-gray-800 p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-4 text-yellow-300">Practice Tools</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-gray-700 p-3 rounded">
<h3 class="font-medium mb-2">Chord Trainer</h3>
<button class="chord-btn bg-green-600 hover:bg-green-700 py-1 px-3 rounded text-sm w-full" data-chord="C">
Play C Major
</button>
</div>
<div class="bg-gray-700 p-3 rounded">
<h3 class="font-medium mb-2">Scale Practice</h3>
<select class="scale-select w-full bg-gray-800 rounded p-1 text-sm mb-2">
<option value="C-major">C Major Scale</option>
<option value="A-minor">A Minor Scale</option>
<option value="G-pentatonic">G Pentatonic</option>
<option value="A-blues">A Blues Scale</option>
</select>
<button class="scale-btn bg-green-600 hover:bg-green-700 py-1 px-3 rounded text-sm w-full">
Start Practice
</button>
</div>
<div class="bg-gray-700 p-3 rounded">
<h3 class="font-medium mb-2">Song Learning</h3>
<select class="song-select w-full bg-gray-800 rounded p-1 text-sm mb-2">
<option value="twinkle">Twinkle Twinkle</option>
<option value="happy">Happy Birthday</option>
<option value="fur-elise">Fur Elise</option>
<option value="canon">Canon in D</option>
</select>
<button class="song-btn bg-green-600 hover:bg-green-700 py-1 px-3 rounded text-sm w-full">
Load Song
</button>
</div>
</div>
</div>
</div>
<script>
// Audio Context Setup
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
const audioElements = {};
// Global variables
let currentPreset = 'piano';
let currentOctave = 4;
let isRecording = false;
let recordingStartTime;
let recordedNotes = [];
let isPlayingRecording = false;
let metronomeInterval;
let isMetronomeOn = false;
let activeOscillators = {};
// DOM Elements
const keys = document.querySelectorAll('.key');
const presetButtons = document.querySelectorAll('.preset-btn');
const currentPresetDisplay = document.getElementById('current-preset');
const currentOctaveDisplay = document.getElementById('current-octave');
const recordBtn = document.getElementById('record-btn');
const playBtn = document.getElementById('play-btn');
const stopBtn = document.getElementById('stop-btn');
const saveBtn = document.getElementById('save-btn');
const recordingStatus = document.getElementById('recording-status');
const octaveUpBtn = document.getElementById('octave-up');
const octaveDownBtn = document.getElementById('octave-down');
const metronomeBtn = document.getElementById('metronome-btn');
const bpmSlider = document.getElementById('bpm-slider');
const bpmDisplay = document.getElementById('bpm-display');
const metronomeDisplay = document.getElementById('metronome-display');
const waveformCanvas = document.getElementById('waveform-visualizer');
const ctx = waveformCanvas.getContext('2d');
// Effects controls
const reverbSlider = document.getElementById('reverb-slider');
const chorusSlider = document.getElementById('chorus-slider');
const delaySlider = document.getElementById('delay-slider');
const eqKnob = document.getElementById('eq-knob');
// Practice tools
const chordButtons = document.querySelectorAll('.chord-btn');
const scaleSelect = document.querySelector('.scale-select');
const scaleButton = document.querySelector('.scale-btn');
const songSelect = document.querySelector('.song-select');
const songButton = document.querySelector('.song-btn');
// Initialize canvas
waveformCanvas.width = waveformCanvas.parentElement.offsetWidth;
waveformCanvas.height = waveformCanvas.parentElement.offsetHeight;
// Note frequencies (A4 = 440Hz)
const noteFrequencies = {
'C': 261.63, 'C#': 277.18, 'D': 293.66, 'D#': 311.13,
'E': 329.63, 'F': 349.23, 'F#': 369.99, 'G': 392.00,
'G#': 415.30, 'A': 440.00, 'A#': 466.16, 'B': 493.88,
'C5': 523.25
};
// Initialize sound bank
function initializeAudioElements() {
const samples = {
'piano': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
'electric': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
'organ': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
'strings': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
'synth': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
'guitar': ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'],
'bass': ['C2', 'D2', 'E2', 'F2', 'G2', 'A2', 'B2', 'C3'],
'drums': ['kick', 'snare', 'hihat', 'tom']
};
// In a real implementation, you would load actual audio files here
// For this demo, we're using Web Audio API oscillators instead
}
// Initialize
initializeAudioElements();
updatePresetDisplay();
drawEmptyWaveform();
// Event Listeners
keys.forEach(key => {
key.addEventListener('mousedown', () => playNote(key));
key.addEventListener('mouseup', () => releaseNote(key));
key.addEventListener('mouseleave', () => releaseNote(key));
});
document.addEventListener('keydown', (e) => {
const keyMap = {
'a': 'C', 'w': 'C#', 's': 'D', 'e': 'D#',
'd': 'E', 'f': 'F', 't': 'F#', 'g': 'G',
'y': 'G#', 'h': 'A', 'u': 'A#', 'j': 'B',
'k': 'C5'
};
const note = keyMap[e.key.toLowerCase()];
if (note) {
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement && !keyElement.classList.contains('active')) {
playNote(keyElement);
}
}
});
document.addEventListener('keyup', (e) => {
const keyMap = {
'a': 'C', 'w': 'C#', 's': 'D', 'e': 'D#',
'd': 'E', 'f': 'F', 't': 'F#', 'g': 'G',
'y': 'G#', 'h': 'A', 'u': 'A#', 'j': 'B',
'k': 'C5'
};
const note = keyMap[e.key.toLowerCase()];
if (note) {
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
releaseNote(keyElement);
}
}
});
presetButtons.forEach(button => {
button.addEventListener('click', () => {
currentPreset = button.dataset.preset;
updatePresetDisplay();
});
});
recordBtn.addEventListener('click', toggleRecording);
playBtn.addEventListener('click', playRecording);
stopBtn.addEventListener('click', stopPlayback);
saveBtn.addEventListener('click', saveRecording);
octaveUpBtn.addEventListener('click', () => {
if (currentOctave < 7) {
currentOctave++;
updateOctaveDisplay();
}
});
octaveDownBtn.addEventListener('click', () => {
if (currentOctave > 1) {
currentOctave--;
updateOctaveDisplay();
}
});
metronomeBtn.addEventListener('click', toggleMetronome);
bpmSlider.addEventListener('input', updateBPM);
// Chord buttons
chordButtons.forEach(button => {
button.addEventListener('click', () => playChord(button.dataset.chord));
});
scaleButton.addEventListener('click', () => playScale(scaleSelect.value));
songButton.addEventListener('click', () => playSong(songSelect.value));
// Audio Functions
function playNote(keyElement) {
const note = keyElement.dataset.note;
// Check if this key is already playing (from another source)
if (activeOscillators[note]) {
return;
}
keyElement.classList.add('active');
if (currentPreset === 'drums') {
playDrumSound(note);
return;
}
// For other instruments, use Web Audio API oscillators
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
// Create a simple ADSR envelope
const now = audioContext.currentTime;
gainNode.gain.setValueAtTime(0, now);
// Attack
gainNode.gain.linearRampToValueAtTime(1, now + 0.05);
// Sustain (we'll handle release in releaseNote)
oscillator.type = getOscillatorTypeForPreset();
oscillator.frequency.value = noteFrequencies[note] * (currentPreset === 'bass' ? 0.25 : 1);
// Connect nodes with effects
const nodes = applyEffects(oscillator, gainNode);
nodes[nodes.length - 1].connect(audioContext.destination);
oscillator.start();
// Store reference to stop later
activeOscillators[note] = {
oscillator: oscillator,
gainNode: gainNode,
keyElement: keyElement
};
// Record the note if recording
if (isRecording) {
recordedNotes.push({
type: 'note',
note: note,
time: audioContext.currentTime - recordingStartTime,
action: 'start',
preset: currentPreset,
octave: currentOctave
});
updateWaveform();
}
}
function releaseNote(keyElement) {
const note = keyElement.dataset.note;
if (!activeOscillators[note]) return;
keyElement.classList.remove('active');
if (currentPreset === 'drums') return;
// Release the note
const now = audioContext.currentTime;
activeOscillators[note].gainNode.gain.cancelScheduledValues(now);
activeOscillators[note].gainNode.gain.setValueAtTime(activeOscillators[note].gainNode.gain.value, now);
activeOscillators[note].gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
// Clean up after release
setTimeout(() => {
if (activeOscillators[note]) {
activeOscillators[note].oscillator.stop();
delete activeOscillators[note];
}
}, 200);
// Record the note release if recording
if (isRecording) {
recordedNotes.push({
type: 'note',
note: note,
time: audioContext.currentTime - recordingStartTime,
action: 'stop'
});
updateWaveform();
}
}
function playDrumSound(type) {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
let frequency, oscType, duration;
// Basic drum mapping
switch(type) {
case 'C': // Kick
frequency = 80;
oscType = 'sine';
duration = 0.3;
break;
case 'D': // Snare
frequency = 150;
oscType = 'white';
duration = 0.2;
break;
case 'E': // Hi-hat
frequency = 800;
oscType = 'white';
duration = 0.05;
break;
default: // Tom
frequency = 200;
oscType = 'sine';
duration = 0.15;
}
const now = audioContext.currentTime;
if (oscType === 'white') {
// White noise for snare/hi-hat
const bufferSize = audioContext.sampleRate * duration;
const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = noiseBuffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = Math.random() * 2 - 1;
}
const noise = audioContext.createBufferSource();
noise.buffer = noiseBuffer;
const filter = audioContext.createBiquadFilter();
filter.type = 'highpass';
filter.frequency.value = frequency;
noise.connect(filter);
filter.connect(gainNode);
// Envelope
gainNode.gain.setValueAtTime(1, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + duration);
// Apply effects and connect to output
const nodes = applyEffects(noise, gainNode);
nodes[nodes.length - 1].connect(audioContext.destination);
noise.start();
noise.stop(now + duration);
} else {
// Regular oscillator for kick/tom
oscillator.type = oscType;
oscillator.frequency.value = frequency;
// Frequency envelope for kick
oscillator.frequency.exponentialRampToValueAtTime(1, now + duration);
// Gain envelope
gainNode.gain.setValueAtTime(1, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + duration);
// Apply effects and connect to output
const nodes = applyEffects(oscillator, gainNode);
nodes[nodes.length - 1].connect(audioContext.destination);
oscillator.start();
oscillator.stop(now + duration);
}
// Record the drum hit if recording
if (isRecording) {
recordedNotes.push({
type: 'drum',
note: type,
time: audioContext.currentTime - recordingStartTime,
frequency: frequency,
duration: duration
});
updateWaveform();
}
}
function applyEffects(sourceNode, gainNode) {
const nodes = [sourceNode];
// Reverb
const reverb = audioContext.createConvolver();
const reverbGain = audioContext.createGain();
reverbGain.gain.value = reverbSlider.value / 100 * 0.3;
// Simple impulse response for reverb
const length = audioContext.sampleRate * 1;
const impulse = audioContext.createBuffer(2, length, audioContext.sampleRate);
const left = impulse.getChannelData(0);
const right = impulse.getChannelData(1);
for (let i = 0; i < length; i++) {
const n = length - i;
left[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 3);
right[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 3);
}
reverb.buffer = impulse;
// Chorus
const chorus = audioContext.createDelay(0.1);
chorus.delayTime.value = 0.03;
const chorusLFO = audioContext.createOscillator();
chorusLFO.frequency.value = 2;
const chorusDepth = audioContext.createGain();
chorusDepth.gain.value = chorusSlider.value / 100 * 0.01;
chorusLFO.connect(chorusDepth);
chorusDepth.connect(chorus.delayTime);
chorusLFO.start();
const chorusGain = audioContext.createGain();
chorusGain.gain.value = chorusSlider.value / 100 * 0.5;
// Delay
const delay = audioContext.createDelay(1.0);
delay.delayTime.value = 0.5;
const feedback = audioContext.createGain();
feedback.gain.value = delaySlider.value / 100 * 0.6;
const delayGain = audioContext.createGain();
delayGain.gain.value = delaySlider.value / 100 * 0.3;
// Connect nodes
const lastNode = nodes[nodes.length - 1];
// Main path (dry + effects)
if (gainNode) {
lastNode.connect(gainNode);
nodes.push(gainNode);
}
// Reverb path
nodes[nodes.length - 1].connect(reverb);
reverb.connect(reverbGain);
nodes.push(reverbGain);
// Chorus path
nodes[0].connect(chorus);
chorus.connect(chorusGain);
nodes.push(chorusGain);
// Delay path (feedback loop)
nodes[0].connect(delay);
delay.connect(delayGain);
delayGain.connect(audioContext.destination);
delay.connect(feedback);
feedback.connect(delay);
return nodes;
}
function getOscillatorTypeForPreset() {
switch(currentPreset) {
case 'piano':
case 'electric':
return 'sine';
case 'organ':
return 'sine';
case 'strings':
return 'sawtooth';
case 'synth':
return 'square';
case 'guitar':
return 'sine';
case 'bass':
return 'sine';
default:
return 'sine';
}
}
// Recording Functions
function toggleRecording() {
if (isRecording) {
// Stop recording
isRecording = false;
recordBtn.classList.remove('recording');
recordingStatus.textContent = 'Recording saved';
playBtn.disabled = recordedNotes.length === 0;
stopBtn.disabled = true;
recordBtn.innerHTML = '<i class="fas fa-circle mr-2"></i> Record';
} else {
// Start recording
isRecording = true;
recordedNotes = [];
recordingStartTime = audioContext.currentTime;
recordBtn.classList.add('recording');
recordingStatus.textContent = 'Recording...';
playBtn.disabled = true;
stopBtn.disabled = true;
recordBtn.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop';
drawEmptyWaveform();
}
}
function playRecording() {
if (recordedNotes.length === 0 || isPlayingRecording) return;
isPlayingRecording = true;
playBtn.disabled = true;
stopBtn.disabled = false;
recordingStatus.textContent = 'Playing back...';
const playStartTime = audioContext.currentTime;
// Play each recorded note
recordedNotes.forEach(note => {
const delay = note.time * 1000; // Convert to milliseconds
setTimeout(() => {
if (note.type === 'drum') {
// Play drum sound
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
if (note.frequency > 500) { // Hi-hat
const bufferSize = audioContext.sampleRate * note.duration;
const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = noiseBuffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
output[i] = Math.random() * 2 - 1;
}
const noise = audioContext.createBufferSource();
noise.buffer = noiseBuffer;
const filter = audioContext.createBiquadFilter();
filter.type = 'highpass';
filter.frequency.value = note.frequency;
noise.connect(filter);
filter.connect(gainNode);
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + note.duration);
noise.start();
noise.stop(audioContext.currentTime + note.duration);
} else {
oscillator.type = note.frequency > 150 ? 'sine' : 'sine';
oscillator.frequency.value = note.frequency;
oscillator.connect(gainNode);
gainNode.gain.setValueAtTime(1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + note.duration);
oscillator.start();
oscillator.stop(audioContext.currentTime + note.duration);
}
gainNode.connect(audioContext.destination);
} else if (note.action === 'start') {
// Play regular note
const keyElement = document.querySelector(`.key[data-note="${note.note}"]`);
if (keyElement) {
playNote(keyElement);
}
} else if (note.action === 'stop') {
// Release note
const keyElement = document.querySelector(`.key[data-note="${note.note}"]`);
if (keyElement) {
releaseNote(keyElement);
}
}
}, delay);
});
// Stop the playback after the last note
const totalDuration = recordedNotes[recordedNotes.length - 1].time * 1000 + 1000;
setTimeout(() => {
if (isPlayingRecording) {
stopPlayback();
}
}, totalDuration);
}
function stopPlayback() {
isPlayingRecording = false;
playBtn.disabled = recordedNotes.length === 0;
stopBtn.disabled = true;
recordingStatus.textContent = 'Ready';
// Stop all playing notes
Object.keys(activeOscillators).forEach(note => {
if (activeOscillators[note]) {
releaseNote(activeOscillators[note].keyElement);
}
});
}
function saveRecording() {
if (recordedNotes.length === 0) {
alert('No recording to save!');
return;
}
const recordingName = prompt('Enter a name for your recording:', `Recording ${new Date().toLocaleString()}`);
if (recordingName) {
// In a real implementation, you would save to localStorage or a server
alert(`Recording "${recordingName}" saved! (This is a demo - recording is not actually saved)`);
}
}
// Helper Functions
function updatePresetDisplay() {
const presetNames = {
'piano': 'Grand Piano',
'electric': 'Electric Piano',
'organ': 'Church Organ',
'strings': 'Orchestral Strings',
'synth': 'Synth Lead',
'guitar': 'Acoustic Guitar',
'bass': 'Electric Bass',
'drums': 'Drum Kit'
};
currentPresetDisplay.textContent = presetNames[currentPreset];
}
function updateOctaveDisplay() {
currentOctaveDisplay.textContent = `Octave: ${currentOctave}`;
}
function updateBPM() {
const bpm = bpmSlider.value;
bpmDisplay.textContent = `BPM: ${bpm}`;
metronomeDisplay.textContent = `♩ = ${bpm}`;
if (isMetronomeOn) {
clearInterval(metronomeInterval);
startMetronome();
}
}
function toggleMetronome() {
isMetronomeOn = !isMetronomeOn;
if (isMetronomeOn) {
metronomeBtn.classList.add('bg-purple-700');
metronomeBtn.classList.remove('bg-purple-600');
startMetronome();
} else {
metronomeBtn.classList.remove('bg-purple-700');
metronomeBtn.classList.add('bg-purple-600');
clearInterval(metronomeInterval);
}
}
function startMetronome() {
const bpm = parseInt(bpmSlider.value);
const interval = 60000 / bpm; // Convert BPM to milliseconds
// Play first click immediately
playMetronomeClick();
// Then set up the interval
metronomeInterval = setInterval(playMetronomeClick, interval);
}
function playMetronomeClick() {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = 'square';
oscillator.frequency.value = 800;
const now = audioContext.currentTime;
gainNode.gain.setValueAtTime(1, now);
gainNode.gain.exponentialRampToValueAtTime(0.001, now + 0.05);
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.start();
oscillator.stop(now + 0.1);
}
function updateWaveform() {
const width = waveformCanvas.width;
const height = waveformCanvas.height;
ctx.clearRect(0, 0, width, height);
// Simple visualization of notes
const maxTime = Math.max(1, ...recordedNotes.map(n => n.time));
const scaleX = width / maxTime;
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.strokeStyle = '#4ade80';
ctx.lineWidth = 2;
recordedNotes.forEach(note => {
if (note.action === 'start' || note.type === 'drum') {
const x = note.time * scaleX;
const noteHeight = note.type === 'drum' ? 20 : mapNoteToHeight(note.note);
ctx.beginPath();
ctx.arc(x, height - noteHeight - 10, 5, 0, Math.PI * 2);
ctx.fill();
// Draw line to connect notes
const endNote = recordedNotes.find(
n => n.note === note.note && n.action === 'stop' && n.time > note.time
);
if (endNote) {
const endX = endNote.time * scaleX;
ctx.beginPath();
ctx.moveTo(x, height - noteHeight - 10);
ctx.lineTo(endX, height - noteHeight - 10);
ctx.stroke();
}
}
});
}
function drawEmptyWaveform() {
const width = waveformCanvas.width;
const height = waveformCanvas.height;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.textAlign = 'center';
ctx.font = '14px Poppins';
ctx.fillText(isRecording ? 'Recording...' : 'No recording yet', width / 2, height / 2);
}
function mapNoteToHeight(note) {
const noteOrder = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B', 'C5'];
const index = noteOrder.indexOf(note);
return 10 + (index * 5);
}
// Practice Tools Functions
function playChord(chord) {
const chordNotes = {
'C': ['C', 'E', 'G'],
'D': ['D', 'F#', 'A'],
'E': ['E', 'G#', 'B'],
'F': ['F', 'A', 'C'],
'G': ['G', 'B', 'D'],
'A': ['A', 'C#', 'E'],
'B': ['B', 'D#', 'F#']
};
const notes = chordNotes[chord] || chordNotes['C'];
notes.forEach(note => {
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
playNote(keyElement);
// Release the note after 1 second
setTimeout(() => {
releaseNote(keyElement);
}, 1000);
}
});
}
function playScale(scale) {
const scalePatterns = {
'C-major': ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C5'],
'A-minor': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'A'],
'G-pentatonic': ['G', 'A', 'B', 'D', 'E', 'G'],
'A-blues': ['A', 'C', 'D', 'D#', 'E', 'G', 'A']
};
const notes = scalePatterns[scale] || scalePatterns['C-major'];
playNotesWithTiming(notes);
}
function playSong(song) {
const songPatterns = {
'twinkle': [
{note: 'C', duration: 0.5}, {note: 'C', duration: 0.5},
{note: 'G', duration: 0.5}, {note: 'G', duration: 0.5},
{note: 'A', duration: 0.5}, {note: 'A', duration: 0.5},
{note: 'G', duration: 1},
{note: 'F', duration: 0.5}, {note: 'F', duration: 0.5},
{note: 'E', duration: 0.5}, {note: 'E', duration: 0.5},
{note: 'D', duration: 0.5}, {note: 'D', duration: 0.5},
{note: 'C', duration: 1}
],
'happy': [
{note: 'G', duration: 0.25}, {note: 'G', duration: 0.25},
{note: 'A', duration: 0.5}, {note: 'G', duration: 0.5},
{note: 'C', duration: 0.5}, {note: 'B', duration: 1},
{note: 'G', duration: 0.25}, {note: 'G', duration: 0.25},
{note: 'A', duration: 0.5}, {note: 'G', duration: 0.5},
{note: 'D', duration: 0.5}, {note: 'C', duration: 1}
],
'fur-elise': [
{note: 'E', duration: 0.25}, {note: 'D#', duration: 0.25},
{note: 'E', duration: 0.25}, {note: 'D#', duration: 0.25},
{note: 'E', duration: 0.25}, {note: 'B', duration: 0.25},
{note: 'D', duration: 0.25}, {note: 'C', duration: 0.25},
{note: 'A', duration: 0.5}
],
'canon': [
{note: 'G', duration: 0.5}, {note: 'D', duration: 0.5},
{note: 'B', duration: 0.5}, {note: 'A', duration: 0.5},
{note: 'G', duration: 0.5}, {note: 'D', duration: 0.5},
{note: 'B', duration: 0.5}, {note: 'A', duration: 0.5}
]
};
const notes = songPatterns[song] || songPatterns['twinkle'];
let time = 0;
notes.forEach(({note, duration}) => {
setTimeout(() => {
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
playNote(keyElement);
// Release the note after the duration
setTimeout(() => {
releaseNote(keyElement);
}, duration * 800); // Slightly shorter than the full duration
}
}, time * 1000);
time += duration;
});
}
function playNotesWithTiming(notes, tempo = 1) {
let time = 0;
const noteDuration = 0.5 * tempo;
notes.forEach(note => {
setTimeout(() => {
const keyElement = document.querySelector(`.key[data-note="${note}"]`);
if (keyElement) {
playNote(keyElement);
// Release the note after the duration
setTimeout(() => {
releaseNote(keyElement);
}, noteDuration * 800); // Slightly shorter than the full duration
}
}, time * 1000);
time += noteDuration;
});
}
// Initialize knob rotation
let rotation = 0;
eqKnob.addEventListener('mousedown', (e) => {
const startY = e.clientY;
const startRotation = rotation;
function rotateKnob(e) {
const deltaY = startY - e.clientY;
rotation = Math.min(90, Math.max(-90, startRotation + deltaY));
eqKnob.style.transform = `rotate(${rotation}deg)`;
}
function stopRotating() {
document.removeEventListener('mousemove', rotateKnob);
document.removeEventListener('mouseup', stopRotating);
}
document.addEventListener('mousemove', rotateKnob);
document.addEventListener('mouseup', stopRotating);
});
</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=Parthiban97/music-player" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
</html>