Help, Our Karaoke Machine Is on Fire! 🔥
At Q42, we love to spend time on things that may not be all that useful, but are too much fun not to do. Often this devolves into a process of diving into every nook and cranny of an obscure technology, doing a lot of experimentation, just to get that one dumb idea to work. We believe this process of exploring and using tech in a playful way is so valuable to keep ourselves engaged and sharp, that we dedicate one day every fortnight to it: Passietijd (Tinker Friday)!
Now, before you lies a tale that is a prime example of a project which happened over the span of multiple Tinker Fridays (and many weekends and nights); a raw display of the sheer passion and tenacity we find ourselves having for such projects! :D
Karaoke is a Feeling
Alright, so I love karaoke. I think it's one of the most fun ways to loosen up with any audience, showcase your mediocre rapping skills or just act dumb. :>
And you know what I might possibly love even more about it? Nerding out about building the perfect homebrew karaoke setup!
Prior to the story I'm telling you about today, I had already spent countless hours building up a huge organised collection of song charts. I had merged multiple friends’ collections, downloaded new charts made by internet citizens, and written scripts that scrape synced lyrics from Spotify and turn those into valid karaoke charts. This collection of song charts is stored in a format that can be read by UltraStar Deluxe (USDX), an open-source software inspired by the classic Playstation game SingStar.
Now I would love to show you the part of our The Hague office where we have our very own traditional English style wooden pub bar! It carries so many fond memories as the background to many water cooler chats, office drinks and epic parties since we moved here.
But hey, what's that box there tucked away in that corner?? Why, it's an old Wurlitzer jukebox, with a PC and some audio gear stuffed inside to repurpose it as a karaoke machine! And hmmm, starting it up it seems like... Say whaaaat! It's running Windows XP with the first version of my old pal UltraStar Deluxe from 2007! 😯
Omg, this means we can just go ahead and start this thing up on a whim during particularly escalating office parties! And I can add some of my own song collection to it, so I can also sing those obscure Japanese songs that I ‘secretly’ (not that secretly) like!
...And so it happened! And fun it was... But there was definitely something missing. You see, in order to view the lyrics of the current song, you had to look down into the jukebox. Not the most engaging way to sing. And, it was only possible to choose a song by scrolling through the song list at the machine after the current song had ended. This meant there would usually be three people in front of the machine singing their lungs out, and the rest of the bar would kind of just be disengaged. And if you wanted to sing a song yourself you had to stand there awkwardly and get them to let you pick.
Compare this to a staple karaoke box setup, in which there is usually some remote screen on which you can add songs to the queue and then when your song comes up later on you can just come up to the "stage" and shine like you're meant to! In some parties I went to, they even had a website you could visit from your phone to add a song to the queue!
Too bad this software runs locally and doesn’t support such a remote queue system...
Wait.
We're programmers aren't we? We can just build that!
Dive into the Depths of an Ancient World Long Lost
(i.e. the UltraStar Deluxe codebase)
No sooner said than done, right? There was no reason it shouldn't be possible to fork UltraStar Deluxe and tack on a thing that asks a little server thingy what song ID to play next! Then the server could keep track of the queue and do all the complex business logic in my ‘comfort food’ TypeScript, topped off with a cute little frontend to request those songs.
Well, on paper that does indeed sound very fair and doable, but with one caveat: the UltraStar Deluxe codebase is ANCIENT and SCARY and it was very possible that it would just be too darn complex to do anything in it. Especially being written in the ancient/scary language Pascal, which I now like to amicably call the "PHP of lower-level languages".
Anyway, no use in speculating. It was the start of a Tinker Friday and my hands were itching to check out what this codebase was about! I got it running surprisingly quickly → and onwards!!
I thought up the devious plan to just bodge my little functionality into the Jukebox mode, a mostly unused part of USDX that goes through a playlist, playing every song after one another and displaying the lyrics in time with it. I found the part just before it starts a new song. Sounded pretty chill if it could request the current status of the queue at that point and use it to change out the playlist before moving on to the next song. Little step by little step, I gained a bit of insight into how this code as well as the Pascal language itself works. So many writeLines and Google searches about basic Pascal syntax! And having no linting, I had to run the compiler over and over again to check where I'd missed a semicolon this time. And oh, the Interesting Ways to work with pointers in this language!
While this whole process probably sounds frustrating, there was such a nice, satisfying edge to working in a language I'm completely unfamiliar with. Stretching yourself like this really makes you feel your power as a developer and I would recommend it to anyone!
And I undeniably was getting closer and closer to a sort-of working contraption. Setting a hard-coded playlist in-game, then getting a basic network request to a basic little server working, parsing the response to an array, and trying to tap into the game loop at the right point so that it would refresh the playlist before the next song started…
By the way, that basic little server was actually a Cloudflare Worker. I had heard that these are a super elegant way to get a little lambda function running in the Edge and was super excited to try it out while I was at it! And indeed it proved to be just that – and more: apart from being elegant and easy-to-use, they offer so many powerful tools to the developer! I ended up storing the entire song queue in an entry in their built-in key-value store as a JSON string, which worked just fine, with no problems whatsoever! 😅
Then the moment of triumph visits: it's 4 AM after a full Friday plus the entire night of tenaciously trial-and-erroring in a dream-like state with trance music in the background. I'm in a dark room with in front of me a screen with the warm blue-glowing UltraStar menu.
I select Jukebox mode... works. No more random crashes before any logs happen. It makes a request to localhost:8787/q and oh, it actually starts playing the first song of the queue rather than the first song in the entire game with the letter A, and still no crashes.
I open the playlist, see that it actually loaded the entire queue of 6 items + 4 placeholder "Rick Astley - Never Gonna Give You Up" items (the game crashes for some weird reason if the queue has less than 10 items). Nice.
Okay, time to skip to the end of the first song. It reaches the end of the song, stutters a bit (that's okay) and fair enough, it loads the second item in the queue. I check the playlist again, see that it correctly removed the last song from it and it’s gone from the database as well.
I change the current state of the queue from Postman and flip to the next song again, and yesss, it correctly updated in-game with no repeats of the current song.
PFFFFFFFF... HHHHH… It seems that I can finally go to sleep....... 😮💨
Omg, it works!?
Then let's start making it fancy~!
The next morning, I find myself unable to stop thinking about my hype for this new rabbithole and the satisfaction I got from getting that monstrosity working. And so, as one does, I immediately start working on making a fancy frontend for it (a couple hours later I realise that I'm still sitting at my desk in my underwear with the heating off and I really need to go get some breakfast).
I'm not sure if everyone can relate to this, but to me, building a web app can honestly be one of the most addictive activities. Being completely inside your comfort zone (in my case React), not giving a damn about code quality, just churning out feature after feature. Utterly losing track of time, enslaved by the little shots of dopamine at every commit.
“Oooh, now that I got this working, it would be so nice to make a little animation there! Oh and what if there was a little button to pick a random song...! Ah and I'd really like important buttons to stick to the top of the screen! Ooooh and TBH I really want a system where you can vote on items from the queue screen so popular items get popped upwards! And how about an entire admin system where it's possible to override those votes to resolve issues and configure the timeout for requesting songs!”
So yeah, this is sort of how that process went along. First the entire weekend after that one Friday, then after getting burned out on it a bit more sparingly and on some more Tinker Fridays. And there it was: a system where it's possible to view the current queue, request songs that are in the songlist, vote on songs that you'd like to sing along with, and have all these queue changes reflected ‘real-time’ (ish) in my custom fork of UltraStar Deluxe.
Yay everything is happy and dandy now!...
...or is it?!
Yeah, at this point I was honestly (naively) hoping that this setup would "just work". Nope. One more proper end-to-end test and everything fell over again. Turns out that a lot of bugs and edge cases just don't show themselves immediately while developing! Who might have thought. :p
A modest selection from the countless issues I fixed after that testing session:
- A bunch of fun edge cases around the order of songs in UltraStar not going as expected! Fixed by sending a history of the songs actually played so the backend knows which songs to remove acupuncturally.
- ↑ btw this sounds manageable, but no. Half a day of being stuck trying to get an insertion in an array to work, with the state of the song list glitching all over the place. Turns out that in Pascal you need to account for the byte size of items (which is not always 1) when doing anything with arrays: pointer stuff is loads of fun! :)
- So many issues because the Cloudflare key-value storage is eventually consistent! Multiple writes at the same time not coming through, get-queue calls retrieving an outdated state of the queue... Solved by implementing my own key-value store in a Cloudflare DurableObject (after which, ironically, Cloudflare's own consistent database solution D1 was announced). I'll spare you the rest of this rabbit hole. :3
- ...And sooo many more extra small fixes and little add-on features on the web-app frontend and backend 😄 (gotta keep it fun am I right?).
But...! After all those fixes, our humble little hacked-together project was actually starting to look rather robust! And another (more gratifying) test session at my student society and some more bug fixes later, and I felt it was finally time to move on to the next step...
Now we can finally...
I hardly dare say it...
Okay, so we had a thing working, and I was really itching to get this into our beloved little karaoke machine. So what do you do then? That's right, take a full Tinker Friday to upgrade the internals of the entire karaoke machine with three people! Hmm? Ah yes, the reason for this is, the friendly old Windows XP brick didn't have internet, nor did it support the latest Ultrastar version that I forked. And you know, there's just something really satisfying about replacing old cluttered hardware in favour of a (c)lean new setup.
So, after an entire day of that fleeting, precious vibe of tinkering around with hardware and coming up with random stupid ideas like "let's try to get a touch screen in there" or "I really want it to have some epic gamer RGB lighting", we had... a fully working karaoke machine at the office again, with queueing system. 😄
Look at it shine! ✨
Ehh it's the exact same innit?
Well at first it does indeed look the same! Hah hah, that means we succeeded in performing an upgrade with minimal invasion of aesthetics! (Or the first picture of the article was just made the same day but from a different angle with different lighting, hahah hahah.)
But I ask of you, please notice the details: check that logo flashing through a fabulous RGB spectrum? 😎 And trust me that the inside is possibly even cooler: in the place of that ancient PC, there is now just an Intel NUC! Think of all the space that we can now use for… storing a secret biscuit stash or something…! Or, as a wise person once said, "A tidy karaoke machine is a tidy head". :)
And of course, it now has my custom fork of USDX running, which modifies the Jukebox mode, constantly syncing its queue to the worker. :D
See it in action right hyah, and come visit us in The Hague anytime to watch it in its full glory! ☺
References
(Check out these repos for some fun and inspiring looks into how the tech works, not for any semblance of ‘good’ code :p)
Would you like to work at a company where you can build your own karaoke machine on a free afternoon? Check our job vacancies (in Dutch) at https://werkenbij.q42.nl!