Spaces:
Running
Running
What happens when u press record, it should save it somewhere as mp3 or something - Follow Up Deployment
9315fe4
verified
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>DJ Beats Pad</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> | |
.pad { | |
transition: all 0.1s ease; | |
box-shadow: 0 5px 0 rgba(0,0,0,0.3); | |
} | |
.pad:active, .pad.active { | |
transform: translateY(5px); | |
box-shadow: 0 2px 0 rgba(0,0,0,0.3); | |
} | |
.bpm-slider::-webkit-slider-thumb { | |
-webkit-appearance: none; | |
appearance: none; | |
width: 24px; | |
height: 24px; | |
border-radius: 50%; | |
background: #7367F0; | |
cursor: pointer; | |
border: 2px solid white; | |
} | |
.metronome-light { | |
transition: all 0.1s ease; | |
box-shadow: 0 0 10px rgba(255,255,255,0.7); | |
background: #7367F0; | |
} | |
.metronome-light.active { | |
background-color: #FF6289; | |
box-shadow: 0 0 15px rgba(255,98,137,0.8); | |
} | |
/* Sequence step colors */ | |
.step.kick { background-color: #10B981; } | |
.step.snare { background-color: #3B82F6; } | |
.step.hihat { background-color: #F59E0B; } | |
.step.clap { background-color: #EF4444; } | |
.step.tom { background-color: #8B5CF6; } | |
.blink { | |
animation: blink 1s infinite; | |
} | |
@keyframes blink { | |
0% { opacity: 1; } | |
50% { opacity: 0.5; } | |
100% { opacity: 1; } | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white min-h-screen flex flex-col"> | |
<div class="container mx-auto px-4 py-8 flex-grow flex flex-col"> | |
<header class="text-center mb-8"> | |
<h1 class="text-4xl font-bold mb-2 bg-gradient-to-r from-purple-500 to-blue-500 bg-clip-text text-transparent">DJ Beats Pad</h1> | |
<p class="text-gray-300">Tap the pads to play sounds. Hold for looping.</p> | |
</header> | |
<div class="flex-grow flex flex-col"> | |
<!-- BPM Control --> | |
<div class="flex items-center justify-center mb-6 gap-4"> | |
<button id="decrease-bpm" class="bg-gray-700 hover:bg-gray-600 rounded-full w-10 h-10 flex items-center justify-center"> | |
<i class="fas fa-minus"></i> | |
</button> | |
<div class="text-center w-32"> | |
<div class="text-lg font-semibold">BPM: <span id="bpm-value">120</span></div> | |
<input type="range" id="bpm-slider" min="60" max="180" value="120" | |
class="bpm-slider w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"> | |
</div> | |
<button id="increase-bpm" class="bg-gray-700 hover:bg-gray-600 rounded-full w-10 h-10 flex items-center justify-center"> | |
<i class="fas fa-plus"></i> | |
</button> | |
<button id="toggle-play" class="ml-4 px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-700 flex items-center gap-2"> | |
<i class="fas fa-play" id="play-icon"></i> | |
<span>Play</span> | |
</button> | |
<div class="metronome-light w-4 h-4 rounded-full bg-gray-600 ml-4"></div> | |
</div> | |
<!-- Beat Pads --> | |
<div class="grid grid-cols-4 gap-4 md:gap-6 mb-8"> | |
<!-- Row 1 --> | |
<div class="pad bg-red-500 aspect-square rounded-lg flex items-center justify-center cursor-pointer text-2xl font-bold" data-sound="clap"> | |
Clap | |
</div> | |
<div class="pad bg-yellow-500 aspect-square rounded-lg flex items-center justify-center cursor-pointer text-2xl font-bold" data-sound="hihat"> | |
Hi-Hat | |
</div> | |
<div class="pad bg-green-500 aspect-square rounded-lg flex items-center justify-center cursor-pointer text-2xl font-bold" data-sound="kick"> | |
Kick | |
</div> | |
<div class="pad bg-blue-500 aspect-square rounded-lg flex items-center justify-center cursor-pointer text-2xl font-bold" data-sound="openhat"> | |
Open Hat | |
</div> | |
<!-- Row 2 --> | |
<div class="pad bg-purple-500 aspect-square rounded-lg flex items-center justify-center cursor-pointer text-2xl font-bold" data-sound="boom"> | |
Boom | |
</div> | |
<div class="pad bg-pink-500 aspect-square rounded-lg flex items-center justify-center cursor-pointer text-2xl font-bold" data-sound="ride"> | |
Ride | |
</div> | |
<div class="pad bg-indigo-500 aspect-square rounded-lg flex items-center justify-center cursor-pointer text-2xl font-bold" data-sound="snare"> | |
Snare | |
</div> | |
<div class="pad bg-teal-500 aspect-square rounded-lg flex items-center justify-center cursor-pointer text-2xl font-bold" data-sound="tom"> | |
Tom | |
</div> | |
</div> | |
<!-- Sequence Recorder --> | |
<div class="bg-gray-800 rounded-lg p-4 mb-6"> | |
<h2 class="text-xl font-semibold mb-3">Sequence Recorder</h2> | |
<div class="grid grid-cols-8 gap-2 mb-4"> | |
<!-- Sequence steps will be added here --> | |
</div> | |
<div class="flex gap-3"> | |
<button id="record-btn" class="px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg flex items-center gap-2"> | |
<i class="fas fa-circle"></i> Record | |
</button> | |
<button id="clear-btn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg flex items-center gap-2"> | |
<i class="fas fa-trash"></i> Clear | |
</button> | |
<button id="save-btn" class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-lg flex items-center gap-2"> | |
<i class="fas fa-save"></i> Save Sequence | |
</button> | |
<div id="audio-recording-status" class="ml-auto text-gray-300 flex items-center gap-2 hidden"> | |
<i class="fas fa-circle text-red-500 blink"></i> | |
<span>Recording audio...</span> | |
</div> | |
<button id="save-audio-btn" class="px-4 py-2 bg-purple-600 hover:bg-purple-700 rounded-lg flex items-center gap-2 hidden"> | |
<i class="fas fa-download"></i> Save Audio | |
</button> | |
</div> | |
</div> | |
</div> | |
<footer class="text-center text-gray-400 text-sm mt-8"> | |
Tap pads to play sounds. Adjust BPM for tempo. Press record to create sequences. | |
</footer> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Audio context and samples | |
const AudioContext = window.AudioContext || window.webkitAudioContext; | |
const audioCtx = new AudioContext(); | |
// Sound samples (using base64 encoded short samples for demonstration) | |
const sounds = { | |
'clap': 'https://assets.codepen.io/1159990/clap.wav', | |
'hihat': 'https://assets.codepen.io/1159990/hihat.wav', | |
'kick': 'https://assets.codepen.io/1159990/kick.wav', | |
'openhat': 'https://assets.codepen.io/1159990/openhat.wav', | |
'boom': 'https://assets.codepen.io/1159990/boom.wav', | |
'ride': 'https://assets.codepen.io/1159990/ride.wav', | |
'snare': 'https://assets.codepen.io/1159990/snare.wav', | |
'tom': 'https://assets.codepen.io/1159990/tom.wav' | |
}; | |
// Load all sounds | |
const soundBuffers = {}; | |
Promise.all(Object.keys(sounds).map(key => | |
fetch(sounds[key]) | |
.then(res => res.arrayBuffer()) | |
.then(arrayBuffer => audioCtx.decodeAudioData(arrayBuffer)) | |
.then(audioBuffer => { soundBuffers[key] = audioBuffer; }) | |
)).then(() => { | |
console.log('All sounds loaded'); | |
}).catch(err => { | |
console.error('Error loading sounds:', err); | |
}); | |
// Play sound function | |
function playSound(soundName) { | |
if (soundBuffers[soundName]) { | |
const source = audioCtx.createBufferSource(); | |
source.buffer = soundBuffers[soundName]; | |
source.connect(audioCtx.destination); | |
source.start(); | |
return source; | |
} | |
return null; | |
} | |
// Pad interactions | |
const pads = document.querySelectorAll('.pad'); | |
let heldPads = {}; | |
pads.forEach(pad => { | |
const soundName = pad.dataset.sound; | |
// Mouse/click events | |
pad.addEventListener('mousedown', () => startPad(soundName, pad)); | |
pad.addEventListener('mouseup', () => stopPad(soundName)); | |
pad.addEventListener('mouseleave', () => stopPad(soundName)); | |
// Touch events for mobile | |
pad.addEventListener('touchstart', (e) => { | |
e.preventDefault(); | |
startPad(soundName, pad); | |
}); | |
pad.addEventListener('touchend', (e) => { | |
e.preventDefault(); | |
stopPad(soundName); | |
}); | |
pad.addEventListener('touchcancel', (e) => { | |
e.preventDefault(); | |
stopPad(soundName); | |
}); | |
}); | |
function startPad(soundName, padElement) { | |
if (!heldPads[soundName]) { | |
padElement.classList.add('active'); | |
playSound(soundName); | |
// For looping on hold | |
heldPads[soundName] = setInterval(() => { | |
playSound(soundName); | |
}, 100); // Quick repeat for hold effect | |
} | |
} | |
function stopPad(soundName) { | |
if (heldPads[soundName]) { | |
clearInterval(heldPads[soundName]); | |
delete heldPads[soundName]; | |
// Remove active class from all pads with this sound | |
document.querySelectorAll(`.pad[data-sound="${soundName}"]`).forEach(pad => { | |
pad.classList.remove('active'); | |
}); | |
} | |
} | |
// BPM control | |
const bpmSlider = document.getElementById('bpm-slider'); | |
const bpmValue = document.getElementById('bpm-value'); | |
const decreaseBpm = document.getElementById('decrease-bpm'); | |
const increaseBpm = document.getElementById('increase-bpm'); | |
const togglePlay = document.getElementById('toggle-play'); | |
const playIcon = document.getElementById('play-icon'); | |
const metronomeLight = document.querySelector('.metronome-light'); | |
let bpm = 120; | |
let isPlaying = false; | |
let playInterval; | |
let beatCounter = 0; | |
bpmSlider.addEventListener('input', () => { | |
bpm = parseInt(bpmSlider.value); | |
bpmValue.textContent = bpm; | |
if (isPlaying) { | |
stopPlayback(); | |
startPlayback(); | |
} | |
}); | |
decreaseBpm.addEventListener('click', () => { | |
bpm = Math.max(60, bpm - 5); | |
bpmSlider.value = bpm; | |
bpmValue.textContent = bpm; | |
if (isPlaying) { | |
stopPlayback(); | |
startPlayback(); | |
} | |
}); | |
increaseBpm.addEventListener('click', () => { | |
bpm = Math.min(180, bpm + 5); | |
bpmSlider.value = bpm; | |
bpmValue.textContent = bpm; | |
if (isPlaying) { | |
stopPlayback(); | |
startPlayback(); | |
} | |
}); | |
togglePlay.addEventListener('click', () => { | |
if (isPlaying) { | |
stopPlayback(); | |
} else { | |
startPlayback(); | |
} | |
}); | |
function startPlayback() { | |
isPlaying = true; | |
playIcon.classList.replace('fa-play', 'fa-pause'); | |
togglePlay.classList.replace('bg-indigo-600', 'bg-indigo-700'); | |
const interval = (60 / bpm) * 1000; // Convert BPM to milliseconds | |
const stepsPerBeat = 4; // 16 steps divided into 4 beats | |
const stepInterval = interval / stepsPerBeat; | |
let currentStep = 0; | |
playInterval = setInterval(() => { | |
beatCounter++; | |
// Flash the metronome light | |
metronomeLight.classList.add('active'); | |
setTimeout(() => metronomeLight.classList.remove('active'), 100); | |
// Play sounds from the sequence | |
if (sequenceSteps[currentStep]?.sound) { | |
playSound(sequenceSteps[currentStep].sound); | |
} | |
// Highlight current step | |
const steps = document.querySelectorAll('.step'); | |
steps.forEach(step => step.classList.remove('ring-2', 'ring-white')); | |
if (steps[currentStep]) { | |
steps[currentStep].classList.add('ring-2', 'ring-white'); | |
} | |
currentStep = (currentStep + 1) % 16; | |
}, stepInterval); | |
} | |
function stopPlayback() { | |
isPlaying = false; | |
playIcon.classList.replace('fa-pause', 'fa-play'); | |
togglePlay.classList.replace('bg-indigo-700', 'bg-indigo-600'); | |
clearInterval(playInterval); | |
beatCounter = 0; | |
metronomeLight.classList.remove('active'); | |
} | |
// Sequence recorder functionality | |
const recordBtn = document.getElementById('record-btn'); | |
const clearBtn = document.getElementById('clear-btn'); | |
const saveBtn = document.getElementById('save-btn'); | |
const sequenceGrid = document.querySelector('.grid.grid-cols-8.gap-2.mb-4'); | |
let isRecording = false; | |
let recordedSequence = []; | |
let recordingStartTime; | |
let sequenceSteps = Array(16).fill(null).map(() => ({})); | |
// Initialize sequence grid | |
function initSequenceGrid() { | |
sequenceGrid.innerHTML = ''; | |
for (let i = 0; i < 16; i++) { | |
const step = document.createElement('div'); | |
step.className = 'h-8 bg-gray-700 rounded cursor-pointer hover:bg-gray-600 step'; | |
step.dataset.step = i; | |
step.addEventListener('click', (e) => { | |
const sounds = ['kick', 'snare', 'hihat', 'clap', 'tom']; | |
// Find next sound in the cycle | |
let nextSound = 'kick'; | |
if (sequenceSteps[i].sound) { | |
const currentIndex = sounds.indexOf(sequenceSteps[i].sound); | |
nextSound = sounds[(currentIndex + 1) % sounds.length]; | |
} | |
toggleStep(i, nextSound); | |
playSound(nextSound); // Play the sound for feedback | |
}); | |
sequenceGrid.appendChild(step); | |
} | |
} | |
function toggleStep(stepIndex, sound = 'kick') { | |
if (sequenceSteps[stepIndex].sound === sound) { | |
sequenceSteps[stepIndex] = {}; | |
} else { | |
sequenceSteps[stepIndex] = { sound: sound }; | |
} | |
updateSequenceGrid(); | |
} | |
function updateSequenceGrid() { | |
const steps = document.querySelectorAll('.step'); | |
steps.forEach((step, i) => { | |
// Reset step classes | |
step.className = 'h-8 bg-gray-700 rounded cursor-pointer hover:bg-gray-600 step'; | |
if (sequenceSteps[i].sound) { | |
step.classList.add(sequenceSteps[i].sound); | |
} | |
}); | |
} | |
recordBtn.addEventListener('click', () => { | |
isRecording = !isRecording; | |
if (isRecording) { | |
recordBtn.innerHTML = '<i class="fas fa-stop"></i> Stop'; | |
recordBtn.classList.remove('bg-red-600'); | |
recordBtn.classList.add('bg-red-700'); | |
recordedSequence = []; | |
recordingStartTime = Date.now(); | |
// Add event listeners to track pad presses | |
pads.forEach(pad => { | |
pad.addEventListener('mousedown', recordPadPress); | |
pad.addEventListener('touchstart', recordPadPress); | |
}); | |
} else { | |
recordBtn.innerHTML = '<i class="fas fa-circle"></i> Record'; | |
recordBtn.classList.add('bg-red-600'); | |
recordBtn.classList.remove('bg-red-700'); | |
// Convert recorded sequence into 16 steps | |
processRecordedSequence(); | |
// Remove event listeners | |
pads.forEach(pad => { | |
pad.removeEventListener('mousedown', recordPadPress); | |
pad.removeEventListener('touchstart', recordPadPress); | |
}); | |
} | |
}); | |
function recordPadPress(e) { | |
e.preventDefault(); | |
const sound = e.target.closest('.pad').dataset.sound; | |
const timestamp = Date.now() - recordingStartTime; | |
recordedSequence.push({ | |
sound: sound, | |
time: timestamp | |
}); | |
} | |
function processRecordedSequence() { | |
if (recordedSequence.length === 0) return; | |
const totalDuration = recordedSequence[recordedSequence.length - 1].time; | |
const stepDuration = totalDuration / 16; | |
// Clear previous sequence | |
sequenceSteps = Array(16).fill(null).map(() => ({})); | |
// Map recorded sounds to steps | |
recordedSequence.forEach(event => { | |
const stepIndex = Math.min(15, Math.floor(event.time / stepDuration)); | |
sequenceSteps[stepIndex].sound = event.sound; | |
}); | |
updateSequenceGrid(); | |
console.log('Processed sequence:', sequenceSteps); | |
} | |
clearBtn.addEventListener('click', () => { | |
sequenceSteps = Array(16).fill(null).map(() => ({})); | |
updateSequenceGrid(); | |
}); | |
saveBtn.addEventListener('click', () => { | |
alert('Sequence saved! (Demo functionality)'); | |
}); | |
// Audio recording functions | |
async function startAudioRecording() { | |
audioChunks = []; | |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
mediaRecorder = new MediaRecorder(stream); | |
mediaRecorder.ondataavailable = (e) => audioChunks.push(e.data); | |
mediaRecorder.onstop = () => { | |
const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' }); | |
const audioUrl = URL.createObjectURL(audioBlob); | |
saveAudioBtn.onclick = () => { | |
const a = document.createElement('a'); | |
a.href = audioUrl; | |
a.download = `dj-mix-${new Date().toISOString().slice(0,19)}.mp3`; | |
a.click(); | |
}; | |
saveAudioBtn.classList.remove('hidden'); | |
}; | |
mediaRecorder.start(); | |
isAudioRecording = true; | |
audioRecordingStatus.classList.remove('hidden'); | |
} | |
function stopAudioRecording() { | |
if (!mediaRecorder) return; | |
mediaRecorder.stop(); | |
mediaRecorder.stream.getTracks().forEach(track => track.stop()); | |
isAudioRecording = false; | |
audioRecordingStatus.classList.add('hidden'); | |
} | |
// Audio recording toggle button | |
document.getElementById('record-btn').addEventListener('click', async (e) => { | |
if (!isAudioRecording) { | |
try { | |
await startAudioRecording(); | |
} catch (err) { | |
console.error('Recording failed:', err); | |
alert('Could not start audio recording. Please check microphone permissions.'); | |
} | |
} else { | |
stopAudioRecording(); | |
} | |
}); | |
// Initialize | |
initSequenceGrid(); | |
// For mobile, we need to handle audio context on touch | |
document.body.addEventListener('touchstart', function() { | |
// Resume audio context on first touch (iOS requirement) | |
if (audioCtx.state === 'suspended') { | |
audioCtx.resume(); | |
} | |
}, { once: true }); | |
document.body.addEventListener('mousedown', function() { | |
// Resume audio context on first click | |
if (audioCtx.state === 'suspended') { | |
audioCtx.resume(); | |
} | |
}, { once: true }); | |
}); | |
</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=AnonymousSub/dj-beatz" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |