---
id: 6762ec275cef87635acc4fe3
title: Build a Drum Machine
challengeType: 25
dashedName: build-drum-machine
demoType: onClick
saveSubmissionToDB: true
---

# --description--

**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.

**User Stories:**

1. You should have a `div` element with an `id` of `drum-machine` that contains all other elements.

2. Inside the `#drum-machine` element you should have another `div` with an `id` of `pad-bank`.

3. Inside the `#drum-machine` element you should have a `p` element with an `id` of `display`.

4. Inside your `#pad-bank` element you should have nine clickable drum pad elements each with a class of `drum-pad`, a unique `id` that describes the audio clip the drum pad will be set up to trigger, and an inner text that corresponds to one of the following keys on the keyboard: `Q`, `W`, `E`, `A`, `S`, `D`, `Z`, `X`, `C`. The drum pads MUST be in this order.

5. Each `.drum-pad` should have an `audio` element which has a class of `clip`, a `src` attribute that points to an audio clip, and an `id` corresponding to the inner text of its parent `.drum-pad` element (e.g. `id="Q"`, `id="W"`, `id="E"` etc.).

6. When you click on a `.drum-pad` element, the audio clip contained in its child audio element should be triggered.

7. When you press the trigger key associated with each `.drum-pad`, the audio clip contained in its child audio element should be triggered (e.g. pressing the `Q` key should trigger the drum pad which contains the string `Q`, pressing the `W` key should trigger the drum pad which contains the string `W`, etc.).

8. When a `.drum-pad` is triggered, you should display a string describing the associated audio clip as the inner text of the `#display` element (each string must be unique).

Some audio samples you can use for your drum machine can be found at `https://cdn.freecodecamp.org/curriculum/drum/<fileName>`, where `<fileName>` is as follows:

| drum name | `fileName` |
| --- | --- |
| Heater 1 | `Heater-1.mp3` |
| Heater 2 | `Heater-2.mp3` |
| Heater 3 | `Heater-3.mp3` |
| Heater 4 | `Heater-4_1.mp3` |
| Clap | `Heater-6.mp3` |
| Open-HH | `Dsc_Oh.mp3` |
| Kick-n'-Hat | `Kick_n_Hat.mp3` |
| Kick | `RP4_KICK_1.mp3` |
| Closed-HH | `Cev_H2.mp3` |

# --hints--

You should have a `div` element with an `id` of `drum-machine` that contains all other elements.

```js
assert.isNotNull(document.querySelector('div#drum-machine'));
```

Inside the `#drum-machine` element you should have another `div` with an `id` of `pad-bank`.

```js
assert.isNotNull(document.querySelector('#drum-machine div#pad-bank'));
```

Inside the `#drum-machine` element you should have a `p` element with an `id` of `display`.

```js
assert.isNotNull(document.querySelector('#drum-machine p#display'));
```

Inside your `#pad-bank` element you should have nine clickable drum pad elements each with a class of `drum-pad`.

```js
assert.lengthOf(document.querySelectorAll('#pad-bank .drum-pad'), 9);
```

Each `.drum-pad` should be a `button` element.

```js
const pads = document.querySelectorAll('.drum-pad');
pads.forEach(pad => {
  assert.equal(pad.tagName, 'BUTTON');
});
```

Each `.drum-pad` should have one of the following letters as `innerText`, in order: `Q`, `W`, `E`, `A`, `S`, `D`, `Z`, `X`, `C`.

```js
const pads = document.querySelectorAll('.drum-pad');
const letters = [`Q`, `W`, `E`, `A`, `S`, `D`, `Z`, `X`, `C`];
for (let i = 0; i < 9; i++) {
  assert.equal(pads[i].innerText.trim(), letters[i]);
}
```

Each `.drum-pad` should have an `audio` element which has a class of `clip`, a `src` attribute that points to an audio clip, and an `id` corresponding to the inner text of its parent `.drum-pad` element (e.g. `id="Q"`, `id="W"`, `id="E"` etc.).

```js
const letters = [`Q`, `W`, `E`, `A`, `S`, `D`, `Z`, `X`, `C`];
const audios = document.querySelectorAll('#drum-machine .drum-pad audio');

for (let i = 0; i < 9; i++) {
  assert.equal(audios[i].id, letters[i]);
  assert.isTrue(audios[i].classList.contains('clip'));
  assert.isTrue(audios[i].hasAttribute('src'));
}
```

When you click on a `.drum-pad` element, the audio clip contained in its child audio element should be triggered.

```js
const pads = document.querySelectorAll('.drum-pad');
assert.isNotEmpty(pads);
pads.forEach(el => {
  const audio = el.querySelector('audio');
  el.click();
  assert.isFalse(audio.paused);
  audio.pause();
});
```

When you press one of the keys Q, W, E, A, S, D, Z, X, C on your keyboard, the corresponding `audio` element should play the corresponding sound.

