Programming, philosophy, pedaling.

Introducing Muzak - A "Metamusic" Player

Nov 29, 2016

Tags: programming, ruby, muzak

Like a lot of people, I enjoy listening to music as I work. It keeps me focused on the task at hand, and allows me to slow down my thinking pace. Without it, I tend to give too much thought to individual components of whatever larger task I'm working on.

Unlike a lot of people (or, at the very least, an increasingly large minority of programmers), I don't like using a streaming service (Spotify, Pandora, &c.) to listen to music. There are a number of reasons why I prefer to avoid such services (you can find one in my rant about CAN-SPAM compliance), but it essentially comes down to three observations:

For a long time, my solution to all of this was to keep my (very large) music library on a server running Subsonic and, when that became proprietary, Libresonic.


This setup allowed me to access all of my music on all my devices (including my phone), at my preferred quality without having to duplicate and sync over 150GB across N devices. It also allowed me to share access with my friends in a controlled fashion, allowing them to upload their own music and allowing me to set bitrate limits when streaming traffic began to negatively affect my home network. For all of these benefits, Subsonic/Libresonic also had plenty of downsides:

When the server that was hosting my Libresonic instance finally kicked the bucket (it was over a decade old, with repurposed disks), I decided to look into replacing it with something simpler. I came up with the following design goals:

With these in mind, I created muzak:


Just as the genre isn't "real" music, muzak isn't a "real" music player. I've been calling it a "metamusic player," which is just a fancy way of saying "index manager that also controls your player of choice via IPC and an interactive prompt."

In action (ffmpeg mangled the audio recording, actual playback is fine):

Click to play on YouTube.

As evidenced, muzak is still very much a work-in-progress. The demonstrated interface (muzak.rb) is just one potential frontend -- it's really just some boilerplate and a readline loop:

opts = {
  debug: ARGV.include?("--debug") || ARGV.include?("-d"),
  verbose: ARGV.include?("--verbose") || ARGV.include?("-v"),
  batch: ARGV.include?("--batch") || ARGV.include?("-b")

muzak = Muzak::Instance.new(opts)

while line = Readline.readline("muzak> ", true)
  cmd_argv = Shellwords.split(line)
  next if cmd_argv.empty?
  muzak.send Muzak::Cmd.resolve_command(cmd_argv.shift), *cmd_argv

As suggested by Muzak::Cmd.resolve_command and muzak.send, interactive commands are just methods defined under Muzak::Instance (by convention, included through Muzak::Cmd):

def self.resolve_command(cmd)
  cmd.tr "-", "_"

def help(*args)
  commands = Muzak::Cmd.humanize_commands!.join(", ")
  info "available commands: #{commands}"

This makes extending muzak's "API" extremely easy, as all new commands are just ruby methods that take a variable number of arguments.

Future Directions

There's quite a bit more work to be done, but here are some immediate steps I plan to take:

As it currently stands, muzak is just a side project. I'm not quite sure where it'll go, but I hope that it'll eventually become flexible enough to replace my dependence on Subsonic/Libresonic and other opaque streaming software.

The source code can be found here. Please try it out, and thanks for reading!

- William