dj-beatz / index.html
AnonymousSub's picture
What happens when u press record, it should save it somewhere as mp3 or something - Follow Up Deployment
9315fe4 verified
<!DOCTYPE html>
<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>