```js
const letters = [`Q`, `W`, `E`, `A`, `S`, `D`, `Z`, `X`, `C`];
const audios = document.querySelectorAll('#drum-machine .drum-pad audio');

for (let i = 0; i < 9; i++) {
  const audio = audios[i];
  document.dispatchEvent(
    new KeyboardEvent('keydown', { key: letters[i].toLowerCase() })
  );
  assert.isFalse(audio.paused);
  audio.pause();
}
```

When a `.drum-pad` is triggered, you should display a string describing the associated audio clip as the inner text of the `#display` element (each string must be unique).

```js
const collection = new Set();
const disp = document.querySelector('#display');
const pads = document.querySelectorAll('.drum-pad');
assert.lengthOf(pads, 9);
pads.forEach(el => {
  const audio = el.querySelector('audio');
  el.click();
  collection.add(disp.innerText.trim());
  audio.pause();
});
assert.lengthOf(collection, 9);
```

# --seed--

## --seed-contents--

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Drum Machine</title>
  </head>
  <body></body>
</html>
```

```css

```

```js

```

# --solutions--

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Drum Machine</title>
    <link rel="stylesheet" href="styles.css" />
  </head>

  <body>
    <div class="inner-container" id="drum-machine">
      <div id="pad-bank"></div>

      <div class="controls-container">
        <div class="control power-control">
          <label class="select">
            <span>Power: <span id="power-status">On</span></span>
            <span class="toggle-container">
              <input type="checkbox" id="power-toggle" checked />
              <span class="slider"></span>
            </span>
          </label>
        </div>

        <!-- Display for Drum Pad Name and Volume -->
        <p id="display"></p>
        
        <div class="volume-slider">
          <label for="volume-control">Volume</label>
          <input type="range" id="volume-control" min="0" max="1" step="0.01" value="0.3" />
        </div>

        <div class="control bank-control">
          <label class="select">
            <span>Bank: <span id="current-bank">Heater Kit</span></span>
            <span class="toggle-container">
              <input type="checkbox" id="bank-toggle" checked />
              <span class="slider"></span>
            </span>
          </label>
        </div>
      </div>
    </div>

    <script src="script.js"></script>
  </body>
</html>
```

```css
/* General Reset */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: 'Arial', sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background-color: #282c34;
  color: white;
}

.inner-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: #3b3b5f;
  border-radius: 8px;
  padding: 20px;
  width: 300px;
}

/* Drum Pad Styles */
#pad-bank {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-bottom: 15px;
}

.drum-pad {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 70px;
  height: 70px;
  background: #666;
  border: none;
  border-radius: 5px;
  font-size: 1.2em;
  color: #fff;
  cursor: pointer;
  font-family: inherit;
}

.drum-pad:hover {
  background: #777;
}

.drum-pad:focus-visible {
  outline: 2px solid #0d6efd;
  outline-offset: 2px;
}

.drum-pad.active {
  background: #ffaa33;
  color: #000;
}

.controls-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
}

.control {
  display: flex;
  justify-content: space-between;
  width: 100%;
  margin: 10px 0;
}

.select {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
}

.toggle-container {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 50px;
  height: 24px;
  flex: 0 0 auto;
}

.toggle-container input[type="checkbox"] {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
}

.toggle-container .slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: #6c757d; /* default gray for inactive state */
  border-radius: 12px;
  transition: background 0.25s ease, box-shadow 0.25s ease;
}

.toggle-container .slider::before {
  position: absolute;
  content: "";
  width: 18px;
  height: 18px;
  left: 1px;
  bottom: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.25s ease, background 0.25s ease;
  box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}

/* move knob and set green background */
.toggle-container input:checked + .slider {
  background: #28a745;
}

.toggle-container input:checked + .slider::before {
  transform: translateX(26px);
}

/* add glow when active */
.toggle-container input#power-toggle:checked + .slider {
  box-shadow: 0 0 0 4px rgba(40,167,69,0.12);
}

/* Bank toggle: always show green and glow in both states */
.toggle-container input#bank-toggle + .slider {
  background: #28a745;
  box-shadow: 0 0 0 4px rgba(40,167,69,0.12);
}

.toggle-container input:focus-visible + .slider {
  outline: 2px solid #0d6efd;
  outline-offset: 2px;
}

#display {
  margin: 15px 0;
  text-align: center;
  font-size: 1em;
  padding: 20px;
  background: #444;
  border-radius: 5px;
  width: 100%;
}

.volume-slider {
  width: 100%;
}

.volume-slider label {
  display: block;
  margin-bottom: 5px;
  font-size: 0.9em;
}

.volume-slider input[type='range'] {
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  background: #ffaa33;
  border-radius: 2px;
  margin-bottom: 10px;
}

.volume-slider input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  background: #fff;
  border-radius: 50%;
  cursor: pointer;
}

.volume-slider input[type='range']:focus-visible {
  outline: 2px solid #0d6efd;
  outline-offset: 2px;
}
```

