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> |