Quetzalcoatl 2.0 Released.
I have finally completed version 2.0 of the MPD client.
It’s at the the old GitHub page:
Previous versions have moved here:
The master branch of the legacy repository is the first release, and the experimental branch is the rewrite that I eventually developed into this one.
Why An MPD Client?
I designed Quetzalcoatl as a standard desktop music player, so why is it an MPD client?
The honest answer is that the decision was technical. Gapless playback was important to me, and using MPD as the backend was the most efficient way to implement it.
C++ or Python?
I decided to use C++, not Python, this time. C++ is Qt’s native language, and with the advances in C++ over the last decade, Python’s efficiency advantages aren’t the same as they used to be.
Plus, I’ve had too many experiences (such as PyQt 4 not having the QSignalSpy class) where the Python bindings weren’t perfect.
QML or QWidgets?
Quetzalcoatl was designed for QWidgets, and QWidgets remains a better match for it than QML.
As for why I decided to do a new release of Quetzalcoatl instead of designing a new player to implement in QML: a lot of passion went into Quetzalcoatl’s UX design, and I continued to refine it long after the initial decisions. I’m just going to quote an old email where I explained my design decisions:
From the iPod I borrowed the tag-based navigation structure. I also recognized that the iPod’s navigation structure is a tree, and therefore used a tree widget to display it. From Rockbox I learned that when the user plays a song, I should queue up the entire album before starting to play that song. From Foobar2000 I took a “total time” label which shows the combined time of all selected songs in the playlist: useful when picking songs for mix CDs. Foobar2000 also gave me the drag-and-drop arrangeable playlist. From the Linux player “moc” I took the two-pane design, with the left half being the music library and the right half being the playlist.
The foundation of any interactive (as opposed to batch) application is the . Network connections are exposed to the application (by the operating system) as sockets. One event that needs to be handled is data being available to read on those sockets. For that, the operating system provides services such as . Integrating the select call (or equivalent) into the event loop gives you the foundation of the . Qt provides an API to handle networking in the main event loop and, if you want to, implement a reactor. It recommends that API, here:
Generally speaking, you should only use threads for networking if a single-threaded event-based architecture is not enough.
MPD is, of course, a server that Quetzalcoatl connects to by opening a socket.
Most of its calls are synchronous; you get a response back immediately. The exception is the “idle” command, which, I believe, was introduced after I started the project. It returns data only when there is a change in the server state. While waiting for that data, the only command allowed is “noidle”, which cancels the “idle” call and returns its results immediately.
It’s not unusual, for authors of graphical desktop MPD clients to write their own data access layers to make calls to MPD. I, however, decided to use MPD’s official one: .
This, from MPD’s mailing list, is how you use libmpdclient in an interactive program:
mpd_send_idle(), then register libmpdclient’s socket (see mpd_connection_get_fd()) in your I/O event loop.
As soon as MPD sends the result, your event loop triggers your callback, and you can use mpd_recv_idle() to read the result. After you’re done handling those idle events, re-enter idle.
If you want to interact with MPD in the meantime, call mpd_run_noidle() and handle idle events which may have occurred; then send the desired commands to MPD. Finally re-enter idle.
This is not magic. And it’s not specific to MPD. This is basic event-based programming. Just one connection and one thread, no latencies, no timeouts, no waiting.
I start by create an active for MPD’s file descriptor. Then, I take the following steps to execute one synchronous MPD command:
- deactivate the socket notifier
- send noidle
- run the synchronous command (send it, immediately receive results)
- activate the socket notifier
- send idle
I’ve found that as long as those steps are followed, the only time when you actually need to worry about blocking is when you’re connecting to MPD. The mpd_connection_new function, internally, makes a getaddrinfo call, which typically is allowed to block for up to 5 seconds if it cannot resolve a hostname. The solution? Use Qt’s class to validate the hostname first.
While I do not want to disparage the authors of other MPD clients, I’ve seen too many that use a separate connection to handle idle notifications. One of the goals of this project is to be an example of how to avoid that.
Testability is often one of the largest considerations behind a program’s architecture. See, for example, all the designs based around and . The problem with these two patterns is that they expect the parts of the code that access external systems (such as MPD) to be abstracted into easily mockable classes. Obviously, libmpdclient does not have that quality. I mean, just look at it. There are a number ways to to deal with that (such as wrapping libmpdclient in classes), but I decided to take the recommendation of my college friend , who personifies Joel Spolsky’s ideal of . His recommendation? Have the unit test runner spin up and tear down instances of MPD.
This type of automated integration testing is not unusual. The unit test suite of a Django project, for example, will typically spin up and tear down SQLite databases as needed.
In QML, you would create the UI, and expose instances of C++ classes for it to use. I decided to start with this approach, not because I’m planning an eventual port to QML, but because it’s a good approach.
Starting there, you already have the foundation of a architecture.
Model-view architectures, including variations such as the Model View Controller and the Model View Viewmodel, start by treating the UI, the “View”, as a separate application. There are at least two advantages to this. First, it separates the part you probably won’t unit test (the UI) from the part that you should (everything else). Second, it gives you the freedom to update the UI and the rest of the code independently, with changes to one having a lower chance of breaking the other.
The boundary between the View and the rest of the application is more obvious if you have a declarative UI. This is one of the advantages of declarative UI technologies.
If you have a main window with three tabs, two dialog boxes, and an Excel-style data grid, then you have one View. Remember, part of the point is to allow the UX designer to make revisions without requiring changes to the underlying code.
The Model-View-Presenter architecture includes a middle layer between the UI and the rest of the application. While I don’t know the actual reason for its name, I know that the UI is sometimes called the Presentation Tier, and I suspect that this is the reason the layer behind it is called the Presenter. I also wonder if this is the reason why OS/2’s user interface was called the Presentation Manager. This middle layer can be thin, an implementation of the .
Because the Presenter’s client is the View, and the View’s needs are stable and clearly defined by the program’s UX design, the Presenter can have a very clean, testable API.
The View is a , and it is also an that subscribes to events from the Presenter.
This version of Quetzalcoatl follows this pattern when communicating with MPD. It encapulates libmpdclient behind a layer. The view sends commands to this layer, and it listens for the events that it emit: queue changes, status changes, and connection loss. Some of these are expected to come back after certain commands, but they can also be generated by another client. By spinning up instances of MPD, we can unit test this layer.
One of my proudest moments with the Python version, was when I launched two copies, dragged songs around in one, and watched the other one automatically update. It felt cool, in part, because I had never planned it. This time, I made a point to implement the responses to the server notifications first, before implementing the commands that would generate those responses. For example, I implemented the responses to the changes in the stored playlists before I implemented renaming or deleting them. I knew, that this way, I would have the architecture I wanted.
One feature from the Python version that I removed from the scope was album art. The Python version would download album art from last.fm. I even wrote a program, the , to tag your collection to facilitate that. For this version, I want to do it using MPD and libmpdclient. As their album art support (via the “readpicture” and “albumart” commands) looks to me to be under development, I’m going to wait a bit before implementing it.
There were two previous releases, both written in Python:
As these were the product of years of continuous refinement and dogfooding, it made sense to keep as much as I could from them. This is, after all, why Joel Spolsky recommends us to . I hope that this fulfills their promise by keeping what was worth keeping, and also finally reaching what they were heading towards.