Tags: programming, ruby, muzak
Preword: This is the second weekly post on muzak’s development, following the introductory post and last week’s post. It’s finals (and holiday) season, so there may not be a post next week or the week after.
Just like last week’s post, some quick statistics:
git diff --stat HEAD HEAD~44
Although the number of commits week-over-week rose substantially, the actual number of line changes dropped dramatically. I attribute this to muzak’s internal API becoming more stable and most of this week’s work being user-facing.
As of today, this is what a typical muzak session (
muzak-cmd) can look like:
(screenkey is being used to display my keybindings)
Last week, the only way to control a muzak instance was through the
command, which provided a pseudo-shell and tab completions for both commands
This week, I added
muzakd, a daemonized version of
muzak that reads commands
from a named pipe (
~/.config/muzak/muzak/muzak.fifo) and passes them on to
an instance. I also added two simple scripts for controlling
muzak-cmd: About as simple as
echo "<command>" | ~/.config/muzak/muzak.fifo, but with some internal sanity
checking to give the user a bit of feedback.
dmenu and feeds it entries from
giving users the ability to quickly fuzzy-find and tab-complete to a command.
Both of these scripts work very nicely when bound to key sequences. I have the following mapping, locally (i3 config):
1 2 3 4 bindsym Control+End exec muzak-cmd toggle bindsym Control+Left exec muzak-cmd previous bindsym Control+Right exec muzak-cmd next bindsym Control+Delete exec muzak-dmenu
Previously, muzak’s index (
~/.config/muzak/index.yml) stored only a
representation of the file tree, with no ID3 tagging in the index itself
(as explained in the previous post, ID3 tags were stored in playlists
and loaded on-demand as required for events).
deep-index is set to
true in muzak’s configuration,
a “deep” index will be stored alongside the standard “shallow” representation.
This index (really just an extra key-array pair under each
Muzak::Song instance for each song in the album. The primary
benefit of this is significantly faster playlist loading (
can be quite slow when called hundreds of times in a row) and generally
diminished disk access, which should help when muzak is being used with a
nonlocal music tree.
As promised last week, muzak now specifies its expected player API in the
Muzak::Player::StubPlayer. All implemented players should inherit
from this class, which currently contains 15 necessary methods.
Muzak’s plugin API is virtually unchanged from last week. However, plugins
can now be loaded from two locations: either the
(reserved for plugins shipped with muzak), or
latter is for users to add their own custom plugins, and plugins
added to it behave identically to plugins added via the reserved tree.
Just remember, plugins are only activated if
plugin-<name> is present
as a key in the configuration!
Muzak is now available on rubygems, which means that installing it is as simple as:
1 $ sudo gem install muzak
1 $ gem install --user-install muzak # make sure that you've configured your PATH
This will install muzak’s entire library, as well as the
Previously, muzak only allowed songs to be enqueued from three sources:
albums, artists, and playlists. This covers a lot of use-cases, but another
common use case is a random shuffle throughout the entire library. This is
now accomplished with the
jukebox [N], where
[N] is an optional number
of random songs to enqueue. If not included,
jukebox defaults to the
value stored in
jukebox-size in the configuration.
config-set command has been removed, for a few reasons:
config-set made type coercion difficult, especially when dumping the
configuration to YAML. This isn’t helped by the fact that Ruby doesn’t have
an idiomatic, one-line way to turn
"true" into a boolean.
muzak’s pseudo-shell isn’t particularly flexible when it comes to
whitespace parsing or nested structures, and fixing these issues within
muzak itself would have been the beginning of an
YAML is already extremely easy to read and manually modify, so having users change their own settings via their editor of choice should suffice.
Development is definitely going to slow down as I go into my final exams and the winter break, but here are some of my general goals for muzak over that period:
Dynamic playlist loading. Right now, muzak only loads whatever playlist
is specified in
default-playlist. This is simple, but it doesn’t reflect
the way people generally use playlists (i.e., having immediate access to
multiple lists, including a “favorites” list).
Isolated album art. Right now,
mpv is used for both audio playback
and album art (via
--external-file, which has
unusual behavior for single-frame files).
I’d like to isolate the two by disabling
mpv entirely and providing
image paths/streams to a simple image viewer like
feh. This is slightly
complicated by the fact that not all album art is stored in discrete files,
but is often inside the music files themselves as ID3 (or equivalent) tags.
Two-way communication with
muzakd, including JSON commands and responses.
muzakd is used to spawn a muzak instance, interaction is essentially
one-way - the user issues commands, and hopes that they get interpreted
correctly. The only way to actually confirm command success is to run
without forking via
--verbose and to pay attention to the
parent terminal. Replacing this will probably involve swapping
pipe for a UNIX domain socket and properly returning messages from muzak’s many
Thanks for reading!