Let's build an actual working digital Drum kit🥁 you can play with your keyboard⌨️🤘
Tags: javascript, css, webdev, tutorial
Published: 2021-02-12 20:27:18

Yes. I've built a guitar already, you can find it here:

{% post https://dev.to/thormeier/let-s-build-an-actual-working-guitar-with-javascript-bfb %}

And now a drum kit. Am I running out of ideas? Well, not exactly. **If you want to form a digital rock band, you need more than one instrument, right?** And this is only part two, so you can expect more.

Without further ado, let's get back into the instrument workshop and get going!

# Assemble the kit

A drum kit has a _lot_ of parts. To build them all and make them look good, I use a few linear gradients and a radial one:

```html
  <defs>
    <radialGradient id="head" fx="26%" fy="26%">
      <stop offset="0%" style="stop-color:#f0ede6;" />
      <stop offset="100%" style="stop-color:#f5e9c9;" />
    </radialGradient>
    <radialGradient id="case" fx="30%" fy="30%">
      <stop offset="0%" style="stop-color:#82827f;" />
      <stop offset="100%" style="stop-color:#6b6b64;" />
    </radialGradient>
    <linearGradient id="caseColor">
      <stop offset="0%" style="stop-color:#6193ba;" />
      <stop offset="20%" style="stop-color:#a8c9e3;" />
      <stop offset="100%" style="stop-color:#6b6b64;" />
    </linearGradient>
    <linearGradient id="cymbal" gradientTransform="rotate(25)">
      <stop offset="0%" style="stop-color:#ede58c;" />
      <stop offset="30%" style="stop-color:#f2eec4;" />
      <stop offset="60%" style="stop-color:#f2eec4;" />
      <stop offset="100%" style="stop-color:#ede58c;" />
    </linearGradient>
  </defs>
```

Then I add the parts of the drum kit, namely a bass drum (made with a single circle), the snare drum and three toms (ellipse + two paths), a hi hat cymbal (two ellipses + one path) and a crash (a singe ellipse and a single path).

```html
  <!-- Hi-Tom -->
  <path stroke-width="20" stroke="url(#case)" d="M 1000 700 1001 1200" fill="none" />
  <path stroke-width="20" stroke="url(#case)" fill="url(#caseColor)" stroke-linejoin="round" d="
    M 880 700
    880 820
    A 1000 1900 0 0 0 1120 820
    L 1120 700 Z
  " />
  <ellipse id="hitom" fill="url(#head)" stroke-width="20" stroke="url(#case)" cx="1000" cy="700" rx="120" ry="20" />

  <!-- Mid-Tom -->
  <path stroke-width="20" stroke="url(#case)" d="M 1350 700 1351 1200" fill="none" />
  <path stroke-width="20" stroke="url(#case)" fill="url(#caseColor)" stroke-linejoin="round" d="
    M 1220 700
    1220 860
    A 1000 1900 0 0 0 1480 860
    L 1480 700 Z
  " />
  <ellipse id="midtom" fill="url(#head)" stroke-width="20" stroke="url(#case)" cx="1350" cy="700" rx="130" ry="20" />

  <!-- Bass drum -->
  <circle id="bass" fill="url(#head)" cx="1200" cy="1200" r="270" stroke-width="20" stroke="url(#case)" />

  <!-- Snare drum -->
  <path stroke-width="20" stroke="url(#case)" d="M 900 910 901 1410 780 1460 M 901 1410 1020 1460" fill="none" />
  <path stroke-width="20" stroke="url(#case)" fill="url(#caseColor)" stroke-linejoin="round" d="
    M 710 900
    710 1050
    A 950 1700 0 0 0 1110 1050
    L 1110 900 Z
  " />
  <ellipse id="snare" fill="url(#head)" stroke-width="20" stroke="url(#case)" cx="910" cy="900" rx="200" ry="50" />

  <!-- Floor tom -->
  <path stroke-width="20" stroke="url(#case)" d="M 1700 1200 1740 1480 M 1500 1200 1450 1480" fill="none" />
  <path stroke-width="20" stroke="url(#case)" fill="url(#caseColor)" stroke-linejoin="round" d="
    M 1380 1020
    1380 1350
    A 950 1700 0 0 0 1820 1350
    L 1820 1020 Z
  " />
  <ellipse id="floortom" fill="url(#head)" stroke-width="20" stroke="url(#case)" cx="1600" cy="1020" rx="220" ry="60" />

  <!-- Hihat -->
  <path stroke-width="20" stroke="url(#case)" d="M 500 830 500 1410 580 1460 M 500 1410 430 1460" fill="none" />
  <ellipse
    cx="500" cy="830" rx="200" ry="40"
    fill="url(#cymbal)" stroke="#222" stroke-width="1"
  />
  <ellipse
    id="hihat-head"
    cx="500" cy="800" rx="200" ry="40"
    fill="url(#cymbal)" stroke="#222" stroke-width="1"
  />

  <!-- Crash -->
  <path stroke-width="20" stroke="url(#case)" d="M 1850 600 1851 1410" fill="none" />
  <ellipse
    id="crash"
    cx="1850" cy="600" rx="300" ry="50"
    fill="url(#cymbal)" stroke="#222" stroke-width="1" transform="rotate(-15 1850 600)"
  />
```

With this result:

![SVG Drum kit](https://dev-to-uploads.s3.amazonaws.com/i/dls84krae89t7lbhgje9.png)

There's really only the pedals missing, but I won't need those anyways when I play everything with my keyboard, right?

# And how do I play this?

I need to come up with some kind of pattern of keys. I want this to almost feel like a real drum kit, so I kind-of replicate the layout of a drum kit on a keyboard:

![Keyboard layout with some keys marked showing which drum they correspond to](https://dev-to-uploads.s3.amazonaws.com/i/7rswdfcg9ignn7ykloub.png)

So, basically

```
Hihat open: A
Hihat closed: Shift+A
Hi tom: F
Mid tom: J
Crash cymbal: O
Snare drum: B
Bass drum/kick: Space bar
```

In JS, I add an event listener to the window and analyse the `key` attribute of the `keydown` event:

```js
let isShiftPressed = false

const hihatHead = document.querySelector('#hihat-head')
const hitom = document.querySelector('#hitom')
const midtom = document.querySelector('#midtom')
const floortom = document.querySelector('#floortom')
const snare = document.querySelector('#snare')
const crash = document.querySelector('#crash')
const bass = document.querySelector('#bass')

/**
 * Finds out which drum was played.
 * @param key
 * @returns {string|null}
 */
const getInstrument = key => {
  switch (key.toLowerCase()) {
    case 'a':
      return hihatHead
    case 'f':
      return hitom
    case 'j':
      return midtom
    case 'l':
      return floortom
    case 'b':
      return snare
    case 'o':
      return crash
    case ' ':
      return bass
  }

  return null
}

window.addEventListener('keydown', e => {
  if (e.key === 'Shift') {
    isShiftPressed = true
    return
  }

  const drum = getInstrument(e.key)

  if (drum === null) {
    return
  }

  // ...
})

window.addEventListener('keyup', e => {
  if (e.key === 'Shift') {
    isShiftPressed = false
    // ...
  }
})
```

Next, I add some animations to give a visual feedback. I use some CSS classes for this that I'll remove shortly after with a timeout. I use `clearTimeout` to not run into weird behaviour when playing a drum really fast:

```css
#hihat-head.closed {
    transform: translateY(10px);
}

.played {
    transform: translateY(5px);
}
#bass.played {
    transform: scale(0.98);
    transform-origin: 1200px 1200px;
}
#crash.played {
    fill: url(#cymbal);
    transform: rotate(-20deg);
    transform-origin: 1850px 600px;
}
#hihat-head.played {
    fill: url(#cymbal);
    transform: rotate(5deg);
    transform-origin: 500px 830px;
}
```

And adding and removing the classes:

```js
const timeouts = new Map()

window.addEventListener('keydown', e => {
  if (e.key === 'Shift') {
    isShiftPressed = true
    hihatHead.classList.add('closed')
    return
  }

  const drum = getInstrument(e.key)
  if (!drum) {
    return
  }

  drum.classList.add('played')
  if (timeouts.has(drum)) {
    clearTimeout(timeouts.get(drum))
  }
  timeouts.set(drum, setTimeout(() => {
    drum.classList.remove('played')
  }, 100))
})

window.addEventListener('keyup', e => {
  if (e.key === 'Shift') {
    isShiftPressed = false
    hihatHead.classList.remove('closed')
  }
})
```

And now play a (silent) drum solo:

![The SVG drum being played](https://dev-to-uploads.s3.amazonaws.com/i/ilh2jiqrvk654ei28hre.gif)

# Time for the sound check

I'll use a midi font, lust like with the guitar, but a different one: [github.com/johntu/midi-js-gm1-percussion](https://github.com/johntu/midi-js-gm1-percussion) The README tells me which note/mp3 file is corresponding to which drum, so I create another map:

```js
const sounds = new Map()
sounds.set(hihatHead, {
  open: new Audio('./sound/Bb2.mp3'),
  closed: new Audio('./sound/Gb2.mp3'),
})
sounds.set(hitom, new Audio('./sound/D3.mp3'))
sounds.set(midtom, new Audio('./sound/B2.mp3'))
sounds.set(floortom, new Audio('./sound/G2.mp3'))
sounds.set(snare, new Audio('./sound/D2.mp3'))
sounds.set(crash, new Audio('./sound/Db3.mp3'))
sounds.set(bass, new Audio('./sound/C2.mp3'))
```

Now I can adjust my event listener to actually play the sound:

```js
window.addEventListener('keydown', e => {
  // ...
  const drum = getInstrument(e.key)
  // ...
  let sound = sounds.get(drum)
  if (drum === hihatHead) {
    sound = isShiftPressed ? sound.closed : sound.open
  }

  const audio = new Audio('./sound/' + sound + '.mp3')
  audio.play()

  const drum = getInstrument(e.key);
  // ..
  let sound = sounds.get(drum);
  if (drum === hihatHead) {
    sound = isShiftPressed ? sound.closed : sound.open;
  }
  sound.pause();
  sound.currentTime = 0;
  sound.play();
  // ...
})
```

That's it! A working drum kit to accompany the guitar! Here's a live demo for you to play:

{% codesandbox js-drumkit-4gbd3 %}

# Takeaway thoughts

I was a lot faster with this one, now that I've mastered the art of SVG-instrument-forging. Nevertheless, I had a lot of fun building this. If you like, you can record your best solo and post a link to the video in the description!

**In the next post of this series, I'm going to connect the two instruments so you can finally form your own digital rock band!**

---

*I hope you enjoyed reading this article as much as I enjoyed writing it! If so, leave a* ❤️ *or a* 🦄*! I write tech articles in my free time and like to drink coffee every once in a while.*

*If you want to support my efforts,* [*buy me a coffee* ☕](http://buymeacoffee.com/pthormeier) *or* [*follow me on Twitter* 🐦](https://twitter.com/pthormeier)*!*

[![Buy me a coffee button](https://dev-to-uploads.s3.amazonaws.com/i/x55hp6jopwyy161d8e2u.png)](http://buymeacoffee.com/pthormeier)