Vuiet, the long road to building my own music player in Emacs Lisp

I've been a last.fm user for over 10 years now, back from when you've had to pay to listen to any music from it, which I proudly did. Ever since then, my routine for listening and discovering new music has been to ask last.fm to play similar artists to some artist I already love and like or to just let last.fm do it's job and play its suggestions for me based on my listening history.

This worked nicely for me. But as I've drifted more and more to a mousless world thanks to the Linux command line, Emacs and StumpWM, I became more and more frustrated every time I've had to change songs, add a song to my favorites or just browse an artist page. This back and forth between the browser and Emacs got tiring after a while. So, step by step, rather unconciously I would say, I've started playing with the possibilites of actually accessing the info from last.fm, and idealy also to play music from it, not from a browser, but programatically, mouselessly and keeping in focus only the things that matter: the music and the artists.

First try, muse, a command line tool written in bash. You would call it "muse your-favorite-artist" from the command line, and it would display all the info last.fm had on this artist directly in the terminal, including its bio, genre, similar artists and then a menu with the next step you'd want to take. You could explore similar artists, or, the best thing, display its top songs and chose one song to play. It made use of youtube-dl, so all songs were searched on youtube, it made use of the last.fm api also, so you needed a key from last.fm and some json parsing with jq. This worked nicely, as expected, but it was hard to use. It felt like an ATM machine where you'd have to go back and forth between the operations. So it's life was short and it was soon abandoned. That was four years ago.

I've also used cmus for a time. After discovering some new artist on last.fm, I would get it's whole discography in mp3 format and use cmus to listen to it. cmus is a mousless tool, and that I liked. One of its features was that you could filter and play songs from a given genre. Unfortunately, most of the mp3 files that I owned had lousy tags for genres, or none at all. Last.fm to the rescue, again. The tool was named lastag and it could tag mp3 files with music genres from on last.fm. That worked nicely. But little by little I've moved my listening habits completely online, and I've used cmus less and less. Concluson: lastag was soon abandoned. That was three years ago.

If you've used the command line long enough, you've probably used some form of completion framework, like zaw. Or, in the Emacs world, helm or counsel. These tools present a list of all the entries for a given command, entries that you can navigate through, select and call an action on your selection. Useful to navigate your command line history, for example, your files and folders and even your git history, amongst others. My idea was, why not have a lyrics tool that builds a lyrics database of all the songs I own, and, besides displaying the lyrics of the currently playing song from cmus, would also let me play songs by searching the lyrics first. That is, I would call "lyrics chasing colors" on the command line, for example, and it would search in the database for all entries containing "chasing colors" anywhere in their lyrics field. The result would be a list of songs I could easily navigate through and that would would play my selection, again using cmus. With this idea, bash-lyrics was born, again a tool written in bash, for the command line. Again, this worked nicely for a while, and I even managed to integrate it with my then new Window Manager, StumpWM. But with my declining use of cmus, that also soon fell into oblivion. That was also three years ago.

The quest for a better player was abandoned for a while. In this time I've discovered Lisp, read SICP, the Little Schemer and a few others. Eventually, you have to do something with all the gathered knowledge and not just read all day and pause in awe at what others did. Sure, your first tries will not be what you've imagined them to be and they will gather no attention whatsoever. That's no reason not to try. You've got to start somewhere. So what better way to practice what I've learned than to go back to my earlier obsession: the perfect music player. So I've made the muse-player, a mouseless last.fm music player with lyrics. This worked somehow, but it's a huge pile of everything in one place. The last.fm pages are web-scrapped, the lyrics are mixed in there, some databases are in there, some local web server and some html files are generated with the currently playing song and what not. It was a big mess, and it had lots of bugs, but at least it was a step in the right direction. That was a year ago.

Now you might be saying, "Mihai, that's just wrong! What are you doing making another website to get rid of clicking through the original website?" Yes, that is wrong. But when you have a feeling of what you want but not of how it should look like, you'll try everything, even if it's complete garbage. And that is good. And that's where Lisp shines. It lets you just go in, fearlessly, without much ceremony, and just try all your stupid ideas. Sometimes all of them in one place, in the same file. You can mix and match and see what gets through to the other side. Failling is fine.

