đž Archived View for gemini.omarpolo.com âş post âş amused.gmi captured on 2023-07-10 at 13:48:53. Gemini links have been rewritten to link to archived content
âŹ ď¸ Previous capture (2023-01-29)
âĄď¸ Next capture (2024-03-21)
-=-=-=-=-=-=-
experiments that I end up liking
Written while listening to âCentury Childâ by Nightwish.
Published: 2022-02-19
Tagged with:
Moved by curiosity I wrote a small music player. The ideas was to see if it was possible to decode the audio files in a tightly sandboxed process.
TL;DR it was an error. I ended up writing a thing I like, now I have to maintain it!
Itâs a small program that plays music on the background and is commanded by a really simple command line interface:
$ amused add *.mp3 # enqueue all mp3s $ amused play playing /path/to/music.mp3 $ amused pause $ amused status paused /path/to/music.mp3
End. Well, there is a little bit more to be honest. It has a unique (in its category I mean) behaviour with regards to pipes, but Iâd like to describe my thought process before showing how it plays with the shell.
The initial idea was to have a daemon process (âamusedâ) that plays the music and a client (âplaylistctlâ?) to control it. Akin to mpd/mpc if you want to. I pretty quickly discarded the idea and went with a single executable â amused â that is both a daemon and a client, and that automatically spawns the daemon if needed. Itâs more easier to use I guess.
One of the first command to be implemented was âshowâ to print all the enqueued files. Amused is a simple program which has only the notion of the current playing queue, and I wanted to keep it as simple as possible. In particular I didnât want to add some kind of state persistence. All the state is manipulated via the command line and is ephemeral: once you kill it, itâs gone.
Then I thought that I could use the âshowâ command to dump the state to the disk, so I wrote a âloadâ command to re-import it from a file:
$ amused show > amused.dump $ # then, later... $ amused load < amused.dump
Pretty cool if I can say so. At this point I had a program lying around that I started to really like, so I was thinking of adding just some more features so that I could actually use it from day to day. One of the first that I thought of was manipulating the playing queue: sorting, shuffling, removing duplicates or specific songs...
Well, itâs not difficult to do, on the contrary, but do I need to code these features myself? (I think stuff like this more and more recently)
Then I had a âunix revelationâ: I could use the shell!
$ amused show > list $ sort -R < list > list.shuffled $ amused load < list.shuffled
it works but itâs quite painful to type. I can do better. I can use pipes!
$ amused show | sort -R | amused load
many many thanks to Douglas McIlroy for the idea of the pipes! dont-know-how-many-years after theyâre still relevant.
To be honest being able to use the pipes like that required a bit of hacking on the client to avoid races: âloadâ used to be an alias for âflushâ (erase the playlist) and one âaddâ per file. If the âloadâ command were executed before âshowâ due to the random nature of pipelines and timing, well, it wouldnât end well. However, making it ârace condition freeâ was actually pretty simple to do and made the âloadâ command more robust.
Some more examples to give the idea of how it composes well:
$ # remove all Gucciniâ song $ amused show | grep -vi guccini | amused load $ # load the dream theater discography $ find ~/music/dream-theater -iname \*.flac | amused load $ # select a song with fzf $ amused jump "$(amused show | fzf)" $ # ...or with dmenu! $ amused jump "$(amused show | dmenu)"
The code is also pretty modest in size:
% wc -l *.c | tail -1 2902 total
of which ~500-1K lines were stolen^W borrowed from other OpenBSD programs and another ~500 are the decoding âbackendsâ for the various audio formats.
The most difficult part was actually the audio decoding. I never wrote âaudio codeâ before. Well, I have a pending PR for an sndio backend for Godot, but in that case the engine itself does the decoding and the driver only needs to play the given array of samples, so itâs kind of cheating.
I my naĂŻvety I didnât think that every format has its own libraries with their own APIs, but it makes sense. What it doesnât make sense is the complete lack of decent documentation!
Iâm talking about libvorbisfile, libopusfile, libflac and libmad. Out of these four libraries none had a single man page. No, I donât consider doxygen-generated pages to be âdocumentationâ, nor header files filled with HTML! (Who, who the fuck thought that putting HTML in header files was a good idea?)
Sure, these libraries have all the functions carefully described in a web page, but whatâs lacking is some sort of global picture. (Plus, the example code was awful for the most part.)
Fortunately theyâre not too hard to use and one can actually write a decoder in a couple of hours starting from knowing nothing. However, Iâd like to do a special mention for libflac: it beats openssl in my personal list of âworst api naming everâ.
I forgot one thing: amused has only OpenBSD as a target. Itâs the only OS I use and I only âknowâ how to use sndio, so⌠but I could try to make a portable release eventually. Itâs a pretty stable program which I guess itâs already pretty much done, modulo bugs and eventually adding support for more audio formats.
Which takes me to the list of missing things:
and fixing bugs, if any :)
-- text: CC0 1.0; code: public domain (unless specified otherwise). No copyright here.