💾 Archived View for gemini.omarpolo.com › post › amused.gmi captured on 2023-04-26 at 13:05:13. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

➡️ Next capture (2024-03-21)

-=-=-=-=-=-=-

I wrote a music player

experiments that I end up liking

Written while listening to “Century Child” by Nightwish.

Published: 2022-02-19

Tagged with:

#c

#music

This is a partial and unaccurate translation of a post with the same title I published the other day on my Italian capsule.

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!

amused

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.