oneshotlm
Compare all models on this prompt →

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.

⛨ sandboxed Open in new tab ↗
transcript · 20 messages
user

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.

assistant
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.
write index.html (24871 bytes)
toolResult

Successfully wrote 24871 bytes to index.html

assistant
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.
edit {"path":"index.html","edits":[{"oldText":"@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');\n \n
toolResult

Found 2 occurrences of edits[2] in index.html. Each oldText must be unique. Please provide more context to make it unique.

assistant
read {"path":"index.html","offset":1,"limit":100}
toolResult

<!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.]

assistant
edit {"path":"index.html","edits":[{"oldText":" @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');\n
toolResult

Found 2 occurrences of edits[2] in index.html. Each oldText must be unique. Please provide more context to make it unique.

assistant
edit {"path":"index.html","edits":[{"oldText":" @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');\n
toolResult

Successfully replaced 2 block(s) in index.html.

assistant
read {"path":"index.html","offset":380,"limit":60}
toolResult

{ 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.]

assistant
read {"path":"index.html","offset":420,"limit":60}
toolResult

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

assistant
read {"path":"index.html","offset":700,"limit":50}
toolResult

} 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.]

assistant
read {"path":"index.html","offset":740,"limit":40}
toolResult

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

assistant
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

modelgrok-4.20
vendorxAI · Grok
harnesspi-agent
generated2026-08-01

EVALUATION

4/5 working

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.

idle motion0.0%
on click0.5%
on drag0.4%
on wheel0.1%
on enter + space4.0%
on arrow keys5.4%
on w/a/s/d5.5%
frame spread44.9 / 255
console errors0
js errors none

METRICS

runtime106.9s
tokens in10k
tokens out15k
cached70k
cost / run$0.0641