Sparked by Pete LePage’s work on talking to a Elgato Stream Deck device from within the browser, I wanted to play with WebHID myself. First thing that came to my mind was to create a DrumPad.
What first started out as a simple/classic DrumPad …
🔥 Have been playing with the #WebHID API tonight, connecting my @elgato Stream Deck to @googlechrome.
Here’s a little drum pad that respons to clicks, keyboard key presses, and the Stream Deck buttons.
🔗 https://t.co/Nkfoh39dr3 (Chrome Only) pic.twitter.com/kn2IsP0jrx
— Bramus! (@bramus) February 10, 2021
… soon led to creating a Soundboard which uses samples from Daft Punk’s “Harder, Better, Faster, Stronger”.
Screenshot of the Soundboard I built.
Before linking to the final version of the Daft Punk Soundboard (which has evolved quite a bit when compared to the screenshot above), let’s take a look at how it works.
~
Building the Drumpad/Soundboard and responding to clicks
All audio samples are defined as a small object on an array, and consist of three properties:
- A label
- A link to an audio fragment
- A Keyboard
keyCode
to respond to
ℹ️ In the final version I added some extra features such as the ability to use an image instead of a label and to customize the action when the button is being pressed, but these are not the focus here.
Each fragment is rendered as a <button>
element and a (non-visible) <audio>
element. The <audio>
element its id
is set to the keyCode
.
<button data-keycode="${keyCode}">
<span>${label}</span>
<audio id="${keyCode}" src=${url} preload="auto"></audio>
</button>
Upon pressing a button, its linked <audio>
element (fetched using the button’s data-keycode
attribute value, instead of relying on DOM traversal) is selected and a play action is triggered on the fragment.
const playSound = (keyCode) => {
const $el = document.getElementById(keyCode);
if (!$el) return;
$el.currentTime = 0;
$el.play();
}
~
Responding to Keyboard Key Presses
To capture key presses a listener on the keydown
event of the document is added. Using the pressed key’s code
the correct button is selected and a click
on it is triggered.
document.addEventListener('keydown', (e) => {
const $button = document.querySelector(`button[data-keycode="${e.code}"]`);
if ($button) $button.click();
});
~
Attaching the Stream Deck
☝️ Do note that connecting a Stream Deck is entirely optional: using a Stream Deck is considered to be an enhancement.
The Stream Deck code itself was borrowed from Pete’s Google Meet Stream Deck Chrome Plug-in, and launched using similar logic. If a Stream Deck device is found and connected, it is attached to the DrumPad instance.
const go = async () => {
const drumPad = new DrumPad(config, document.querySelector("#app"));
await drumPad.init();
if (navigator.hid) {
const streamDeck = new StreamDeck();
// Connect to previously connected device
await streamDeck.connect();
// A previously connected device was found
if (streamDeck.isConnected) {
drumPad.attachStreamDeck(streamDeck);
}
// No Previously connected device was found
else {
// Add button to connect new device
const elem = document.createElement("button");
elem.innerText = "Connect Stream Deck";
elem.addEventListener("click", async () => {
elem.remove();
await streamDeck.connect(true);
drumPad.attachStreamDeck(streamDeck);
});
document.body.appendChild(elem);
}
}
}
go();
💡 As not all browsers support top-level await, we wrap the whole logic in a async function
~
Responding to Stream Deck button presses
To also respond to button presses on the Stream Deck, a map that maps a Stream Deck button ID (0
, 1
, 2
, …) to a certain keyCode
is built.
const buttonIdToKeyCodeMap = {
0: "KeyQ",
1: "KeyW",
2: "KeyE",
…
}
Upon pressing a Stream Deck button it will — using the buttonIdToKeyCodeMap
— fetch the corresponding HTML button and trigger a click on it, similar to how the keyboard key presses work.
This is set up in the call to drumPad.attachStreamDeck(streamDeck);
(see above) and looks like this:
streamDeck.addEventListener('keydown', (e) => {
const keyCode = buttonIdToKeyCodeMap[e.detail.buttonId] ?? '';
const $button = document.querySelector(`button[data-keycode="${keyCode}"]`);
if ($button) $button.click();
});
In that same attachStreamDeck
method the buttons on the Stream Deck are also drawn.
~
Stretching it a bit more …
The switch to the Daft Punk board didn’t sit 100% well me with me though: there are 16 samples to use, but the Stream Deck “only” has 15 buttons available …
But then it hit me: what if I paginated the samples, and allowed you to switch between two sets of 8 samples each? In that idea the 1st row would be filled with buttons to switch between different sample sets, and the 2nd+3rd row would respond to that.
With that refactor being worked on, I also took the time to update the UI to closely reflect the layout of the Stream Deck device.
In the end, it ended up like this:
Here’s a video of it, so see how it works and behaves, including with a connected Stream Deck:
~
During lunch today I polished the code a bit further and pushed everything online. The Source Code can be found in GitHub, and the app is deployed on Netlify.
Elgato Stream Deck Daft Punk Soundboard Demo →
Elgato Stream Deck Daft Punk Soundboard Source Code →
👨🔬 The demo website is registered for the WebHID Origin Trial, and therefore WebHID should be enabled by default. If you however don’t see a connect button, go to chrome://flags/
and manually enable ”Experimental Web Platform Features”.
~
Thank me with a coffee.
I don\'t do this for profit but a small one-time donation would surely put a smile on my face. Thanks!
To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.