Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/Jennys-Drumste / drum-song-mode.html |
| Date | Not specified in source |
| Repository | Janady13/Jennys-Drumste |
| Revision | c601bf1648fe71058835589331c7f7e669940b8b |
| Branch | main |
| Path or archive member | drum-song-mode.html |
| Original SHA-256 | 8a368aa187c3f5e893a0a52d14aa0de1b649c80e5405ed007d43dfc089076231 |
| Git blob SHA | 98e13c4edd668547b33a77d03992a4927008a680 |
| Reading-copy SHA-256 | 8a368aa187c3f5e893a0a52d14aa0de1b649c80e5405ed007d43dfc089076231 |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/Jennys-Drumste / drum-song-mode.html | main | c601bf1648fe71058835589331c7f7e669940b8b | Open original |
| Janady13/Jennys-Drumste / drum-song-mode.html | claude/complete-integration-feature-011CUkTzyyk8bKmXaRXJAR61 | 01c36ac1a180916e11864b4573b8c1999e2c5db3 | Open original |
Recovered source transcript
Download complete source text ↓1,506 lines · 48.7 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DrumMaster Pro - Learn Real Songs | YouTube Integration</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #00ffcc;
--secondary: #7c3aed;
--accent: #ff00ff;
--dark: #0f0f23;
--darker: #050510;
--light: #ffffff;
--success: #10b981;
--error: #ef4444;
--warning: #f59e0b;
}
body {
font-family: 'Inter', sans-serif;
background: var(--darker);
color: var(--light);
overflow-x: hidden;
min-height: 100vh;
}
.app-container {
display: grid;
grid-template-columns: 250px 1fr 300px;
height: 100vh;
gap: 20px;
padding: 20px;
background: linear-gradient(135deg, rgba(124, 58, 237, 0.05) 0%, rgba(0, 255, 204, 0.05) 100%);
}
/* Header */
.header {
grid-column: 1 / -1;
background: rgba(15, 15, 35, 0.95);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 20px;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.8em;
font-weight: 700;
background: linear-gradient(135deg, var(--primary), var(--accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.connection-status {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 20px;
background: rgba(0, 255, 204, 0.1);
border: 1px solid rgba(0, 255, 204, 0.3);
border-radius: 50px;
}
.connection-status.connected {
background: rgba(16, 185, 129, 0.1);
border-color: var(--success);
}
/* Left Sidebar - Song Library */
.sidebar {
background: rgba(15, 15, 35, 0.95);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 20px;
padding: 20px;
overflow-y: auto;
max-height: calc(100vh - 120px);
}
.sidebar h2 {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.3em;
margin-bottom: 20px;
color: var(--primary);
}
.search-box {
width: 100%;
padding: 12px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(0, 255, 204, 0.2);
border-radius: 10px;
color: var(--light);
font-size: 1em;
margin-bottom: 20px;
}
.search-box:focus {
outline: none;
border-color: var(--primary);
background: rgba(255, 255, 255, 0.08);
}
.song-categories {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
}
.category-tab {
padding: 10px 15px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
}
.category-tab:hover {
background: rgba(0, 255, 204, 0.1);
border-color: var(--primary);
}
.category-tab.active {
background: linear-gradient(135deg, rgba(0, 255, 204, 0.2), rgba(255, 0, 255, 0.1));
border-color: var(--primary);
}
.song-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.song-card {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 12px;
padding: 12px;
cursor: pointer;
transition: all 0.3s;
}
.song-card:hover {
background: rgba(0, 255, 204, 0.05);
border-color: var(--primary);
transform: translateX(5px);
}
.song-card.active {
background: linear-gradient(135deg, rgba(0, 255, 204, 0.1), rgba(255, 0, 255, 0.05));
border-color: var(--primary);
}
.song-title {
font-weight: 600;
margin-bottom: 4px;
}
.song-artist {
color: rgba(255, 255, 255, 0.6);
font-size: 0.9em;
margin-bottom: 8px;
}
.song-stats {
display: flex;
gap: 15px;
font-size: 0.85em;
color: rgba(255, 255, 255, 0.5);
}
.difficulty-badge {
padding: 2px 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
font-size: 0.8em;
}
.difficulty-easy { background: rgba(16, 185, 129, 0.2); color: #10b981; }
.difficulty-medium { background: rgba(245, 158, 11, 0.2); color: #f59e0b; }
.difficulty-hard { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
/* Main Content Area */
.main-content {
display: flex;
flex-direction: column;
gap: 20px;
max-height: calc(100vh - 120px);
}
/* YouTube Video Player */
.video-section {
background: rgba(15, 15, 35, 0.95);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 20px;
padding: 20px;
flex: 1;
}
.youtube-input {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.youtube-input input {
flex: 1;
padding: 12px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(0, 255, 204, 0.2);
border-radius: 10px;
color: var(--light);
font-size: 1em;
}
.btn {
padding: 12px 24px;
background: linear-gradient(135deg, var(--primary), var(--accent));
color: var(--dark);
border: none;
border-radius: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(0, 255, 204, 0.3);
}
.btn-outline {
background: transparent;
border: 2px solid var(--primary);
color: var(--primary);
}
.btn-outline:hover {
background: var(--primary);
color: var(--dark);
}
#youtube-player {
width: 100%;
aspect-ratio: 16/9;
background: #000;
border-radius: 15px;
overflow: hidden;
}
.video-controls {
display: flex;
gap: 10px;
margin-top: 20px;
align-items: center;
flex-wrap: wrap;
}
.tempo-slider {
flex: 1;
min-width: 150px;
}
.tempo-slider input {
width: 100%;
-webkit-appearance: none;
height: 6px;
background: rgba(0, 255, 204, 0.1);
border-radius: 3px;
outline: none;
}
.tempo-slider input::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
}
.tempo-display {
padding: 8px 16px;
background: rgba(0, 255, 204, 0.1);
border-radius: 8px;
font-family: 'Space Grotesk', monospace;
}
/* Drum Pattern Display */
.pattern-section {
background: rgba(15, 15, 35, 0.95);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 20px;
padding: 20px;
height: 300px;
overflow-y: auto;
}
.pattern-timeline {
position: relative;
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 10px;
min-height: 200px;
}
.pattern-tracks {
display: flex;
flex-direction: column;
gap: 5px;
}
.track {
display: flex;
align-items: center;
height: 30px;
background: rgba(255, 255, 255, 0.02);
border-radius: 5px;
padding: 0 10px;
}
.track-label {
width: 80px;
font-size: 0.9em;
color: var(--primary);
}
.track-beats {
flex: 1;
display: flex;
gap: 2px;
height: 20px;
}
.beat {
flex: 1;
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
transition: all 0.1s;
}
.beat.active {
background: var(--primary);
box-shadow: 0 0 10px var(--primary);
}
.beat.hit {
background: var(--success);
animation: hitPulse 0.3s;
}
.beat.upcoming {
background: var(--warning);
animation: pulse 0.5s infinite;
}
@keyframes hitPulse {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
@keyframes pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
.playhead {
position: absolute;
width: 2px;
height: 100%;
background: var(--accent);
top: 0;
left: 0;
box-shadow: 0 0 20px var(--accent);
transition: left 0.1s linear;
}
/* Right Panel - Drum Kit */
.drum-panel {
background: rgba(15, 15, 35, 0.95);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 20px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
max-height: calc(100vh - 120px);
}
.stats-display {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.stat-card {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 10px;
padding: 10px;
text-align: center;
}
.stat-value {
font-size: 1.5em;
font-weight: 700;
color: var(--primary);
}
.stat-label {
font-size: 0.8em;
color: rgba(255, 255, 255, 0.6);
}
.drum-kit-mini {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
flex: 1;
}
.drum-pad {
background: rgba(255, 255, 255, 0.02);
border: 2px solid rgba(0, 255, 204, 0.2);
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.1s;
padding: 10px;
}
.drum-pad.active {
background: var(--primary);
border-color: var(--primary);
color: var(--dark);
transform: scale(0.95);
}
.drum-pad.target {
animation: targetPulse 0.5s infinite;
border-color: var(--warning);
background: rgba(245, 158, 11, 0.1);
}
@keyframes targetPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.drum-icon {
font-size: 1.5em;
margin-bottom: 5px;
}
.drum-name {
font-size: 0.8em;
font-weight: 600;
}
/* Song Learning Mode */
.learning-controls {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 10px;
padding: 15px;
display: flex;
flex-direction: column;
gap: 10px;
}
.loop-section {
display: flex;
gap: 10px;
align-items: center;
}
.loop-input {
width: 80px;
padding: 5px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(0, 255, 204, 0.2);
border-radius: 5px;
color: var(--light);
text-align: center;
}
/* Modal for Song Import */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: linear-gradient(135deg, rgba(15, 15, 35, 0.98), rgba(124, 58, 237, 0.1));
border: 1px solid var(--primary);
border-radius: 20px;
padding: 30px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
font-size: 1.5em;
font-weight: 700;
margin-bottom: 20px;
color: var(--primary);
}
.tab-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab-tab {
padding: 10px 20px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(0, 255, 204, 0.2);
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
}
.tab-tab.active {
background: var(--primary);
color: var(--dark);
border-color: var(--primary);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.tab-editor {
width: 100%;
min-height: 300px;
padding: 15px;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(0, 255, 204, 0.2);
border-radius: 10px;
color: var(--light);
font-family: 'Courier New', monospace;
font-size: 0.9em;
resize: vertical;
}
.import-instructions {
background: rgba(0, 255, 204, 0.05);
border-left: 3px solid var(--primary);
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
}
.song-info-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.form-group label {
color: rgba(255, 255, 255, 0.8);
font-size: 0.9em;
}
.form-group input, .form-group select {
padding: 10px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(0, 255, 204, 0.2);
border-radius: 8px;
color: var(--light);
}
/* Progress Tracker */
.progress-section {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(0, 255, 204, 0.1);
border-radius: 10px;
padding: 15px;
}
.progress-bar {
height: 30px;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--accent));
width: 0%;
transition: width 0.3s;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 10px;
font-weight: 600;
color: var(--dark);
}
.section-markers {
display: flex;
justify-content: space-between;
margin-top: 5px;
font-size: 0.8em;
color: rgba(255, 255, 255, 0.6);
}
/* Responsive */
@media (max-width: 1200px) {
.app-container {
grid-template-columns: 1fr;
}
.sidebar, .drum-panel {
display: none;
}
}
</style>
</head>
<body>
<div class="app-container">
<!-- Header -->
<div class="header">
<div class="logo">🥁 DrumMaster Pro - Song Mode</div>
<div class="connection-status" id="connectionStatus">
<span class="status-dot">🔴</span>
<span>Connect MIDI Device</span>
</div>
<button class="btn" onclick="connectMIDI()">Connect MIDI</button>
</div>
<!-- Left Sidebar - Song Library -->
<div class="sidebar">
<h2>🎵 Song Library</h2>
<input type="text" class="search-box" placeholder="Search songs..." id="songSearch">
<div class="song-categories">
<div class="category-tab active" data-category="popular">🔥 Popular</div>
<div class="category-tab" data-category="rock">🎸 Rock</div>
<div class="category-tab" data-category="pop">🎤 Pop</div>
<div class="category-tab" data-category="metal">🤘 Metal</div>
<div class="category-tab" data-category="custom">💾 My Songs</div>
</div>
<div class="song-list" id="songList">
<!-- Songs will be populated here -->
</div>
<button class="btn btn-outline" style="width: 100%; margin-top: 20px;" onclick="showImportModal()">
+ Import Song
</button>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- YouTube Video Section -->
<div class="video-section">
<div class="youtube-input">
<input type="text" id="youtubeUrl" placeholder="Paste YouTube URL or search for a song...">
<button class="btn" onclick="loadYouTubeVideo()">Load Video</button>
</div>
<div id="youtube-player"></div>
<div class="video-controls">
<button class="btn btn-outline" onclick="playPause()">⏯ Play/Pause</button>
<button class="btn btn-outline" onclick="restart()">⏮ Restart</button>
<button class="btn btn-outline" onclick="toggleLoop()">🔁 Loop</button>
<div class="tempo-slider">
<input type="range" id="tempoSlider" min="50" max="150" value="100">
</div>
<div class="tempo-display">Speed: <span id="tempoValue">100</span>%</div>
</div>
<div class="learning-controls">
<div class="loop-section">
<label>Loop Section:</label>
<input type="text" class="loop-input" id="loopStart" placeholder="0:00">
<span>to</span>
<input type="text" class="loop-input" id="loopEnd" placeholder="0:30">
<button class="btn btn-outline" onclick="setLoop()">Set Loop</button>
</div>
</div>
</div>
<!-- Drum Pattern Display -->
<div class="pattern-section">
<div class="pattern-timeline">
<div class="playhead" id="playhead"></div>
<div class="pattern-tracks" id="patternTracks">
<!-- Pattern will be displayed here -->
</div>
</div>
<div class="progress-section" style="margin-top: 20px;">
<div class="progress-bar">
<div class="progress-fill" id="progressFill">0%</div>
</div>
<div class="section-markers">
<span>Intro</span>
<span>Verse 1</span>
<span>Chorus</span>
<span>Verse 2</span>
<span>Chorus</span>
<span>Bridge</span>
<span>Outro</span>
</div>
</div>
</div>
</div>
<!-- Right Panel - Drum Kit & Stats -->
<div class="drum-panel">
<div class="stats-display">
<div class="stat-card">
<div class="stat-value" id="accuracy">0%</div>
<div class="stat-label">Accuracy</div>
</div>
<div class="stat-card">
<div class="stat-value" id="combo">0</div>
<div class="stat-label">Combo</div>
</div>
<div class="stat-card">
<div class="stat-value" id="score">0</div>
<div class="stat-label">Score</div>
</div>
<div class="stat-card">
<div class="stat-value" id="bpm">120</div>
<div class="stat-label">BPM</div>
</div>
</div>
<div class="drum-kit-mini">
<div class="drum-pad" data-note="49">
<span class="drum-icon">💥</span>
<span class="drum-name">Crash</span>
</div>
<div class="drum-pad" data-note="46">
<span class="drum-icon">🎩</span>
<span class="drum-name">Hi-Hat</span>
</div>
<div class="drum-pad" data-note="51">
<span class="drum-icon">🔔</span>
<span class="drum-name">Ride</span>
</div>
<div class="drum-pad" data-note="48">
<span class="drum-icon">🥁</span>
<span class="drum-name">Tom 1</span>
</div>
<div class="drum-pad" data-note="38">
<span class="drum-icon">🎵</span>
<span class="drum-name">Snare</span>
</div>
<div class="drum-pad" data-note="45">
<span class="drum-icon">🥁</span>
<span class="drum-name">Tom 2</span>
</div>
<div class="drum-pad" data-note="43">
<span class="drum-icon">🥁</span>
<span class="drum-name">Tom 3</span>
</div>
<div class="drum-pad" data-note="36">
<span class="drum-icon">🦶</span>
<span class="drum-name">Kick</span>
</div>
<div class="drum-pad" data-note="41">
<span class="drum-icon">🥁</span>
<span class="drum-name">Floor</span>
</div>
</div>
<button class="btn" style="width: 100%;" onclick="startPractice()">
🎯 Start Practice
</button>
</div>
</div>
<!-- Import Song Modal -->
<div class="modal" id="importModal">
<div class="modal-content">
<div class="modal-header">Import Song Pattern</div>
<div class="tab-tabs">
<div class="tab-tab active" data-tab="youtube">YouTube</div>
<div class="tab-tab" data-tab="tab">Tab Editor</div>
<div class="tab-tab" data-tab="midi">MIDI File</div>
</div>
<div class="tab-content active" data-content="youtube">
<div class="import-instructions">
<strong>Import from YouTube:</strong><br>
Enter a YouTube URL and we'll help you create the drum pattern.
</div>
<div class="song-info-form">
<div class="form-group">
<label>YouTube URL</label>
<input type="text" id="importYoutubeUrl" placeholder="https://youtube.com/watch?v=...">
</div>
<div class="form-group">
<label>Song Title</label>
<input type="text" id="importTitle" placeholder="Enter song title">
</div>
<div class="form-group">
<label>Artist</label>
<input type="text" id="importArtist" placeholder="Enter artist name">
</div>
<div class="form-group">
<label>BPM (Tempo)</label>
<input type="number" id="importBPM" placeholder="120" min="60" max="200">
</div>
<div class="form-group">
<label>Difficulty</label>
<select id="importDifficulty">
<option value="easy">Easy</option>
<option value="medium">Medium</option>
<option value="hard">Hard</option>
</select>
</div>
</div>
</div>
<div class="tab-content" data-content="tab">
<div class="import-instructions">
<strong>Tab Format:</strong><br>
C = Crash, H = Hi-Hat, S = Snare, K = Kick, T = Tom, R = Ride<br>
Use | for bar lines, - for rests
</div>
<textarea class="tab-editor" id="tabEditor" placeholder="Example:
|K---S---K---S---|K---S---K---S---|
|H-H-H-H-H-H-H-H-|H-H-H-H-H-H-H-H-|
|----------------|C---------------|"></textarea>
</div>
<div class="tab-content" data-content="midi">
<div class="import-instructions">
<strong>Upload MIDI File:</strong><br>
Import drum patterns from MIDI files
</div>
<input type="file" accept=".mid,.midi" id="midiFileInput">
</div>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<button class="btn" onclick="importSong()">Import Song</button>
<button class="btn btn-outline" onclick="closeModal()">Cancel</button>
</div>
</div>
</div>
<!-- YouTube API -->
<script src="https://www.youtube.com/iframe_api"></script>
<script>
// Song Database with Patterns
const SONG_DATABASE = {
popular: [
{
id: 'seven-nation-army',
title: 'Seven Nation Army',
artist: 'The White Stripes',
youtubeId: '0J2QdDbelmY',
bpm: 124,
difficulty: 'easy',
pattern: {
kick: '1---1---1---1---',
snare: '----1-------1---',
hihat: '----------------',
crash: '1---------------',
ride: '----------------'
}
},
{
id: 'billie-jean',
title: 'Billie Jean',
artist: 'Michael Jackson',
youtubeId: 'Zi_XLOBDo_Y',
bpm: 118,
difficulty: 'medium',
pattern: {
kick: '1---1---1---1---',
snare: '----1-------1---',
hihat: '1-1-1-1-1-1-1-1-',
crash: '----------------',
ride: '----------------'
}
},
{
id: 'smells-like-teen-spirit',
title: 'Smells Like Teen Spirit',
artist: 'Nirvana',
youtubeId: 'hTWKbfoikeg',
bpm: 117,
difficulty: 'medium',
pattern: {
kick: '1--1--1-1--1--1-',
snare: '----1-------1---',
hihat: '1-1---1-1-1---1-',
crash: '1---------------',
ride: '----------------'
}
}
],
rock: [
{
id: 'back-in-black',
title: 'Back In Black',
artist: 'AC/DC',
youtubeId: 'pAgnJDJN4VA',
bpm: 92,
difficulty: 'medium',
pattern: {
kick: '1---1---1---1---',
snare: '----1-------1---',
hihat: '1-1-1-1-1-1-1-1-',
crash: '1---------------',
ride: '----------------'
}
},
{
id: 'enter-sandman',
title: 'Enter Sandman',
artist: 'Metallica',
youtubeId: 'CD-E-LDc384',
bpm: 123,
difficulty: 'hard',
pattern: {
kick: '1-1---1-1-1---1-',
snare: '----1-------1---',
hihat: '--1---1---1---1-',
crash: '1---------------',
ride: '----------------'
}
}
],
pop: [
{
id: 'blinding-lights',
title: 'Blinding Lights',
artist: 'The Weeknd',
youtubeId: '4NRXx6U8ABQ',
bpm: 171,
difficulty: 'easy',
pattern: {
kick: '1-1-1-1-1-1-1-1-',
snare: '----1-------1---',
hihat: '1111111111111111',
crash: '----------------',
ride: '----------------'
}
},
{
id: 'uptown-funk',
title: 'Uptown Funk',
artist: 'Mark Ronson ft. Bruno Mars',
youtubeId: 'OPf0YbXqDm0',
bpm: 115,
difficulty: 'medium',
pattern: {
kick: '1----1--1----1--',
snare: '----1-------1---',
hihat: '1-1-1-1-1-1-1-1-',
crash: '----------------',
ride: '----------------'
}
}
],
metal: [
{
id: 'toxicity',
title: 'Toxicity',
artist: 'System of a Down',
youtubeId: 'iywaBOMvYLI',
bpm: 134,
difficulty: 'hard',
pattern: {
kick: '11--11--11--11--',
snare: '----1-------1---',
hihat: '----------------',
crash: '1---------------',
ride: '1-1-1-1-1-1-1-1-'
}
},
{
id: 'chop-suey',
title: 'Chop Suey!',
artist: 'System of a Down',
youtubeId: 'CSvFpBOe8eY',
bpm: 127,
difficulty: 'hard',
pattern: {
kick: '1-1-1-1-1-1-1-1-',
snare: '--1---1---1---1-',
hihat: '111111----------',
crash: '------1---------',
ride: '--------11111111'
}
}
],
custom: []
};
// YouTube Player
let player;
let isPlayerReady = false;
let currentSong = null;
let isPlaying = false;
let currentBeat = 0;
let playInterval = null;
let stats = {
accuracy: 0,
combo: 0,
score: 0,
hits: 0,
misses: 0
};
// MIDI variables
let midiAccess = null;
let midiInput = null;
// Initialize YouTube Player
function onYouTubeIframeAPIReady() {
player = new YT.Player('youtube-player', {
height: '100%',
width: '100%',
videoId: '',
playerVars: {
'playsinline': 1,
'controls': 1
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
isPlayerReady = true;
console.log('YouTube player ready');
}
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING) {
startPatternSync();
} else if (event.data == YT.PlayerState.PAUSED) {
stopPatternSync();
}
}
// Load YouTube Video
function loadYouTubeVideo() {
const url = document.getElementById('youtubeUrl').value;
const videoId = extractYouTubeId(url);
if (videoId && isPlayerReady) {
player.loadVideoById(videoId);
}
}
function extractYouTubeId(url) {
const regex = /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/;
const match = url.match(regex);
return match ? match[1] : null;
}
// Pattern Synchronization
function startPatternSync() {
if (!currentSong) return;
isPlaying = true;
const bpm = currentSong.bpm;
const beatDuration = 60000 / (bpm * 4); // 16th notes
playInterval = setInterval(() => {
updatePatternDisplay();
checkUpcomingNotes();
currentBeat = (currentBeat + 1) % 16;
}, beatDuration);
}
function stopPatternSync() {
isPlaying = false;
if (playInterval) {
clearInterval(playInterval);
playInterval = null;
}
}
function updatePatternDisplay() {
const tracks = document.getElementById('patternTracks');
tracks.innerHTML = '';
if (!currentSong || !currentSong.pattern) return;
Object.entries(currentSong.pattern).forEach(([drum, pattern]) => {
const track = document.createElement('div');
track.className = 'track';
track.innerHTML = `
<span class="track-label">${drum.charAt(0).toUpperCase() + drum.slice(1)}</span>
<div class="track-beats">
${pattern.split('').map((beat, index) =>
`<div class="beat ${beat === '1' ? 'active' : ''} ${index === currentBeat ? 'upcoming' : ''}" data-beat="${index}"></div>`
).join('')}
</div>
`;
tracks.appendChild(track);
});
// Update playhead position
const playhead = document.getElementById('playhead');
playhead.style.left = (currentBeat / 16 * 100) + '%';
}
function checkUpcomingNotes() {
if (!currentSong || !currentSong.pattern) return;
// Check 2 beats ahead for visual cue
const lookAhead = (currentBeat + 2) % 16;
Object.entries(currentSong.pattern).forEach(([drum, pattern]) => {
if (pattern[lookAhead] === '1') {
highlightDrum(drum);
}
});
}
function highlightDrum(drumType) {
const drumMap = {
kick: 36,
snare: 38,
hihat: 46,
crash: 49,
ride: 51
};
const note = drumMap[drumType];
const pad = document.querySelector(`.drum-pad[data-note="${note}"]`);
if (pad) {
pad.classList.add('target');
setTimeout(() => pad.classList.remove('target'), 500);
}
}
// MIDI Connection
async function connectMIDI() {
try {
midiAccess = await navigator.requestMIDIAccess();
const inputs = midiAccess.inputs.values();
for (let input of inputs) {
midiInput = input;
midiInput.onmidimessage = handleMIDIMessage;
break;
}
if (midiInput) {
const status = document.getElementById('connectionStatus');
status.classList.add('connected');
status.innerHTML = '<span class="status-dot">🟢</span><span>MIDI Connected</span>';
}
} catch (error) {
console.error('MIDI connection failed:', error);
alert('MIDI not supported in this browser. Try Chrome or Edge.');
}
}
function handleMIDIMessage(message) {
const [status, note, velocity] = message.data;
if (status >= 144 && status <= 159 && velocity > 0) {
handleDrumHit(note, velocity);
}
}
function handleDrumHit(note, velocity) {
// Visual feedback
const pad = document.querySelector(`.drum-pad[data-note="${note}"]`);
if (pad) {
pad.classList.add('active');
setTimeout(() => pad.classList.remove('active'), 200);
}
// Check accuracy if playing
if (isPlaying && currentSong) {
checkHitAccuracy(note);
}
}
function checkHitAccuracy(note) {
const drumMap = {
36: 'kick',
38: 'snare',
46: 'hihat',
49: 'crash',
51: 'ride'
};
const drumType = drumMap[note];
if (!drumType || !currentSong.pattern[drumType]) return;
// Check if hit is within timing window
const pattern = currentSong.pattern[drumType];
const expectedHit = pattern[currentBeat] === '1' || pattern[(currentBeat + 1) % 16] === '1';
if (expectedHit) {
stats.hits++;
stats.combo++;
stats.score += 10 * (1 + stats.combo * 0.1);
// Visual feedback
const beats = document.querySelectorAll(`.track-beats .beat[data-beat="${currentBeat}"]`);
beats.forEach(beat => beat.classList.add('hit'));
} else {
stats.misses++;
stats.combo = 0;
}
updateStats();
}
function updateStats() {
const total = stats.hits + stats.misses;
stats.accuracy = total > 0 ? Math.round((stats.hits / total) * 100) : 0;
document.getElementById('accuracy').textContent = stats.accuracy + '%';
document.getElementById('combo').textContent = stats.combo;
document.getElementById('score').textContent = Math.round(stats.score);
}
// Song Library
function loadSongLibrary(category = 'popular') {
const songList = document.getElementById('songList');
songList.innerHTML = '';
const songs = SONG_DATABASE[category] || [];
songs.forEach(song => {
const card = document.createElement('div');
card.className = 'song-card';
card.onclick = () => selectSong(song);
card.innerHTML = `
<div class="song-title">${song.title}</div>
<div class="song-artist">${song.artist}</div>
<div class="song-stats">
<span class="difficulty-badge difficulty-${song.difficulty}">${song.difficulty}</span>
<span>${song.bpm} BPM</span>
</div>
`;
songList.appendChild(card);
});
}
function selectSong(song) {
currentSong = song;
currentBeat = 0;
// Update UI
document.querySelectorAll('.song-card').forEach(card => card.classList.remove('active'));
event.currentTarget.classList.add('active');
// Load YouTube video
if (isPlayerReady) {
player.loadVideoById(song.youtubeId);
}
// Display pattern
updatePatternDisplay();
// Update BPM display
document.getElementById('bpm').textContent = song.bpm;
}
// Video Controls
function playPause() {
if (!player) return;
if (isPlaying) {
player.pauseVideo();
} else {
player.playVideo();
}
}
function restart() {
if (!player) return;
player.seekTo(0);
currentBeat = 0;
stats = { accuracy: 0, combo: 0, score: 0, hits: 0, misses: 0 };
updateStats();
}
function toggleLoop() {
// Implementation for loop functionality
const loopStart = document.getElementById('loopStart').value;
const loopEnd = document.getElementById('loopEnd').value;
// Convert time to seconds and set loop
console.log('Loop from', loopStart, 'to', loopEnd);
}
function setLoop() {
// Set specific loop points
const start = timeToSeconds(document.getElementById('loopStart').value);
const end = timeToSeconds(document.getElementById('loopEnd').value);
if (player && start < end) {
// Implement loop logic
console.log('Setting loop:', start, '-', end);
}
}
function timeToSeconds(time) {
const parts = time.split(':');
return parseInt(parts[0]) * 60 + parseInt(parts[1]);
}
// Import Modal Functions
function showImportModal() {
document.getElementById('importModal').classList.add('active');
}
function closeModal() {
document.getElementById('importModal').classList.remove('active');
}
function importSong() {
const activeTab = document.querySelector('.tab-tab.active').dataset.tab;
if (activeTab === 'youtube') {
const song = {
id: 'custom-' + Date.now(),
title: document.getElementById('importTitle').value,
artist: document.getElementById('importArtist').value,
youtubeId: extractYouTubeId(document.getElementById('importYoutubeUrl').value),
bpm: parseInt(document.getElementById('importBPM').value),
difficulty: document.getElementById('importDifficulty').value,
pattern: generateBasicPattern(parseInt(document.getElementById('importBPM').value))
};
SONG_DATABASE.custom.push(song);
loadSongLibrary('custom');
closeModal();
}
}
function generateBasicPattern(bpm) {
// Generate a basic pattern based on BPM
return {
kick: '1---1---1---1---',
snare: '----1-------1---',
hihat: '1-1-1-1-1-1-1-1-',
crash: '1---------------',
ride: '----------------'
};
}
// Tab switching
document.querySelectorAll('.tab-tab').forEach(tab => {
tab.addEventListener('click', (e) => {
document.querySelectorAll('.tab-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
e.target.classList.add('active');
const content = document.querySelector(`[data-content="${e.target.dataset.tab}"]`);
if (content) content.classList.add('active');
});
});
// Category switching
document.querySelectorAll('.category-tab').forEach(tab => {
tab.addEventListener('click', (e) => {
document.querySelectorAll('.category-tab').forEach(t => t.classList.remove('active'));
e.target.classList.add('active');
loadSongLibrary(e.target.dataset.category);
});
});
// Tempo control
document.getElementById('tempoSlider').addEventListener('input', (e) => {
const tempo = e.target.value;
document.getElementById('tempoValue').textContent = tempo;
if (player) {
player.setPlaybackRate(tempo / 100);
}
});
// Search functionality
document.getElementById('songSearch').addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
const cards = document.querySelectorAll('.song-card');
cards.forEach(card => {
const title = card.querySelector('.song-title').textContent.toLowerCase();
const artist = card.querySelector('.song-artist').textContent.toLowerCase();
if (title.includes(query) || artist.includes(query)) {
card.style.display = 'block';
} else {
card.style.display = 'none';
}
});
});
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadSongLibrary('popular');
});
// Keyboard support
document.addEventListener('keydown', (e) => {
const keyMap = {
' ': 36, // Space: Kick
's': 38, // S: Snare
'h': 46, // H: Hi-hat
'c': 49, // C: Crash
'r': 51, // R: Ride
};
const note = keyMap[e.key.toLowerCase()];
if (note) {
e.preventDefault();
handleDrumHit(note, 100);
}
});
function startPractice() {
if (!currentSong) {
alert('Please select a song first!');
return;
}
// Reset stats
stats = { accuracy: 0, combo: 0, score: 0, hits: 0, misses: 0 };
updateStats();
// Start video if ready
if (player) {
player.playVideo();