So the first Lisp try was a mess. But I soon figured out that if I split my functionalities into different modules, that can be published and used independently, I will make my life easier. The first piece of the puzzle was finally having a complete interface to the last.fm api, even if I will use all of the functionality in my final app or not. Now last.fm has over 50 api methods, from getting the top tracks of an artist, to getting the friends of the given user and finally to those that require authentication, like loving a track or adding a tag to a track. Every method needs the api key, some key signing, some url building and other such actions. I've looked over the internet and seen countless implementations with thousands of line of code, in more than one programming language, and most of that code was copy/paste with small variations, like the actual parameters sent to last.fm, the api method name and the url. And most importantly, none of them implemented the complete API, only bits and pieces. After much playing around, it came to me: I could use a macro to generate all these functions and keep the implementation to a few hundred lines of code, not thousands. It worked, even though my method of using the macro it was "non-orthodox" as one user mildly put it. But I've seen my first real-world use of a macro, and I've felt like a God. A small core implementing the basic functionality and a macro to generate all the variations and the public interfaces of the module. So this was the lastfm module for Common Lisp.

By now, you already know it, the lyrics module soon followed, and after that zbucium, the music player written in Common Lisp, again as a library, and the first music player for which I've had a little bit more inspiration whan it came to choosing its name. Again this worked quite like I've imagined, and I was able to couple it with StumpWM in the zbucium-stump project, for a mouseless experience. But, unfortunately, this was still too hard to use. The music explorer was bad, you couldn't easily browse similar artists, for example, since the StumwpWM message window was not meant for highly interactive applications. I could play the music in the background, no mouse needed, get the lyrics, switch songs, pause them, all that. But it was still buggy and not so user-friendly. Abandoned, again, but it was a step in the right direction and I came out of it with a few ideas and, at least as important if not more importantly, with the knowledge that some avenues were not productive and had to be abandoned. That was almost a year ago.

Finally, I've started playing with the idea of implementing the player in Emacs Lisp. At least, I could give it a shot, see how it looks and behaves, and if it seems usable, keep developing it, and if not, throw it away like all the rest before it. First step: lastfm.el, the last.fm api for Emacs Lisp. By this time I've had a ton of experience with calling and parsing the last.fm api. Using again the insight I've had with how to use a macro to ease my work, I've managed to implement the complete api in 400 lines of actual code, and it was my first package that I've submitted to melpa, the Emacs package archive. I've felt good now. So I've attacked my next piece of the puzzle. The old lyrics functionality, with a local database, with lyrics saving and, most importantly, lyrics searching. This was my second contributon the Emacs melpa repository. And now finally, by using org-mode, I was able to display all the info that last.fm has about an artist in a new buffer and, unlike my first try at the command line from four years ago, actually interact with that buffer: move the cursor around, follow links to similar artists or play songs directly from this buffer. Appart from this browser, I also managed to integrate the lyrics and to let the user create playlists from a ton of criteria and play them with mpv and the help of youtube. This looks like it has more potential than the tools before it, I've used it to listen to music for a while now, and it seems it has some happy users also. This is now called vuiet - the music player and explorer for Emacs, available from melpa as well.

Throughout all these adventures towards the perfect music player, which might not be finished yet, I've found that working, or rather playing, with Lisp was the most fun I've ever had with a computer. Often times I've had some code interacting with the mpv player, some code that returned some artist top songs from last.fm and some code that fetched the lyrics all mixed up in one single page of Emacs Lisp. All visible on one monitor, and me still having no idea of how all this should come together. But slowly, by mixing all this up, like a puzzle or lego blocks, gluing them together, taking them appart, bringing in or easily creating new pieces just when I needed them and throwing them out just as easily if they proved to be the wrong toys, little by little, after testing a lot of ideas, it finally clicked into place. It finally worked. I finally got a strong base with the basic funtionality I've always dreamt about. And all this process was all so seamless as you can take appart anything, debug and inspect everything, bring in new functionality without restarting the whole system, as making a lot of mistakes and not feeling stupid about them seems to be part of the whole system.

Now I'm gonna let you try vuiet and tell me what you think about it. In the meantime, I'm gonna go on the mouseless listening session that I've always dreamt about.

Update: vuiet is Romanian for roar, rumble or big noise.