CODE:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Piano</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h2 {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 1rem;
}
select {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 1rem;
}
.controls {
display: flex;
align-items: center;
margin-bottom: 1rem;
}
button {
background-color: #22c55e;
color: white;
border: none;
border-radius: 4px;
padding: 0.5rem;
cursor: pointer;
margin-right: 0.5rem;
}
button:disabled {
background-color: #d1d5db;
cursor: not-allowed;
}
.piano {
position: relative;
display: inline-flex;
margin-bottom: 1rem;
}
.key {
border: 1px solid #d1d5db;
border-radius: 0 0 4px 4px;
display: flex;
align-items: flex-end;
justify-content: center;
padding-bottom: 0.5rem;
transition: background-color 0.1s;
}
.white-key {
width: 3rem;
height: 10rem;
background-color: white;
}
.black-key {
width: 2rem;
height: 6rem;
background-color: black;
position: absolute;
top: 0;
z-index: 1;
}
.key span {
font-size: 0.875rem;
}
.white-key span {
color: black;
}
.black-key span {
color: white;
}
.pressed {
background-color: #d1d5db;
}
.black-key.pressed {
background-color: #4b5563;
}
.highlight {
box-shadow: 0 0 0 2px #3b82f6;
}
.white-key.highlight {
background-color: #e6f2ff;
}
.black-key.highlight {
background-color: #4a5568;
}
p {
text-align: center;
margin-top: 1rem;
margin-bottom: 1rem;
}
.sequence-display {
display: flex;
justify-content: center;
align-items: center;
background-color: #f3f4f6;
padding: 1rem;
border-radius: 4px;
}
.sequence-key {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background-color: #e5e7eb;
border-radius: 4px;
margin: 0 0.25rem;
}
.sequence-key.current {
background-color: #3b82f6;
color: white;
}
</style>
</head>
<body>
<div class="container">
<h2>Interactive Piano</h2>
<select id="songSelect">
<option value="">Select a song</option>
</select>
<div class="controls">
<button id="restartBtn">
<i data-lucide="rotate-ccw"></i>
</button>
<i data-lucide="music"></i>
<span id="nextKey">Next key: </span>
</div>
<div class="piano" id="piano"></div>
<p>Use your keyboard keys (A-J and W, E, T, Y, U) to play the piano.</p>
<div class="sequence-display">
<button id="scrollLeftBtn">
<i data-lucide="chevron-left"></i>
</button>
<div id="sequenceKeys"></div>
<button id="scrollRightBtn">
<i data-lucide="chevron-right"></i>
</button>
</div>
</div>
<script>
const keys = [
{ note: 'C', key: 'a', color: 'white', frequency: 261.63 },
{ note: 'C#', key: 'w', color: 'black', frequency: 277.18 },
{ note: 'D', key: 's', color: 'white', frequency: 293.66 },
{ note: 'D#', key: 'e', color: 'black', frequency: 311.13 },
{ note: 'E', key: 'd', color: 'white', frequency: 329.63 },
{ note: 'F', key: 'f', color: 'white', frequency: 349.23 },
{ note: 'F#', key: 't', color: 'black', frequency: 369.99 },
{ note: 'G', key: 'g', color: 'white', frequency: 392.00 },
{ note: 'G#', key: 'y', color: 'black', frequency: 415.30 },
{ note: 'A', key: 'h', color: 'white', frequency: 440.00 },
{ note: 'A#', key: 'u', color: 'black', frequency: 466.16 },
{ note: 'B', key: 'j', color: 'white', frequency: 493.88 },
];
const songs = {
'Ode to Joy (Full)': ['E', 'E', 'F', 'G', 'G', 'F', 'E', 'D', 'C', 'C', 'D', 'E', 'E', 'D', 'D',
'E', 'E', 'F', 'G', 'G', 'F', 'E', 'D', 'C', 'C', 'D', 'E', 'D', 'C', 'C',
'D', 'D', 'E', 'C', 'D', 'E', 'F', 'E', 'C', 'D', 'E', 'F', 'E', 'D', 'C', 'D', 'G',
'E', 'E', 'F', 'G', 'G', 'F', 'E', 'D', 'C', 'C', 'D', 'E', 'D', 'C', 'C'],
'Twinkle Twinkle': ['C', 'C', 'G', 'G', 'A', 'A', 'G', 'F', 'F', 'E', 'E', 'D', 'D', 'C', 'G', 'G', 'F', 'F', 'E', 'E', 'D', 'G', 'G', 'F', 'F', 'E', 'E', 'D', 'C', 'C', 'G', 'G', 'A', 'A', 'G', 'F', 'F', 'E', 'E', 'D', 'D', 'C'],
'Happy Birthday': ['C', 'C', 'D', 'C', 'F', 'E', 'C', 'C', 'D', 'C', 'G', 'F', 'C', 'C', 'C', 'A', 'F', 'E', 'D', 'B', 'B', 'A', 'F', 'G', 'F'],
'Jingle Bells': ['E', 'E', 'E', 'E', 'E', 'E', 'E', 'G', 'C', 'D', 'E', 'F', 'F', 'F', 'F', 'F', 'E', 'E', 'E', 'E', 'D', 'D', 'E', 'D', 'G', 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'G', 'C', 'D', 'E', 'F', 'F', 'F', 'F', 'F', 'E', 'E', 'E', 'G', 'G', 'F', 'D', 'C'],
'Mary Had a Little Lamb': ['E', 'D', 'C', 'D', 'E', 'E', 'E', 'D', 'D', 'D', 'E', 'G', 'G', 'E', 'D', 'C', 'D', 'E', 'E', 'E', 'E', 'D', 'D', 'E', 'D', 'C'],
'Fur Elise (Intro)': ['E', 'D#', 'E', 'D#', 'E', 'B', 'D', 'C', 'A', 'C', 'E', 'A', 'B', 'E', 'G#', 'B', 'C'],
'Canon in D (Simplified)': ['D', 'A', 'B', 'F#', 'G', 'D', 'G', 'A', 'D', 'A', 'B', 'F#', 'G', 'D', 'G', 'A', 'F#', 'D', 'A', 'B', 'G', 'D', 'G', 'A'],
'Greensleeves': ['G', 'A#', 'C', 'D', 'D#', 'D', 'C', 'A', 'F', 'G', 'A', 'A#', 'G', 'A#', 'C', 'D', 'D#', 'D', 'C', 'A', 'F', 'G', 'A', 'F', 'D', 'G'],
'Eine Kleine Nachtmusik': ['G', 'D', 'G', 'D', 'G', 'D', 'G', 'B', 'A', 'B', 'C', 'D', 'C', 'B', 'A', 'G', 'D', 'G', 'D', 'G', 'D', 'G'],
};
let audioContext;
let currentSong = '';
let currentKeyIndex = 0;
let scrollOffset = 0;
function createPiano() {
const piano = document.getElementById('piano');
keys.forEach((key, index) => {
const keyElement = document.createElement('div');
keyElement.className = `key ${key.color}-key`;
keyElement.id = `key-${key.note}`;
if (key.color === 'black') {
const whiteKeyWidth = 3;
const offset = keys.filter(k => k.color === 'white').indexOf(keys.find(k => k.note[0] === key.note[0])) * whiteKeyWidth + 2;
keyElement.style.left = `${offset}rem`;
}
const keyLabel = document.createElement('span');
keyLabel.textContent = key.key.toUpperCase();
keyElement.appendChild(keyLabel);
piano.appendChild(keyElement);
});
}
function initAudio() {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
function playNote(frequency) {
if (audioContext) {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.00001, audioContext.currentTime + 1);
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.start();
oscillator.stop(audioContext.currentTime + 1);
}
}
function handleKeyDown(event) {
const key = event.key.toLowerCase();
const keyObj = keys.find((k) => k.key === key);
if (keyObj) {
const keyElement = document.getElementById(`key-${keyObj.note}`);
keyElement.classList.add('pressed');
playNote(keyObj.frequency);
if (currentSong && key === getKeybindSequence()[currentKeyIndex]) {
currentKeyIndex++;
scrollOffset = Math.min(scrollOffset + 1, getKeybindSequence().length - 10);
updateSequenceDisplay();
}
}
}
function handleKeyUp(event) {
const key = event.key.toLowerCase();
const keyObj = keys.find((k) => k.key === key);
if (keyObj) {
const keyElement = document.getElementById(`key-${keyObj.note}`);
keyElement.classList.remove('pressed');
}
}
function populateSongList() {
const songSelect = document.getElementById('songSelect');
Object.keys(songs).forEach(song => {
const option = document.createElement('option');
option.value = song;
option.textContent = song;
songSelect.appendChild(option);
});
}
function handleSongSelect(event) {
currentSong = event.target.value;
currentKeyIndex = 0;
scrollOffset = 0;
updateSequenceDisplay();
}
function restartSong() {
currentKeyIndex = 0;
scrollOffset = 0;
updateSequenceDisplay();
}
function getKeybindSequence() {
if (!currentSong) return [];
return songs[currentSong].map(note => keys.find(k => k.note === note).key);
}
function updateNextKey() {
const nextKeySpan = document.getElementById('nextKey');
const keybindSequence = getKeybindSequence();
const nextKey = keybindSequence[currentKeyIndex];
nextKeySpan.textContent = `Next key: ${nextKey?.toUpperCase() || ''}`;
// Remove highlight from all keys
keys.forEach(key => {
const keyElement = document.getElementById(`key-${key.note}`);
keyElement.classList.remove('highlight');
});
// Add highlight to the next key
if (nextKey) {
const nextKeyObj = keys.find(k => k.key === nextKey);
const nextKeyElement = document.getElementById(`key-${nextKeyObj.note}`);
nextKeyElement.classList.add('highlight');
}
}
function updateSequenceDisplay() {
const sequenceKeys = document.getElementById('sequenceKeys');
sequenceKeys.innerHTML = '';
const keybindSequence = getKeybindSequence();
keybindSequence.slice(scrollOffset, scrollOffset + 10).forEach((key, index) => {
const keyElement = document.createElement('div');
keyElement.className = `sequence-key ${index + scrollOffset === currentKeyIndex ? 'current' : ''}`;
keyElement.textContent = key.toUpperCase();
sequenceKeys.appendChild(keyElement);
});
updateNextKey();
}
function scrollLeft() {
scrollOffset = Math.max(0, scrollOffset - 1);
updateSequenceDisplay();
}
function scrollRight() {
const keybindSequence = getKeybindSequence();
scrollOffset = Math.min(keybindSequence.length - 10, scrollOffset + 1);
updateSequenceDisplay();
}
function init() {
createPiano();
initAudio();
populateSongList();
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
document.getElementById('songSelect').addEventListener('change', handleSongSelect);
document.getElementById('restartBtn').addEventListener('click', restartSong);
document.getElementById('scrollLeftBtn').addEventListener('click', scrollLeft);
document.getElementById('scrollRightBtn').addEventListener('click', scrollRight);
lucide.createIcons();
}
window.addEventListener('load', init);
</script>
</body>
</html>