| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>YouTube Chord Analyzer</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/tonal@4.6.2/dist/tonal.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@magenta/music@1.23.1/es6/core.js"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .piano-key { |
| position: relative; |
| border: 1px solid #333; |
| display: flex; |
| align-items: flex-end; |
| justify-content: center; |
| cursor: pointer; |
| user-select: none; |
| } |
| |
| .white-key { |
| width: 40px; |
| height: 160px; |
| background-color: white; |
| z-index: 1; |
| border-radius: 0 0 5px 5px; |
| box-shadow: 0 5px 5px rgba(0,0,0,0.2); |
| } |
| |
| .black-key { |
| width: 24px; |
| height: 100px; |
| background-color: black; |
| margin-left: -12px; |
| margin-right: -12px; |
| z-index: 2; |
| border-radius: 0 0 3px 3px; |
| } |
| |
| .active-key { |
| background-color: #3b82f6; |
| } |
| |
| .active-black-key { |
| background-color: #1d4ed8; |
| } |
| |
| .chord-progression { |
| background-color: rgba(59, 130, 246, 0.2); |
| border-left: 3px solid #3b82f6; |
| } |
| |
| .visualizer-container { |
| position: relative; |
| height: 200px; |
| background-color: #1e293b; |
| border-radius: 8px; |
| overflow: hidden; |
| } |
| |
| .audio-wave { |
| position: absolute; |
| bottom: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| display: flex; |
| align-items: flex-end; |
| } |
| |
| .audio-bar { |
| flex-grow: 1; |
| background-color: rgba(59, 130, 246, 0.6); |
| margin-right: 2px; |
| transition: height 0.05s ease; |
| } |
| |
| .audio-bar:last-child { |
| margin-right: 0; |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(1); } |
| 50% { transform: scale(1.05); } |
| 100% { transform: scale(1); } |
| } |
| |
| .analyzing { |
| animation: pulse 1.5s infinite; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 min-h-screen"> |
| <div class="container mx-auto px-4 py-8"> |
| <header class="mb-8 text-center"> |
| <h1 class="text-4xl font-bold text-blue-600 mb-2">YouTube Chord Analyzer</h1> |
| <p class="text-gray-600 text-lg">Detect chords and keys from any YouTube video in real-time</p> |
| </header> |
| |
| <div class="bg-white rounded-xl shadow-lg p-6 mb-8"> |
| <div class="flex flex-col md:flex-row gap-6"> |
| <div class="flex-1"> |
| <div class="mb-4"> |
| <label for="youtube-url" class="block text-sm font-medium text-gray-700 mb-1">YouTube Video URL</label> |
| <div class="flex gap-2"> |
| <input type="text" id="youtube-url" placeholder="https://www.youtube.com/watch?v=..." |
| class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"> |
| <button id="analyze-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg font-medium transition-colors"> |
| <i class="fas fa-play mr-2"></i> Analyze |
| </button> |
| </div> |
| </div> |
| |
| <div id="video-container" class="aspect-w-16 aspect-h-9 bg-gray-200 rounded-lg overflow-hidden mb-4"> |
| <div class="flex items-center justify-center h-full text-gray-500"> |
| <i class="fas fa-music text-4xl"></i> |
| </div> |
| </div> |
| |
| <div class="flex items-center justify-between mb-4"> |
| <div class="flex items-center gap-2"> |
| <button id="play-btn" class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-full disabled:opacity-50" disabled> |
| <i class="fas fa-play"></i> |
| </button> |
| <button id="pause-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 p-2 rounded-full disabled:opacity-50" disabled> |
| <i class="fas fa-pause"></i> |
| </button> |
| <span id="time-display" class="text-sm text-gray-600">00:00 / 00:00</span> |
| </div> |
| <div id="status-indicator" class="flex items-center gap-2"> |
| <span class="text-sm font-medium text-gray-500">Ready</span> |
| <div class="w-3 h-3 rounded-full bg-gray-400"></div> |
| </div> |
| </div> |
| |
| <div class="visualizer-container mb-4"> |
| <div id="audio-wave" class="audio-wave"></div> |
| </div> |
| </div> |
| |
| <div class="flex-1"> |
| <div class="bg-gray-50 rounded-lg p-4 mb-4"> |
| <h3 class="font-medium text-lg text-gray-800 mb-3">Current Analysis</h3> |
| |
| <div class="grid grid-cols-2 gap-4 mb-4"> |
| <div class="bg-white p-3 rounded-lg shadow-sm"> |
| <p class="text-sm text-gray-500 mb-1">Detected Key</p> |
| <p id="current-key" class="text-2xl font-bold text-blue-600">-</p> |
| </div> |
| <div class="bg-white p-3 rounded-lg shadow-sm"> |
| <p class="text-sm text-gray-500 mb-1">Current Chord</p> |
| <p id="current-chord" class="text-2xl font-bold text-blue-600">-</p> |
| </div> |
| </div> |
| |
| <div class="mb-4"> |
| <p class="text-sm text-gray-500 mb-2">Chord Notes</p> |
| <div id="piano" class="flex relative h-40 mb-2"></div> |
| </div> |
| |
| <div> |
| <p class="text-sm text-gray-500 mb-2">Chord Progression</p> |
| <div id="chord-progression" class="flex gap-2 overflow-x-auto py-2"> |
| <div class="text-center"> |
| <div class="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center mx-auto mb-1"> |
| <span class="text-gray-600">?</span> |
| </div> |
| <span class="text-xs text-gray-500">0:00</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bg-white rounded-xl shadow-lg p-6"> |
| <h2 class="text-xl font-bold text-gray-800 mb-4">Chord Analysis Results</h2> |
| |
| <div class="overflow-x-auto"> |
| <table class="min-w-full divide-y divide-gray-200"> |
| <thead class="bg-gray-50"> |
| <tr> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Bar</th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Measure</th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Chord</th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Notes</th> |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th> |
| </tr> |
| </thead> |
| <tbody id="results-table" class="bg-white divide-y divide-gray-200"> |
| <tr> |
| <td colspan="6" class="px-6 py-4 text-center text-gray-500">No analysis data yet. Enter a YouTube URL and click "Analyze"</td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let player; |
| let audioContext; |
| let analyser; |
| let dataArray; |
| let animationId; |
| let currentChord = ''; |
| let detectedKey = ''; |
| let chordHistory = []; |
| let barCount = 1; |
| let measureCount = 1; |
| let lastChordChangeTime = 0; |
| |
| |
| const youtubeUrlInput = document.getElementById('youtube-url'); |
| const analyzeBtn = document.getElementById('analyze-btn'); |
| const videoContainer = document.getElementById('video-container'); |
| const playBtn = document.getElementById('play-btn'); |
| const pauseBtn = document.getElementById('pause-btn'); |
| const timeDisplay = document.getElementById('time-display'); |
| const statusIndicator = document.getElementById('status-indicator'); |
| const statusText = statusIndicator.querySelector('span'); |
| const statusDot = statusIndicator.querySelector('div'); |
| const audioWave = document.getElementById('audio-wave'); |
| const currentKeyDisplay = document.getElementById('current-key'); |
| const currentChordDisplay = document.getElementById('current-chord'); |
| const piano = document.getElementById('piano'); |
| const chordProgression = document.getElementById('chord-progression'); |
| const resultsTable = document.getElementById('results-table'); |
| |
| |
| function initPiano() { |
| piano.innerHTML = ''; |
| |
| const whiteKeys = ['C', 'D', 'E', 'F', 'G', 'A', 'B']; |
| const blackKeys = ['C#', 'D#', '', 'F#', 'G#', 'A#', '']; |
| |
| whiteKeys.forEach((note, i) => { |
| const key = document.createElement('div'); |
| key.className = 'piano-key white-key'; |
| key.dataset.note = note; |
| key.innerHTML = `<span class="text-xs text-gray-500 mb-1">${note}</span>`; |
| piano.appendChild(key); |
| |
| |
| if (i < 6 && blackKeys[i] !== '') { |
| const blackKey = document.createElement('div'); |
| blackKey.className = 'piano-key black-key'; |
| blackKey.dataset.note = blackKeys[i]; |
| piano.appendChild(blackKey); |
| } |
| }); |
| } |
| |
| |
| function highlightChord(chord) { |
| |
| document.querySelectorAll('.piano-key').forEach(key => { |
| if (key.classList.contains('white-key')) { |
| key.classList.remove('active-key'); |
| } else { |
| key.classList.remove('active-black-key'); |
| } |
| }); |
| |
| if (!chord |
| </html> |