Spaces:
Running
Running
nice. but the grid became not clickable. return the grid to the state when I can click on the boxes - Initial Deployment
b6d4ada
verified
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Drum Machine - Кахон</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> | |
@keyframes playHead { | |
from { left: 0; } | |
to { left: 100%; } | |
} | |
.play-head { | |
animation: playHead linear infinite; | |
animation-duration: var(--bpm); | |
} | |
.note { | |
transition: all 0.1s ease; | |
} | |
.note.active { | |
transform: scale(1.05); | |
box-shadow: 0 0 10px rgba(255, 255, 255, 0.8); | |
} | |
.pulse { | |
animation: pulse 0.5s; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.1); } | |
100% { transform: scale(1); } | |
} | |
.staff-container { | |
position: relative; | |
height: 120px; | |
margin-bottom: 20px; | |
overflow-x: auto; | |
overflow-y: hidden; | |
white-space: nowrap; | |
} | |
.staff { | |
position: relative; | |
display: inline-block; | |
height: 100px; | |
width: 800px; | |
background-color: rgba(255, 255, 255, 0.05); | |
border-radius: 4px; | |
} | |
.staff-line { | |
position: absolute; | |
width: 100%; | |
height: 1px; | |
background-color: white; | |
} | |
.staff-line:nth-child(1) { top: 10%; } | |
.staff-line:nth-child(2) { top: 30%; } | |
.staff-line:nth-child(3) { top: 50%; } | |
.staff-line:nth-child(4) { top: 70%; } | |
.staff-line:nth-child(5) { top: 90%; } | |
.neutral-clef { | |
position: absolute; | |
left: 10px; | |
top: 50%; | |
transform: translateY(-50%); | |
font-size: 40px; | |
font-weight: bold; | |
color: white; | |
} | |
.bar-line { | |
position: absolute; | |
top: 0; | |
height: 100%; | |
width: 2px; | |
background-color: white; | |
} | |
.drum-note { | |
position: absolute; | |
width: 12px; | |
height: 12px; | |
border-radius: 50%; | |
background-color: white; | |
transform: translateX(-50%); | |
} | |
.drum-note.bass { top: 90%; } | |
.drum-note.snare { top: 50%; } | |
.drum-note.hihat { top: 30%; } | |
.drum-note.rim { top: 70%; } | |
.note-stem { | |
position: absolute; | |
width: 2px; | |
background-color: white; | |
transform-origin: top center; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white min-h-screen flex flex-col"> | |
<header class="py-6 bg-gray-800 shadow-lg"> | |
<div class="container mx-auto px-4"> | |
<h1 class="text-3xl font-bold text-center text-yellow-400"> | |
<i class="fas fa-drum mr-2"></i> Drum Machine - Кахон | |
</h1> | |
</div> | |
</header> | |
<main class="flex-grow container mx-auto px-4 py-8"> | |
<div class="flex flex-col lg:flex-row gap-8"> | |
<!-- Controls --> | |
<div class="lg:w-1/4 bg-gray-800 p-6 rounded-lg shadow-lg"> | |
<div class="mb-6"> | |
<h2 class="text-xl font-semibold mb-4 text-yellow-400">Controls</h2> | |
<div class="flex items-center justify-between mb-4"> | |
<button id="playBtn" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg flex items-center"> | |
<i class="fas fa-play mr-2"></i> Play | |
</button> | |
<button id="stopBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg flex items-center"> | |
<i class="fas fa-stop mr-2"></i> Stop | |
</button> | |
</div> | |
<div class="mb-4"> | |
<label for="bpm" class="block mb-2">BPM: <span id="bpmValue">120</span></label> | |
<input type="range" id="bpm" min="60" max="240" value="120" class="w-full accent-yellow-500"> | |
</div> | |
<div class="mb-4"> | |
<label for="volume" class="block mb-2">Volume: <span id="volumeValue">80</span>%</label> | |
<input type="range" id="volume" min="0" max="100" value="80" class="w-full accent-yellow-500"> | |
</div> | |
<div class="mb-4"> | |
<label for="steps" class="block mb-2">Steps: <span id="stepsValue">16</span></label> | |
<input type="range" id="steps" min="4" max="32" step="4" value="16" class="w-full accent-yellow-500"> | |
</div> | |
<button id="clearBtn" class="w-full bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg flex items-center justify-center"> | |
<i class="fas fa-trash-alt mr-2"></i> Clear All | |
</button> | |
</div> | |
<div class="mb-6"> | |
<h2 class="text-xl font-semibold mb-4 text-yellow-400">Sounds</h2> | |
<div class="grid grid-cols-2 gap-2"> | |
<button data-sound="bass" class="sound-btn bg-blue-600 hover:bg-blue-700 text-white p-2 rounded flex items-center justify-center"> | |
<i class="fas fa-drum mr-2"></i> Bass | |
</button> | |
<button data-sound="snare" class="sound-btn bg-purple-600 hover:bg-purple-700 text-white p-2 rounded flex items-center justify-center"> | |
<i class="fas fa-drum mr-2"></i> Snare | |
</button> | |
<button data-sound="hihat" class="sound-btn bg-green-600 hover:bg-green-700 text-white p-2 rounded flex items-center justify-center"> | |
<i class="fas fa-drum mr-2"></i> Hi-Hat | |
</button> | |
<button data-sound="rim" class="sound-btn bg-yellow-600 hover:bg-yellow-700 text-white p-2 rounded flex items-center justify-center"> | |
<i class="fas fa-drum mr-2"></i> Rim | |
</button> | |
</div> | |
</div> | |
<div> | |
<h2 class="text-xl font-semibold mb-4 text-yellow-400">Pattern</h2> | |
<select id="patternSelect" class="w-full bg-gray-700 text-white p-2 rounded mb-2"> | |
<option value="empty">Empty</option> | |
<option value="basic">Basic Pattern</option> | |
<option value="complex">Complex Pattern</option> | |
<option value="random">Random</option> | |
</select> | |
<div class="flex gap-2"> | |
<button id="savePatternBtn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg"> | |
Save | |
</button> | |
<button id="loadPatternBtn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg"> | |
Load | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Drum Machine Grid --> | |
<div class="lg:w-3/4"> | |
<div class="bg-gray-800 p-6 rounded-lg shadow-lg"> | |
<h2 class="text-xl font-semibold mb-4 text-yellow-400">Drum Grid</h2> | |
<!-- Playhead --> | |
<div class="relative h-2 mb-4 bg-gray-700 rounded overflow-hidden"> | |
<div id="playHead" class="absolute top-0 left-0 w-1 h-2 bg-yellow-400 z-10" style="display: none;"></div> | |
</div> | |
<!-- Note Visualization --> | |
<div id="noteVisualization" class="mb-6 p-4 bg-gray-700 rounded-lg"> | |
<div class="text-center text-gray-400">Notes will appear here when playing</div> | |
</div> | |
<!-- Staff Visualization --> | |
<div id="staffVisualization" class="mb-6 p-4 bg-gray-700 rounded-lg"> | |
<h3 class="text-lg font-semibold mb-2">Drum Notation</h3> | |
<div class="staff-container"> | |
<div class="staff" id="drumStaff"> | |
<!-- Staff lines --> | |
<div class="staff-line"></div> | |
<div class="staff-line"></div> | |
<div class="staff-line"></div> | |
<div class="staff-line"></div> | |
<div class="staff-line"></div> | |
<!-- Neutral clef --> | |
<div class="neutral-clef">𝄽</div> | |
<!-- Bar lines will be added here --> | |
<!-- Notes will be added here --> | |
</div> | |
</div> | |
</div> | |
<!-- Drum Grid --> | |
<div class="overflow-x-auto"> | |
<table id="drumGrid" class="w-full"> | |
<thead> | |
<tr> | |
<th class="w-16 h-8 bg-gray-700"></th> | |
<!-- Step numbers --> | |
</tr> | |
</thead> | |
<tbody> | |
<!-- Rows will be added by JavaScript --> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
</div> | |
</main> | |
<footer class="py-4 bg-gray-800 text-center text-gray-400"> | |
<div class="container mx-auto px-4"> | |
<p>Drum Machine - Кахон | Created with HTML, CSS & JavaScript</p> | |
</div> | |
</footer> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Audio context | |
const AudioContext = window.AudioContext || window.webkitAudioContext; | |
const audioContext = new AudioContext(); | |
const masterGain = audioContext.createGain(); | |
masterGain.gain.value = 0.8; | |
masterGain.connect(audioContext.destination); | |
// Sound samples (base64 encoded short samples for demo purposes) | |
const sounds = { | |
bass: { | |
name: "Bass", | |
color: "bg-blue-600", | |
sample: generateKick() | |
}, | |
snare: { | |
name: "Snare", | |
color: "bg-purple-600", | |
sample: generateSnare() | |
}, | |
hihat: { | |
name: "Hi-Hat", | |
color: "bg-green-600", | |
sample: generateHiHat() | |
}, | |
rim: { | |
name: "Rim", | |
color: "bg-yellow-600", | |
sample: generateRim() | |
} | |
}; | |
// Generate simple drum sounds (for demo) | |
function generateKick() { | |
const sampleRate = audioContext.sampleRate; | |
const duration = 0.5; | |
const numFrames = duration * sampleRate; | |
const buffer = audioContext.createBuffer(1, numFrames, sampleRate); | |
const data = buffer.getChannelData(0); | |
for (let i = 0; i < numFrames; i++) { | |
const n = -0.5 * Math.cos(i / (sampleRate * 0.002)) + 0.5; | |
data[i] = n * Math.exp(-i / (sampleRate * 0.1)); | |
} | |
return buffer; | |
} | |
function generateSnare() { | |
const sampleRate = audioContext.sampleRate; | |
const duration = 0.3; | |
const numFrames = duration * sampleRate; | |
const buffer = audioContext.createBuffer(1, numFrames, sampleRate); | |
const data = buffer.getChannelData(0); | |
for (let i = 0; i < numFrames; i++) { | |
const noise = Math.random() * 2 - 1; | |
const env = Math.exp(-i / (sampleRate * 0.1)); | |
data[i] = noise * env; | |
} | |
return buffer; | |
} | |
function generateHiHat() { | |
const sampleRate = audioContext.sampleRate; | |
const duration = 0.1; | |
const numFrames = duration * sampleRate; | |
const buffer = audioContext.createBuffer(1, numFrames, sampleRate); | |
const data = buffer.getChannelData(0); | |
for (let i = 0; i < numFrames; i++) { | |
const noise = Math.random() * 2 - 1; | |
const env = 1 - (i / numFrames); | |
data[i] = noise * env; | |
} | |
return buffer; | |
} | |
function generateRim() { | |
const sampleRate = audioContext.sampleRate; | |
const duration = 0.2; | |
const numFrames = duration * sampleRate; | |
const buffer = audioContext.createBuffer(1, numFrames, sampleRate); | |
const data = buffer.getChannelData(0); | |
for (let i = 0; i < numFrames; i++) { | |
const n = Math.sin(i / (sampleRate * 0.0005)); | |
data[i] = n * Math.exp(-i / (sampleRate * 0.05)); | |
} | |
return buffer; | |
} | |
// Play a sound | |
function playSound(sound) { | |
const source = audioContext.createBufferSource(); | |
source.buffer = sound.sample; | |
source.connect(masterGain); | |
source.start(); | |
} | |
// State variables | |
let isPlaying = false; | |
let currentStep = 0; | |
let timerId = null; | |
let bpm = 120; | |
let steps = 16; | |
let selectedSound = 'bass'; | |
let pattern = {}; | |
// Initialize pattern | |
function initPattern() { | |
pattern = {}; | |
Object.keys(sounds).forEach(sound => { | |
pattern[sound] = Array(steps).fill(false); | |
}); | |
} | |
initPattern(); | |
// DOM elements | |
const drumGrid = document.getElementById('drumGrid'); | |
const playBtn = document.getElementById('playBtn'); | |
const stopBtn = document.getElementById('stopBtn'); | |
const bpmSlider = document.getElementById('bpm'); | |
const bpmValue = document.getElementById('bpmValue'); | |
const volumeSlider = document.getElementById('volume'); | |
const volumeValue = document.getElementById('volumeValue'); | |
const stepsSlider = document.getElementById('steps'); | |
const stepsValue = document.getElementById('stepsValue'); | |
const clearBtn = document.getElementById('clearBtn'); | |
const soundBtns = document.querySelectorAll('.sound-btn'); | |
const playHead = document.getElementById('playHead'); | |
const noteVisualization = document.getElementById('noteVisualization'); | |
const patternSelect = document.getElementById('patternSelect'); | |
const savePatternBtn = document.getElementById('savePatternBtn'); | |
const loadPatternBtn = document.getElementById('loadPatternBtn'); | |
// Create drum grid | |
function createDrumGrid() { | |
// Clear existing grid | |
drumGrid.querySelector('tbody').innerHTML = ''; | |
// Create header with step numbers | |
const headerRow = drumGrid.querySelector('thead tr'); | |
headerRow.innerHTML = '<th class="w-16 h-8 bg-gray-700"></th>'; | |
for (let i = 0; i < steps; i++) { | |
const th = document.createElement('th'); | |
th.className = 'w-8 h-8 bg-gray-700 text-xs text-center'; | |
th.textContent = i + 1; | |
headerRow.appendChild(th); | |
} | |
// Create rows for each sound | |
Object.keys(sounds).forEach(sound => { | |
const tr = document.createElement('tr'); | |
// Sound name cell | |
const th = document.createElement('th'); | |
th.className = 'w-16 h-8 bg-gray-700 text-xs text-center'; | |
th.textContent = sounds[sound].name; | |
tr.appendChild(th); | |
// Create cells for each step | |
for (let i = 0; i < steps; i++) { | |
const td = document.createElement('td'); | |
td.className = 'w-8 h-8 p-0 border border-gray-700'; | |
const cell = document.createElement('div'); | |
cell.className = 'w-full h-full cursor-pointer note hover:bg-gray-600'; | |
cell.dataset.sound = sound; | |
cell.dataset.step = i; | |
if (pattern[sound][i]) { | |
cell.classList.add(sounds[sound].color); | |
} else { | |
cell.classList.add('bg-gray-800'); | |
} | |
cell.addEventListener('click', function() { | |
toggleStep(sound, i); | |
}); | |
td.appendChild(cell); | |
tr.appendChild(td); | |
} | |
drumGrid.querySelector('tbody').appendChild(tr); | |
}); | |
} | |
// Toggle step on/off | |
function toggleStep(sound, step) { | |
pattern[sound][step] = !pattern[sound][step]; | |
updateGrid(); | |
// Play the sound when clicking | |
if (pattern[sound][step]) { | |
playSound(sounds[sound]); | |
} | |
} | |
// Update grid display | |
function updateGrid() { | |
const cells = document.querySelectorAll('#drumGrid .note'); | |
cells.forEach(cell => { | |
const sound = cell.dataset.sound; | |
const step = parseInt(cell.dataset.step); | |
if (pattern[sound][step]) { | |
cell.classList.remove('bg-gray-800'); | |
cell.classList.add(sounds[sound].color); | |
} else { | |
cell.classList.remove(sounds[sound].color); | |
cell.classList.add('bg-gray-800'); | |
} | |
}); | |
updateDrumNotation(); | |
} | |
// Update drum notation display | |
function updateDrumNotation() { | |
const staff = document.getElementById('drumStaff'); | |
const cells = document.querySelectorAll('#drumGrid .note'); | |
// Clear existing notes and bar lines | |
document.querySelectorAll('.drum-note, .note-stem, .bar-line').forEach(el => el.remove()); | |
// Add bar lines for each measure (assuming 4/4 time) | |
const measures = Math.ceil(steps / 16); | |
for (let i = 0; i <= measures; i++) { | |
const barLine = document.createElement('div'); | |
barLine.className = 'bar-line'; | |
barLine.style.left = `${(i * 100) / measures}%`; | |
staff.appendChild(barLine); | |
} | |
cells.forEach(cell => { | |
const sound = cell.dataset.sound; | |
const step = parseInt(cell.dataset.step); | |
if (pattern[sound][step]) { | |
cell.classList.remove('bg-gray-800'); | |
cell.classList.add(sounds[sound].color); | |
// Add drum notation | |
const note = document.createElement('div'); | |
note.className = `drum-note ${sound}`; | |
note.style.left = `${(step / steps) * 100}%`; | |
staff.appendChild(note); | |
// Add stem based on note duration | |
const stem = document.createElement('div'); | |
stem.className = 'note-stem'; | |
stem.style.left = `${(step / steps) * 100}%`; | |
stem.style.height = '20px'; | |
stem.style.top = sound === 'hihat' ? '30%' : sound === 'snare' ? '50%' : sound === 'rim' ? '70%' : '90%'; | |
staff.appendChild(stem); | |
} else { | |
cell.classList.remove(sounds[sound].color); | |
cell.classList.add('bg-gray-800'); | |
} | |
}); | |
} | |
// Play the sequence | |
function playSequence() { | |
if (!isPlaying) return; | |
// Highlight current step | |
const cells = document.querySelectorAll(`#drumGrid .note[data-step="${currentStep}"]`); | |
cells.forEach(cell => { | |
cell.classList.add('active'); | |
const sound = cell.dataset.sound; | |
if (pattern[sound][currentStep]) { | |
cell.classList.add('pulse'); | |
playSound(sounds[sound]); | |
// Add note to visualization | |
addNoteToVisualization(sounds[sound].name, currentStep); | |
} | |
setTimeout(() => { | |
cell.classList.remove('active'); | |
cell.classList.remove('pulse'); | |
}, 100); | |
}); | |
// Move playhead | |
const progress = (currentStep / steps) * 100; | |
playHead.style.left = `${progress}%`; | |
// Increment step | |
currentStep = (currentStep + 1) % steps; | |
// Schedule next step | |
const stepDuration = (60 / bpm) * (4 / (steps / 4)) * 1000; | |
timerId = setTimeout(playSequence, stepDuration); | |
} | |
// Add note to visualization | |
function addNoteToVisualization(noteName, step) { | |
const noteElement = document.createElement('div'); | |
noteElement.className = 'inline-block mx-1 text-xs bg-opacity-70 rounded px-2 py-1 mb-1'; | |
switch(noteName) { | |
case 'Bass': | |
noteElement.classList.add('bg-blue-600'); | |
noteElement.textContent = 'B'; | |
break; | |
case 'Snare': | |
noteElement.classList.add('bg-purple-600'); | |
noteElement.textContent = 'S'; | |
break; | |
case 'Hi-Hat': | |
noteElement.classList.add('bg-green-600'); | |
noteElement.textContent = 'H'; | |
break; | |
case 'Rim': | |
noteElement.classList.add('bg-yellow-600'); | |
noteElement.textContent = 'R'; | |
break; | |
} | |
noteVisualization.appendChild(noteElement); | |
// Limit number of notes shown | |
if (noteVisualization.children.length > 20) { | |
noteVisualization.removeChild(noteVisualization.children[0]); | |
} | |
} | |
// Start playback | |
function startPlayback() { | |
if (isPlaying) return; | |
isPlaying = true; | |
currentStep = 0; | |
playBtn.disabled = true; | |
stopBtn.disabled = false; | |
playHead.style.display = 'block'; | |
noteVisualization.innerHTML = ''; | |
// Calculate animation duration based on BPM | |
const loopDuration = (60 / bpm) * 4 * 1000; // 4 beats per measure | |
playHead.style.setProperty('--bpm', `${loopDuration}ms`); | |
playHead.style.animationDuration = `${loopDuration}ms`; | |
// Start audio context if suspended | |
if (audioContext.state === 'suspended') { | |
audioContext.resume(); | |
} | |
playSequence(); | |
} | |
// Stop playback | |
function stopPlayback() { | |
isPlaying = false; | |
clearTimeout(timerId); | |
playBtn.disabled = false; | |
stopBtn.disabled = true; | |
playHead.style.display = 'none'; | |
// Remove active classes | |
document.querySelectorAll('#drumGrid .note').forEach(cell => { | |
cell.classList.remove('active'); | |
cell.classList.remove('pulse'); | |
}); | |
} | |
// Clear all steps | |
function clearAll() { | |
if (confirm('Are you sure you want to clear all steps?')) { | |
initPattern(); | |
updateGrid(); | |
} | |
} | |
// Load predefined pattern | |
function loadPattern(patternName) { | |
initPattern(); | |
switch(patternName) { | |
case 'basic': | |
// Basic rock pattern | |
for (let i = 0; i < steps; i++) { | |
// Hi-hat on every step | |
pattern.hihat[i] = true; | |
// Bass drum on 1 and 3 | |
if (i % (steps / 4) === 0) { | |
pattern.bass[i] = true; | |
} | |
// Snare on 2 and 4 | |
if (i % (steps / 2) === (steps / 4)) { | |
pattern.snare[i] = true; | |
} | |
} | |
break; | |
case 'complex': | |
// More complex pattern | |
for (let i = 0; i < steps; i++) { | |
// Hi-hat | |
pattern.hihat[i] = true; | |
// Bass drum | |
if (i % (steps / 4) === 0 || (i % (steps / 8) === (steps / 16))) { | |
pattern.bass[i] = true; | |
} | |
// Snare | |
if (i % (steps / 2) === (steps / 4) || (i % (steps / 4) === (steps / 8))) { | |
pattern.snare[i] = true; | |
} | |
// Rim | |
if (i % (steps / 8) === (steps / 16) && !pattern.snare[i]) { | |
pattern.rim[i] = true; | |
} | |
} | |
break; | |
case 'random': | |
// Random pattern | |
for (let sound in pattern) { | |
for (let i = 0; i < steps; i++) { | |
pattern[sound][i] = Math.random() > 0.7; | |
} | |
} | |
break; | |
} | |
updateGrid(); | |
} | |
// Save current pattern | |
function savePattern() { | |
const patternName = prompt('Enter a name for this pattern:'); | |
if (patternName) { | |
localStorage.setItem(`drumMachinePattern_${patternName}`, JSON.stringify(pattern)); | |
alert(`Pattern "${patternName}" saved!`); | |
} | |
} | |
// Load saved pattern | |
function loadSavedPattern() { | |
const patternName = prompt('Enter the name of the pattern to load:'); | |
if (patternName) { | |
const savedPattern = localStorage.getItem(`drumMachinePattern_${patternName}`); | |
if (savedPattern) { | |
pattern = JSON.parse(savedPattern); | |
steps = pattern.bass.length; | |
stepsSlider.value = steps; | |
stepsValue.textContent = steps; | |
createDrumGrid(); | |
updateGrid(); | |
alert(`Pattern "${patternName}" loaded!`); | |
} else { | |
alert(`Pattern "${patternName}" not found!`); | |
} | |
} | |
} | |
// Event listeners | |
playBtn.addEventListener('click', startPlayback); | |
stopBtn.addEventListener('click', stopPlayback); | |
bpmSlider.addEventListener('input', function() { | |
bpm = parseInt(this.value); | |
bpmValue.textContent = bpm; | |
if (isPlaying) { | |
stopPlayback(); | |
startPlayback(); | |
} | |
}); | |
volumeSlider.addEventListener('input', function() { | |
const volume = parseInt(this.value) / 100; | |
volumeValue.textContent = this.value; | |
masterGain.gain.value = volume; | |
}); | |
stepsSlider.addEventListener('input', function() { | |
steps = parseInt(this.value); | |
stepsValue.textContent = steps; | |
initPattern(); | |
createDrumGrid(); | |
}); | |
clearBtn.addEventListener('click', clearAll); | |
soundBtns.forEach(btn => { | |
btn.addEventListener('click', function() { | |
selectedSound = this.dataset.sound; | |
soundBtns.forEach(b => b.classList.remove('ring-2', 'ring-yellow-400')); | |
this.classList.add('ring-2', 'ring-yellow-400'); | |
playSound(sounds[selectedSound]); | |
}); | |
}); | |
// Select first sound by default | |
soundBtns[0].classList.add('ring-2', 'ring-yellow-400'); | |
patternSelect.addEventListener('change', function() { | |
if (this.value !== 'empty') { | |
loadPattern(this.value); | |
} | |
}); | |
savePatternBtn.addEventListener('click', savePattern); | |
loadPatternBtn.addEventListener('click', loadSavedPattern); | |
// Set note duration style | |
function setNoteDuration(noteElement, duration) { | |
switch(duration) { | |
case 'quarter': | |
// Single note head with stem | |
break; | |
case 'eighth': | |
// Add flag | |
noteElement.style.borderRight = '6px solid transparent'; | |
noteElement.style.borderLeft = '6px solid transparent'; | |
noteElement.style.borderBottom = '6px solid white'; | |
noteElement.style.borderRadius = '0'; | |
break; | |
case 'sixteenth': | |
// Double flag | |
noteElement.style.borderRight = '6px solid transparent'; | |
noteElement.style.borderLeft = '6px solid transparent'; | |
noteElement.style.borderBottom = '6px solid white'; | |
noteElement.style.borderRadius = '0'; | |
noteElement.style.boxShadow = '10px 0 0 0 white'; | |
break; | |
} | |
} | |
// Initialize | |
createDrumGrid(); | |
updateGrid(); | |
// Keyboard shortcuts | |
document.addEventListener('keydown', function(e) { | |
if (e.code === 'Space') { | |
e.preventDefault(); | |
if (isPlaying) { | |
stopPlayback(); | |
} else { | |
startPlayback(); | |
} | |
} | |
// Number keys for sounds | |
if (e.code === 'Digit1') { | |
document.querySelector('[data-sound="bass"]').click(); | |
} else if (e.code === 'Digit2') { | |
document.querySelector('[data-sound="snare"]').click(); | |
} else if (e.code === 'Digit3') { | |
document.querySelector('[data-sound="hihat"]').click(); | |
} else if (e.code === 'Digit4') { | |
document.querySelector('[data-sound="rim"]').click(); | |
} | |
}); | |
}); | |
</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=IgorSlinko/drum-machine" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |