RIP.
If you’re into this kind of things, be sure to check the rest of the Sample Breakdown videos. Some nice gems in there!
A rather geeky/technical weblog, est. 2001, by Bramus
RIP.
If you’re into this kind of things, be sure to check the rest of the Sample Breakdown videos. Some nice gems in there!
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.
~
All audio samples are defined as a small object on an array, and consist of three properties:
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();
}
~
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();
});
~
☝️ 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
~
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.
~
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”.
~
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.
There are existing apps or flash to do this, but no web version so here it is.