Dec 12, 2016 Tags: muzak, programming, ruby
This post is at least a year old.
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 (muzakd
+
muzak-dmenu
+ 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 muzak
command, which provided a pseudo-shell and tab completions for both commands
and albums/artists.
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 muzakd
:
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.
muzak-dmenu
: Spawns dmenu
and feeds it entries from Muzak::Cmd.commands
,
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).
Now, when 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 album
) contains
a serialized Muzak::Song
instance for each song in the album. The primary
benefit of this is significantly faster playlist loading (Muzak::Song.new
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
form of 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 lib/muzak/plugins
tree
(reserved for plugins shipped with muzak), or ~/.config/muzak/plugins
. The
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!
gem
availabilityMuzak is now available on rubygems, which means that installing it is as simple as:
1
$ sudo gem install muzak
or:
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 muzak
, muzakd
,
muzak-cmd
, and muzak-dmenu
executables.
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.
The 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
inner-platform effect.
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 vo
on 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.
When 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 muzakd
without forking via --debug
and/or --verbose
and to pay attention to the
parent terminal. Replacing this will probably involve swapping muzakd
’s named
pipe for a UNIX domain socket and properly returning messages from muzak’s many
commands.
Thanks for reading!
- William