< < < < <Web Audio Drum Machine < :root { --bg-color: #1a1a1a; --card-color: #2d2d2d; --text-color: #e0e0e0; --accent-color: #00adb5; --kick-color: #ff4d4d; --snare-color: #ffaf40; --hihat-color: #fffa65; --clap-color: #32ff7e; --step-off: #444; --step-on: #fff; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: var(--bg-color); color: var(--text-color); display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; } .container { background-color: var(--card-color); padding: 2rem; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); width: 90%; max-width: 800px; } h1 { text-align: center; margin-top: 0; color: var(--accent-color); } .controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; flex-wrap: wrap; gap: 1rem; } .btn { padding: 0.8rem 1.5rem; font-size: 1rem; font-weight: bold; cursor: pointer; border: none; border-radius: 4px; transition: opacity 0.2s, transform 0.1s; user-select: none; } .btn:active { transform: scale(0.95); } #play-stop-btn { background-color: var(--accent-color); color: white; min-width: 100px; } #play-stop-btn.playing { background-color: #ff4d4d; } .bpm-control { display: flex; align-items: center; gap: 10px; } input[type="range"] { cursor: pointer; } .grid { display: grid; grid-template-columns: 100px repeat(16, 1fr); gap: 5px; align-items: center; } .track-label { font-weight: bold; font-size: 0.9rem; } .step { aspect-ratio: 1/1; background-color: var(--step-off); border-radius: 3px; cursor: pointer; transition: background-color 0.1s; } .step.active { background-color: var(--step-on); box-shadow: 0 0 8px var(--step-on); } .step.playing-now { outline: 2px solid white; outline-offset: 2px; } /* Color coding for active steps by instrument type */ .step[data-instr="kick"].active { background-color: var(--kick-color); box-shadow: 0 0 8px var(--kick-color); } .step[data-instr="snare"].active { background-color: var(--snare-color); box-shadow: 0 0 8px var(--snare-color); } .step[data-instr="hihat"].active { background-color: var(--hihat-color); box-shadow: 0 0 8px var(--hihat-color); } .step[data-instr="clap"].active { background-color: var(--clap-color); box-shadow: 0 0 8px var(--clap-color); } @media (max-width: 600px) { .grid { grid-template-columns: 60px repeat(16, 1fr); } .track-label { font-size: 0.7rem; } } <

SynthDrum 16

< <PLAY < <BPM: <120 < < < // Audio Context Setup let audioCtx; let isPlaying = false; let currentStep = 0; let nextStepTime = 0; let bpm = 120; let timerID; const instruments = ['kick', 'snare', 'hihat', 'clap']; const stepsCount = 16; const sequence = instruments.map(() => Array(stepsCount).fill(false)); // DOM Elements const gridContainer = document.getElementById('drum-grid'); const playStopBtn = document.getElementById('play-stop-btn'); const bpmSlider = document.getElementById('bpm-slider'); const bpmValDisplay = document.getElementById('bpm-val'); // Initialize Grid function createGrid() { gridContainer.innerHTML = ''; instruments.forEach((instr, instrIdx) => { const label = document.createElement('div'); label.className = 'track-label'; label.textContent = instr.toUpperCase(); gridContainer.appendChild(label); for (let s = 0; s << steps stepsCount; s++) { const step = document.createElement('div'); step.className = 'step'; step.dataset.instr = instr; step.dataset.step = s; step.addEventListener('click', () => toggleStep(instrIdx, s, step)); gridContainer.appendChild(step); } }); } function toggleStep(instrIdx, stepIdx, element) { sequence[instrIdx][stepIdx] = !sequence[instrIdx][stepIdx]; element.classList.toggle('active'); } // --- SYNTHESIZERS --- function initAudio() { if (!audioCtx) { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } } function playKick(time) { const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.connect(gain); gain.connect(audioCtx.destination); osc.frequency.setValueAtTime(150, time); osc.frequency.exponentialRampToValueAtTime(0.01, time + 0.5); gain.gain.setValueAtTime(1, time); gain.gain.exponentialRampToValueAtTime(0.01, time + 0.5); osc.start(time); osc.stop(time + 0.5); } function playSnare(time) { // Noise component const bufferSize = audioCtx.sampleRate * 0.2; const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i << buffer bufferSize; i++) { data[i] = Math.random() * 2 - 1; } const noise = audioCtx.createBufferSource(); noise.buffer = buffer; const noiseFilter = audioCtx.createBiquadFilter(); noiseFilter.type = 'highpass'; noiseFilter.frequency.setValueAtTime(1000, time); const noiseGain = audioCtx.createGain(); noiseGain.gain.setValueAtTime(0.5, time); noiseGain.gain.exponentialRampToValueAtTime(0.01, time + 0.2); noise.connect(noiseFilter); noiseFilter.connect(noiseGain); noiseGain.connect(audioCtx.destination); // Tone component (the "snap") const osc = audioCtx.createOscillator(); const oscGain = audioCtx.createGain(); osc.type = 'triangle'; osc.frequency.setValueAtTime(250, time); oscGain.gain.setValueAtTime(0.5, time); oscGain.gain.exponentialRampToValueAtTime(0.01, time + 0.1); osc.connect(oscGain); oscGain.connect(audioCtx.destination); noise.start(time); osc.start(time); noise.stop(time + 0.2); osc.stop(time + 0.2); } function playHiHat(time) { const bufferSize = audioCtx.sampleRate * 0.05; const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i << buffer bufferSize; i++) { data[i] = Math.random() * 2 - 1; } const noise = audioCtx.createBufferSource(); noise.buffer = buffer; const filter = audioCtx.createBiquadFilter(); filter.type = 'highpass'; filter.frequency.setValueAtTime(7000, time); const gain = audioCtx.createGain(); gain.gain.setValueAtTime(0.3, time); gain.gain.exponentialRampToValueAtTime(0.01, time + 0.05); noise.connect(filter); filter.connect(gain); gain.connect(audioCtx.destination); noise.start(time); noise.stop(time + 0.05); } function playClap(time) { // Clap is basically a series of fast noise bursts const numBursts = 6; const burstDuration = 0.02; for (let i = 0; i << num numBursts; i++) { const burstTime = time + (i * 0.01); const bufferSize = audioCtx.sampleRate * burstDuration; const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); const data = buffer.getChannelData(0); for (let j = 0; j << buffer bufferSize; j++) { data[j] = Math.random() * 2 - 1; } const noise = audioCtx.createBufferSource(); noise.buffer = buffer; const filter = audioCtx.createBiquadFilter(); filter.type = 'bandpass'; filter.frequency.setValueAtTime(1200, burstTime); const gain = audioCtx.createGain(); gain.gain.setValueAtTime(0.4, burstTime); gain.gain.exponentialRampToValueAtTime(0.01, burstTime + burstDuration); noise.connect(filter); filter.connect(gain); gain.connect(audioCtx.destination); noise.start(burstTime); noise.stop(burstTime + burstDuration); } } // --- SCHEDULER --- function scheduler() { while (nextStepTime << audio audioCtx.currentTime + 0.1) { scheduleStep(currentStep, nextStepTime); advanceStep(); } timerID = requestAnimationFrame(scheduler); } function advanceStep() { const secondsPerBeat = 60.0 / bpm / 4; // 16th notes nextStepTime += secondsPerBeat; currentStep = (currentStep + 1) % stepsCount; } function scheduleStep(step, time) { // Visual update // We use setTimeout for visual because the scheduler works on audio time setTimeout(() => { updateVisuals(step); }, (time - audioCtx.currentTime) * 1000); // Audio trigger if (sequence[0][step]) playKick(time); if (sequence[1][step]) playSnare(time); if (sequence[2][step]) playHiHat(time); if (sequence[3][step]) playClap(time); } function updateVisuals(step) { // Remove highlighting from all steps const allSteps = document.querySelectorAll('.step'); allSteps.forEach(s => s.classList.remove('playing-now')); // Add highlighting to the current step column const currentColumnSteps = document.querySelectorAll(`.step[data-step="${step}"]`); currentColumnSteps.forEach(s => s.classList.add('playing-now')); } // --- EVENT HANDLERS --- playStopBtn.addEventListener('click', () => { initAudio(); if (audioCtx.state === 'suspended') { audioCtx.resume(); } isPlaying = !isPlaying; if (isPlaying) { currentStep = 0; nextStepTime = audioCtx.currentTime; playStopBtn.textContent = 'STOP'; playStopBtn.classList.add('playing'); scheduler(); } else { playStopBtn.textContent = 'PLAY'; playStopBtn.classList.remove('playing'); cancelAnimationFrame(timerID); // Remove all visual highlights document.querySelectorAll('.step').forEach(s => s.classList.remove('playing-now')); } }); bpmSlider.addEventListener('input', (e) => { bpm = e.target.value; bpmValDisplay.textContent = bpm; }); // Initialization createGrid();