```js
const projectName = 'drum-machine';

const bankOne = [
  {
    keyCode: 81,
    keyTrigger: 'Q',
    id: 'Heater-1',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Heater-1.mp3'
  },
  {
    keyCode: 87,
    keyTrigger: 'W',
    id: 'Heater-2',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Heater-2.mp3'
  },
  {
    keyCode: 69,
    keyTrigger: 'E',
    id: 'Heater-3',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Heater-3.mp3'
  },
  {
    keyCode: 65,
    keyTrigger: 'A',
    id: 'Heater-4',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Heater-4_1.mp3'
  },
  {
    keyCode: 83,
    keyTrigger: 'S',
    id: 'Clap',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Heater-6.mp3'
  },
  {
    keyCode: 68,
    keyTrigger: 'D',
    id: 'Open-HH',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Dsc_Oh.mp3'
  },
  {
    keyCode: 90,
    keyTrigger: 'Z',
    id: "Kick-n'-Hat",
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Kick_n_Hat.mp3'
  },
  {
    keyCode: 88,
    keyTrigger: 'X',
    id: 'Kick',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/RP4_KICK_1.mp3'
  },
  {
    keyCode: 67,
    keyTrigger: 'C',
    id: 'Closed-HH',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Cev_H2.mp3'
  }
];

const bankTwo = [
  {
    keyCode: 81,
    keyTrigger: 'Q',
    id: 'Chord-1',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Chord_1.mp3'
  },
  {
    keyCode: 87,
    keyTrigger: 'W',
    id: 'Chord-2',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Chord_2.mp3'
  },
  {
    keyCode: 69,
    keyTrigger: 'E',
    id: 'Chord-3',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Chord_3.mp3'
  },
  {
    keyCode: 65,
    keyTrigger: 'A',
    id: 'Shaker',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Give_us_a_light.mp3'
  },
  {
    keyCode: 83,
    keyTrigger: 'S',
    id: 'Open-HH',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Dry_Ohh.mp3'
  },
  {
    keyCode: 68,
    keyTrigger: 'D',
    id: 'Closed-HH',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Bld_H1.mp3'
  },
  {
    keyCode: 90,
    keyTrigger: 'Z',
    id: 'Punchy-Kick',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/punchy_kick_1.mp3'
  },
  {
    keyCode: 88,
    keyTrigger: 'X',
    id: 'Side-Stick',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/side_stick_1.mp3'
  },
  {
    keyCode: 67,
    keyTrigger: 'C',
    id: 'Snare',
    url: 'https://cdn.freecodecamp.org/curriculum/drum/Brk_Snr.mp3'
  }
];

const state = {
  power: true,
  display: ' ',
  currentPadBank: bankOne,
  currentPadBankId: 'Heater Kit',
  sliderVal: 0.3
};

const display = document.getElementById('display');

const setDisplay = text => {
  state.display = text;
  display.innerText = text;
};

const adjustVolume = e => {
  if (state.power) {
    state.sliderVal = e.target.value;
    setDisplay(`Volume: ${Math.round(e.target.value * 100)}`);
    setTimeout(() => setDisplay(' '), 1000);
    document.querySelectorAll('.clip').forEach(sound => {
      sound.volume = state.sliderVal;
    });
  }
};

const activatePad = pad => {
  pad.classList.add('active');
  setTimeout(() => pad.classList.remove('active'), 100);
};

const playSound = (id, name) => {
  if (!state.power) return;

  const sound = document.getElementById(id);
  sound.currentTime = 0;
  sound.play();
  activatePad(sound.parentNode);
  setDisplay(name);
};

const renderPadBank = () => {
  const padBank = document.querySelector('#pad-bank');
  padBank.innerHTML = '';

  state.currentPadBank.forEach(drum => {
    const pad = document.createElement('button');
    pad.classList.add('drum-pad');
    pad.id = drum.id;
    pad.innerText = drum.keyTrigger;

    const audio = document.createElement('audio');
    audio.classList.add('clip');
    audio.id = drum.keyTrigger;
    audio.src = drum.url;
    pad.appendChild(audio);

    pad.addEventListener('click', () =>
      playSound(drum.keyTrigger, drum.id.replace(/-/g, ' '))
    );
    document.addEventListener('keydown', e => {
      if (e.key === drum.keyTrigger.toLowerCase())
        playSound(drum.keyTrigger, drum.id.replace(/-/g, ' '));
    });

    padBank.appendChild(pad);
  });
};

const powerControl = () => {
  state.power = !state.power;
  document.getElementById('power-status').textContent = state.power
    ? 'On'
    : 'Off';

  setDisplay('');
};

const selectBank = () => {
  if (!state.power) return;
  setDisplay(' ');
  state.currentPadBank =
    state.currentPadBankId === 'Heater Kit' ? bankTwo : bankOne;
  state.currentPadBankId =
    state.currentPadBankId === 'Heater Kit' ? 'Smooth Piano Kit' : 'Heater Kit';

  renderPadBank();
  document.getElementById('current-bank').textContent =
    state.currentPadBankId === 'Heater Kit' ? 'Heater Kit' : 'Smooth Piano Kit';
};

document
  .getElementById('power-toggle')
  .addEventListener('change', powerControl);
document
  .getElementById('bank-toggle')
  .addEventListener('change', selectBank);
document
  .getElementById('volume-control')
  .addEventListener('input', adjustVolume);

renderPadBank();
```
