Drum machine · grok-4.20
Build a 16-step drum machine with a clickable grid for kick, snare, hi-hat, and clap, all sounds synthesized with the Web Audio API, plus play/stop and an adjustable BPM.
transcript · 20 messages
Build a 16-step drum machine with a clickable grid for kick, snare, hi-hat, and clap, all sounds synthesized with the Web Audio API, plus play/stop and an adjustable BPM.
thinking
The task is: "Build a 16-step drum machine with a clickable grid for kick, snare, hi-hat, and clap, all sounds synthesized with the Web Audio API, plus play/stop and an adjustable BPM."
The task is to build a 16-step drum machine using HTML, CSS, and JS in one file, with Web Audio API for sounds, clickable grid, play/stop, adjustable BPM.
I need to create index.html with everything inside.
First, plan the structure:
- <!DOCTYPE html>
- <html>
- <head>
- <title>Drum Machine</title>
- <style> CSS here </style>
- </head>
- <body>
- <h1>Drum Machine</h1>
- <div> BPM: <input type="range" min="60" max="200" value="120" id="bpm"> <span id="bpmval">120</span> </div>
- <button id="playstop">Play</button>
- <div id="grid" class="grid"> 4 rows x 16 cols </div>
- <script> JS here </script>
- </body>
</html>
For grid: use CSS grid with 4 rows, 16 columns.
Each cell: button or div with onclick toggle.
Better divs with class, toggle class 'active'.
For sounds: define synth functions.
Let's define:
- Kick: 50Hz sine, fast attack, decay to 0.1s
- Snare: white noise + 200Hz sine pulse
- Hi-hat: white noise burst 0.05s, high freq
- Clap: noise + tone
I need to implement AudioContext, oscillators, gains.
For noise : use buffer or oscillator with random phase.
Simple way: create function to play noise.
Let's write the code in mind.
const audioCtx = new AudioContext();
function playKick(time) {
const osc = audioCtx.createOscillator();
osc.type = 'sine';
osc.frequency.setValueAtTime (100, time);
osc.frequency.exponentialRampToValueAtTime(50, time+0.1);
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(1, time);
gain.gain.exponentialRampToValueAtTime(0.01, time+ 0.2);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(time);
osc.stop(time+0.3);
}
Similar for others.
For snare:
function playSnare(time) {
const noise = audioCtx.createBufferSource();
const buffer = audioCtx.createBuffer 1, audioCtx.sampleRate, 1);
// fill with noise
const data = buffer.getChannelData(0);
for (let i=0; i<data.length; i++) data[i] = Math.random()*2-1;
noise.buffer = buffer;
const filter = audioCtx createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 1000;
const gain = audioCtx.createGain();
gain.gain.value = 0.5;
gain.gain.exponentialRampToValueAtTime(0.01, time+0.2 );
noise.connect(filter);
filter.connect(gain);
gain.connect(audioCtx.destination);
noise.start(time);
noise.stop(time+0.3);
// add tone
const osc = audioCtx.createOscillator();
osc.frequency.value = 180;
const oscgain = audioCtx.create Gain();
oscgain.gain.setValueAtTime(0.3, time);
oscgain.gain.exponentialRampToValueAtTime(0.01, time+0.1);
osc.connect(oscgain);
oscgain.connect(audioCtx.destination);
osc.start(time);
osc.stop (time+0.2);
}
Yes.
For hi-hat:
function playHihat(time) {
const noise = audioCtx.createBufferSource();
const buffer = audioCtx.createBuffer(1, audioCtx.sampleRate * 0.05, audioCtx.sampleRate); // short
// fill noise 0.05s
const data = buffer.getChannelData(0);
for (let i=0; i<data.length; i++) data[i] = Math.random()*2-1;
noise.buffer = buffer;
const filter = audioCtx.createBiquadFilter();
filter.type = ' highpass';
filter.frequency.value = 5000;
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.1);
}
For clap:
function playClap(time) {
// two hits
playHihat(time); // short
playHihat(time + 0.02); // delayed 20ms
// or better noise with envelope
// for simplicity, use noise with attack and decay twice
const noise = audioCtx.createBufferSource();
const buffer = audioCtx.createBuffer(1, audioCtx.sampleRate * 0.1, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i=0; i<data.length; i++) data[i] = Math.random()*2-1;
noise.buffer = buffer;
const filter = audioCtx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 1500;
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(0, time);
gain.gain.linearRampToValueAtTime(1, time+0.005);
gain.gain.linearRampToValueAtTime(0, time+0.1);
gain.gain.linearRampToValueAtTime(1, time+0.12);
gain.gain.linearRampToValueAtTime(0, time+0.2);
noise.connect(filter);
filter.connect(gain);
gain.connect(audioCtx.destination);
noise.start(time);
noise.stop(time +0.25);
}
Yes, that works for clap.
For the grid: use CSS grid.
.container {
display: grid;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: repeat(4, 60px);
gap: 2px;
background: #222;
padding: 10px;
}
Then each cell is a div with class "pad" data-inst data-step
On click toggle active class.
For labels: add a column for labels.
So grid-template-columns: 100px repeat(16,1fr);
Then label in first column, 4 rows.
Yes.
For play/stop: button that starts the loop.
let bpm = 120;
let isPlaying = false;
let currentStep = 0;
let timeout;
const stepDuration = 60000 / (bpm * 4); // ms
function togglePlay() {
if (is Playing) {
isPlaying = false;
clearTimeout(timeout);
button.textContent = 'Play';
currentStep = 0;
clear highlights
} else {
isPlaying = true;
button.textContent = 'Stop';
audioCtx.index.html (24871 bytes)Successfully wrote 24871 bytes to index.html
thinking
The task is complete. I have built a 16-step drum machine as a single self-contained index.html file. It includes: The user is asking me to build a 16-step drum machine with specific features. But the message seems cut off or like it's providing an example.
{"path":"index.html","edits":[{"oldText":"@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');\n \n Found 2 occurrences of edits[2] in index.html. Each oldText must be unique. Please provide more context to make it unique.
{"path":"index.html","offset":1,"limit":100}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>NEON DRUM • 16</title> <style> @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); :root { --kick: #ff2a6d; --snare: #00f7ff; --hihat: #7aff6b; --clap: #ffe600; } * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #0d0d0d; color: #00ffcc; font-family: 'Press Start 2P', system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; overflow: hidden; position: relative; } body::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(rgba(0, 255, 200, 0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 255, 200, 0.03) 1px, transparent 1px); background-size: 40px 40px; pointer-events: none; z-index: 0; } .container { width: 920px; background: #111111; border: 8px solid #00ffcc; box-shadow: 0 0 40px #00ffcc, 0 0 80px rgba(0, 255, 204, 0.3); padding: 20px; position: relative; z-index: 1; } header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 4px solid #222; } .title { font-size: 28px; letter-spacing: 4px; color: #00ffcc; text-shadow: 0 0 20px #00ffcc; display: flex; align-items: center; gap: 15px; } .title::before { content: '◉'; color: #ff2a6d; animation: pulse 1s infinite alternate; } .controls { display: flex; align-items: center; gap: 30px; } .bpm-control { display: flex; align-items: center; gap: 12px; background: #1a1a1a; padding: 8px 18px; border: 3px solid #222; } [667 more lines in file. Use offset=101 to continue.]
{"path":"index.html","edits":[{"oldText":" @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');\n Found 2 occurrences of edits[2] in index.html. Each oldText must be unique. Please provide more context to make it unique.
{"path":"index.html","edits":[{"oldText":" @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');\n Successfully replaced 2 block(s) in index.html.
{"path":"index.html","offset":380,"limit":60}{ id: 'hihat', name: 'HI-HAT', color: '#7aff6b', emoji: '⚡' }, { id: 'clap', name: 'CLAP', color: '#ffe600', emoji: '👏' } ]; function generateNoiseBuffer() { const bufferSize = audioCtx.sampleRate * 3; const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); const output = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) { output[i] = Math.random() * 2 - 1; } return buffer; } function triggerKick(time) { // Main oscillator with pitch drop const osc = audioCtx.createOscillator(); osc.frequency.setValueAtTime(170, time); osc.frequency.exponentialRampToValueAtTime(45, time + 0.32); const gain = audioCtx.createGain(); gain.gain.setValueAtTime(1.2, time); gain.gain.exponentialRampToValueAtTime(0.001, time + 0.45); // Click noise const noise = audioCtx.createBufferSource(); noise.buffer = noiseBuffer; const noiseFilter = audioCtx.createBiquadFilter(); noiseFilter.type = 'lowpass'; noiseFilter.frequency.value = 650; const noiseGain = audioCtx.createGain(); noiseGain.gain.setValueAtTime(0.9, time); noiseGain.gain.exponentialRampToValueAtTime(0.001, time + 0.08); const master = audioCtx.createGain(); master.gain.value = 0.85; osc.connect(gain).connect(master); noise.connect(noiseFilter).connect(noiseGain).connect(master); master.connect(audioCtx.destination); osc.start(time); noise.start(time); osc.stop(time + 0.6); noise.stop(time + 0.6); } function triggerSnare(time) { // Noise component const noise = audioCtx.createBufferSource(); noise.buffer = noiseBuffer; const noiseFilter = audioCtx.createBiquadFilter(); noiseFilter.type = 'highpass'; noiseFilter.frequency.setValueAtTime(850, time); const noiseGain = audioCtx.createGain(); noiseGain.gain.setValueAtTime(1, time); noiseGain.gain.exponentialRampToValueAtTime(0.001, time + 0.3); // Tone component const tone = audioCtx.createOscillator(); [326 more lines in file. Use offset=440 to continue.]
{"path":"index.html","offset":420,"limit":60}osc.start(time); noise.start(time); osc.stop(time + 0.6); noise.stop(time + 0.6); } function triggerSnare(time) { // Noise component const noise = audioCtx.createBufferSource(); noise.buffer = noiseBuffer; const noiseFilter = audioCtx.createBiquadFilter(); noiseFilter.type = 'highpass'; noiseFilter.frequency.setValueAtTime(850, time); const noiseGain = audioCtx.createGain(); noiseGain.gain.setValueAtTime(1, time); noiseGain.gain.exponentialRampToValueAtTime(0.001, time + 0.3); // Tone component const tone = audioCtx.createOscillator(); tone.frequency.setValueAtTime(240, time); const toneGain = audioCtx.createGain(); toneGain.gain.setValueAtTime(0.6, time); toneGain.gain.exponentialRampToValueAtTime(0.001, time + 0.18); const master = audioCtx.createGain(); master.gain.value = 0.75; // Light distortion simulation const filter = audioCtx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 1800; noise.connect(noiseFilter).connect(noiseGain).connect(filter).connect(master); tone.connect(toneGain).connect(filter).connect(master); master.connect(audioCtx.destination); noise.start(time); tone.start(time); noise.stop(time + 0.5); tone.stop(time + 0.4); } function triggerHihat(time) { const noise = audioCtx.createBufferSource(); noise.buffer = noiseBuffer; const filter = audioCtx.createBiquadFilter(); filter.type = 'highpass'; filter.frequency.setValueAtTime(7200, time); filter.Q.value = 8; const gain = audioCtx.createGain(); gain.gain.setValueAtTime(0.65, time); gain.gain.exponentialRampToValueAtTime(0.001, time + 0.085); const master = audioCtx.createGain(); master.gain.value = 0.7; noise.connect(filter).connect(gain).connect(master); [286 more lines in file. Use offset=480 to continue.]
{"path":"index.html","offset":700,"limit":50}} else { el.classList.remove('active'); } }); } function handleKeyboard(e) { if (e.key === ' ' || e.key === 'k' || e.key === 'K') { e.preventDefault(); togglePlayback(); } if (e.key === 'r' || e.key === 'R') { randomize(); } if (e.key === 'c' || e.key === 'C') { clearAll(); } } function init() { // Generate noise buffer noiseBuffer = generateNoiseBuffer(); // Initialize sequence with a cool pattern initSequence(); // Build UI createStepLabels(); createTracks(); // BPM slider const bpmSlider = document.getElementById('bpm-slider'); const bpmDisplay = document.getElementById('bpm-display'); bpmDisplay.textContent = bpm; bpmSlider.value = bpm; bpmSlider.addEventListener('input', (e) => { updateBPM(e.target.value); }); // Play button const playBtn = document.getElementById('play-btn'); playBtn.addEventListener('click', togglePlayback); // Clear button document.getElementById('clear-btn').addEventListener('click', clearAll); // Random button document.getElementById('random-btn').addEventListener('click', randomize); [16 more lines in file. Use offset=750 to continue.]
{"path":"index.html","offset":740,"limit":40}// Play button const playBtn = document.getElementById('play-btn'); playBtn.addEventListener('click', togglePlayback); // Clear button document.getElementById('clear-btn').addEventListener('click', clearAll); // Random button document.getElementById('random-btn').addEventListener('click', randomize); // Keyboard support document.addEventListener('keydown', handleKeyboard); // Touch feedback for mobile document.documentElement.style.setProperty('--accent', '#00ffcc'); // Demo: start with a short silent pulse to unlock audio (but only on interaction) console.log('%cNEON DRUM ready. Click PLAY or press SPACE.', 'color:#00ffcc; font-family:monospace'); } // Initialize the app window.onload = init; </script> </body> </html>
thinking
The app is complete. It's a self-contained index.html with a 16-step drum machine featuring kick, snare, hi-hat, and clap. All sounds are synthesized using the Web Audio API with oscillators, noise bu...
The file **index.html** has been created with a complete, self-contained 16-step drum machine. Open it in any modern browser (Chrome/Firefox/Safari recommended for best Web Audio performance). It includes: - **4 instruments** (Kick, Snare, Hi-Hat, Clap) with dedicated rows. - **Clickable 16-step grid** — toggle steps on/off (bright neon active states). - **Real-time synthesized drums** via Web Audio API (oscillators + noise + filters + envelopes for punchy analog-style sounds; no samples). - **Play/Stop** button (also triggered by **Spacebar**). - **BPM control** (60–200 via slider; updates tempo instantly). - **Visual sequencer** — current step pulses brightly + LED indicator. - **Extra controls**: Clear All, Randomize pattern (also via **R** and **C** keys). - Fully responsive retro-neon cyberpunk UI with glows, animations, and step labels. Everything runs sandboxed with zero network/CDN dependencies. Enjoy making beats!
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
16-step drum grid with kick/snare/hi-hat/clap, BPM slider, play/stop; click toggles cells, space starts playback with step indicator moving.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.