Interactive Piano

copy code

Interactive Piano

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>