Reflections on Ambient DJ

Ambient DJ records a list of atmospheric audio tracks stored on a user’s computer, allowing them to select a series of words describing their mood so the software can build an appropriate playlist. A primary goal was to make the process of selecting moods as quick as possible.
Early in 2019, I was playing a tabletop role-playing game with some friends when one of them decided to try adding ambient music to make it more immersive. Those of us playing thought it was effective, but there was very little audio to work with. I knew I already owned a number of soundtracks that could serve the same purpose if only I had a tool that could quickly pull them up when they were appropriate.
I started by manually listening to songs and writing down what moods I thought they evoked, and after about a hundred I realized that I had no way to easily parse through those moods and play the appropriate songs. The timing of this realization was convenient to work on with my team at Holberton.
Our team decided to complete the back-end first before working on any user interface, and we attempted to have as few dependencies as possible during that step. We only required Bass, a proprietary, multi-platform, low-level library for playing sound. It’s been around for a long time and was a little difficult to integrate into our Python code base, but it surpassed all the Python solutions we found in power and compatibility.
When it came time to build the graphical user interface, we knew we’d need to use someone else’s library for that, too. We tried out a number of different ones, but many were difficult to install and configure or didn’t run well on some operating systems. We ultimately chose Kivy, a library designed for making mobile apps look nice.
In the two weeks we had to write the code, we achieved all our goals of making the application useful and usable outside our team. The interface displays a grid of moods to select from, and you can click any of them to generate a playlist of tracks fitting that mood. When you do so, all the mood buttons that are not relevant to your selection become transparent and aren’t clickable. You can also right-click on moods to ensure that the generated playlist does not include that mood. There are also buttons that pause and resume the current song, and one that skips to the next one with a cross-fade. You can also look at a preview of the new playlist that will be enabled for each mood button that you click on.
Using a framework intended for mobile apps brought some challenges. Kivy displays buttons graphically using texture images rather than simple shapes. This looks fine for buttons that only take a couple of states, but our mood buttons needed 4: “include”, “exclude”, “available”, and “unavailable”. This wasn’t possible using the provided buttons, which only supported “available”, “unavailable”, and “active” (which is for the short period while the button is being clicked). I tried creating a new texture image, but this didn’t work. I wanted the buttons to have rounded corners. Kivy doesn’t support using vector images for button textures, and the rounded parts of raster images look worse as you scale them up.
Instead, I looked at Kivy’s support for drawing primitive shapes on the application window. After some experimentation, I was able to create a button border by drawing two rounded rectangles, where one was smaller and in front of the other. Both of these shapes share the position of the button object and the larger one shares its size, while the smaller one’s size is 10% smaller. That way, by changing the color of the larger rectangle in the back, it looked like the button had a border that was changing.
While we were able to build the application with the features we set out to create, I wouldn’t approach future projects in exactly the same way. Our reliance on a low-level audio library and my disappointment in the memory cost of our application makes me wonder if we should have used an older, lower-level language like C or Pascal rather than Python. Both of those other languages already support the Bass library so we would only have to change our GUI framework.
In regards to the GUI, I think we would have been better off spending more time setting up the frameworks that are built for desktop applications like PyQT or WxPython. Kivy led to a number of difficult challenges due to the way it was designed.
Finally, I’m interested in continuing to study how to play a music file in a computer program. We knew from the beginning that this was difficult, which is why we let Bass do that hard work. Still, I wonder how this task could have been overcome had we the time.
My name is Sam Hermes, and I was the team lead on Ambient DJ. I’ve been programming since my early teens and still enjoy doing it for fun. I recently took up a hobby in tabletop role-playing, which inspired this project.
A more fluffy write-up of Ambient DJ is available here, where you can also download distributions of the program. The source code is available on GitHub.
