So guess who just finished Kindergarten?
Congrats, little boy.
In recognition of Dylan’s Kindergarten Graduation (which I guess is a thing), I built him a present. Dylan likes to listen to music when he’s going to bed, just like I did when I was a kid (honestly, just like I did pretty much up through my twenties). And since he’s also been getting into Doctor Who, he took an interest in the possibility of listening to some of my Doctor Who radio adaptations (The BBC productions are pretty safe. I’m going to have to heavily vet the Big Finish ones). Since his MP3 player’s got a modest capacity and modest battery life and is a little cumbersome to load, I decided that, as a Kindergarten graduate, he was mature enough to have a network-connected music player.
So I built one.
This isn’t going to be a full instructable, since, as per usual, I just sort of fumbled my way forward until it worked and neglected to document things. But maybe it’ll be enough to get you started.
The Hardware
Okay, so I did not actually start this project specifically for Dylan’s sake; I’d come up with the idea of making small networked music players last year with the goal of just coordinating Christmas music between the family room and the dining room. IoT speakers are a thing now, obviously, but I wanted a few things specifically:
- To play music from my local collection on the NAS, rather than streaming it from a cloud service, particularly one that would ask my to buy or subscribe to things I already owned
- To synchronize playback between multiple devices
- To just be a music player and not quietly listen in on me all the time waiting for its moment to betray me when the robot empire takes over help me buy more lightbulbs or whatever.
- Fit inside the 1960s clock radio I inherited from my parents
The obvious choice of base platform for something like this was the Raspberry Pi Zero W, a ten dollar Single Board Computer that has onboard wifi and bluetooth and is about the size of a key fob (the fancy big ones where the key switchblades out of the side).
The full-sized Raspberry Pi models have analogue audio output. The original hardware versions had a dedicated headphone jack, while later ones use a combined jack that carries analogue audio and video. But the zero drops the analogue jack altogether. Apparently, you can produce analogue audio on the GPIO pins by just adding a couple of resistors in the right places, but there’s another option.
As with most Raspberry Pi products, demand outstripped supply for a while, so part of the proximate motivation for this project was that Adafruit had this bundle which included the Zero along with a bonnet (ie. a tiny little sidekick card that plugs into the top of the Pi. Ones scaled to the full-sized Raspberry Pi are called “HATs”) that could drive 3 watt speakers, and a set of speakers. (Adafruit also sells the bonnet separately, but at the time, bundles were pretty much the only things that would stay in stock long enough for a normal human to buy them). The included speakers aren’t great, but they’re at least as good as the speaker in Dylan’s MP3 player. I only ended up using the Adafruit speakers for the first one I built. For the second, I attached RCA plugs and used the speakers from an old bookshelf system we were getting rid of. The third one, I have wired to the ancient speaker from my clock radio, which sounds fantastic, but the poor thing isn’t putting out enough power to drive it at full volume.
This project required a bit of soldering, and on my first try, I donked it up a little, damaging the pad for one of the pins I needed. Fortunately, I was able to attach a header elsewhere and use a jumper wire to move the signal to the right pin. I also had a few DHT11 temperature sensors lying around, so I wired that up to the other pins so that my device could also mesh with my household network of sensors.
For a case, I gutted an old 10/100 ethernet switch. It was a tight fit. Too tight, if I’m being honest, but it was what I had on hand. I drilled holes through the top and hot glued the speakers under them. There was just enough space in the middle for the bonnetted Zero… if you didn’t connect the USB cables that it needed for power. But I jiggled things around and finally got it to more-or-less fit with the help of the fact that a network switch’s case has a hole in the back where the network jacks go. The second version, the one which had RCA jacks instead of integrated speakers, I enclosed in a modified electrical outlet child safety cover. That one I’d hoped to just stick inside the speaker housing, but thanks to some miscommunication, Leah had donated the set of speakers I wanted to use to charity and kept a different set. No big deal. Version 3 isn’t done yet because even though the pi and supporting hardware is a lot smaller than the clock radio’s original innards, it’s the wrong shape and I’m still working out how to fit it all inside.
The first device, as built, promptly got plugged in and stuck on my nightstand where I never bothered with it. It has, you may notice, no user interface. It has to be controlled remotely, and a smartphone is a perfectly good way to do this. But once I got it into my head to give this device to Dylan, that wasn’t going to cut it. I mean. half the motivation here is to let him listen to music in bed without giving him a tablet.
I spent several hours looking at human interface devices before it suddenly occurred to me that I’d bought a bunch of USB gamepads of various designs a few years back and only ever used them once or twice (I also bought a powered 20-foot USB cable as part of this experiment. It never worked great. I think the powered USB cable was introducing some kind of jitter. I eventually tried switching to bluetooth, and had various problems from not being able to connect at all to being able to connect, but then needing a reboot to get it to reconnect once the controller had gone to sleep. But that’s a tale for another day). So with a bit of work, I managed to cram another USB plug into the Zero, and now I had a gamepad connected to the music player.
The Software
For the OS on the Zero, I went with Raspbian Jesse Lite, a stripped-down flavor of the default Raspberry Pi Linux distribution. The “Lite” here was important because I had some fairly small SD cards that I wanted to get some use out of. But there was a bonus here I only learned later. The full Raspbian image installs X, the standard Linux windowing system. Because normally you’d want that on a computer. On all of my previous Raspberry Pi setups, I’d gone ahead and left X installed in case I ever needed it, but set up the pi not to launch it automatically. And this was all very good and fine, but I’ve discovered that if you do install X, then PulseAudio, the standard engine for accessing the audio hardware in most contemporary Linux installations, gets configured in a way that assumes X is running. When I tried setting up a regular Raspbian installation to run the same music software as my Zero-based devices, I ran into an issue where every time as song finished, PulseAudio would panic over the fact that it couldn’t talk to the X session it was expecting to be there, and crashed. And several things I did which I thought ought to have set this right just didn’t work. I can get it to play one song, and then I have to manually restart a bunch of daemons to get audio working again. If you never install X, this doesn’t come up and it all just works.
For the music player itself, I went with MPD, the Music Player Daemon. There’s a few things about it I’m not crazy about, but it seems to do everything I wanted. For my purposes, one of the nicest features is that you can slave one installation to use another installation’s database. Because of the small storage space and limited processing power on the Zero, this was a godsend. But even if those weren’t issues, the fact that I can maintain one database on my main media center computer and have it apply to every media player in the house is literally the thing I wanted out of this. A common repository of songs and playlists for as many devices as I want. Each individual instance can also publish its own audio stream via HTTP, so I can point any player in the house at any other player in the house and they’ll play the same thing. There are other solutions which can accomplish something similar to this, but my setup with MPD lets me choose which rooms play which things; they don’t all have to be the same. I can have the family room and the dining room play one thing, while Dylan’s room plays something else, and once I build a couple more, I can arbitrarily mix and match.
I installed MPD on my main media center computer, a much beefier i5 system running Linux Mint. The installation is a little different there, since that one is running X, so the MPD process gets run as a user application rather than a system daemon. It hosts the main database. The smaller devices SMB or NFS mount the NAS to reach the music files. MPD doesn’t actually require this: it can speak SMB and NFS directly. But there seems to be a weird bug where if you do that, filetypes other than MP3 don’t work. I wanted to support my old MIDI collection, so I had to actually mount the filesystem. Another little quirk is that while MPD supports automatic volume normalization, on the Zeroes, this made the playback weird and crackly and clipped. It worked fine on the media center. Another issue is that the audio bonnet doesn’t expose any hardware volume control, so I needed to configure PulseAudio to do volume control in software, and then configure MPD to use the software volume controls.
The other major quirk had to do with networking. MPD had a number of frontends you can install. When I’m at my computer, I just use the command-line client, MPC. For my phone, I installed MALP. Of all the MPD clients I’ve found for Android, MALP is the only one which makes it easy to switch between multiple MPD instances. Most of the others assume you only have one MPD server. The downside to MALP is that it requires a pretty recent version of Android. That might not be a problem for you, but you know how it is with phone manufacturers and system updates. Of the dozen or so Android devices in our house, only three could run it. But I discovered an odd issue where if my phone went to sleep with MALP open and connected to one of the Zeroes, the Zero would lose its wifi connection. You could bring it back up by restarting the networking service, but that’s hard to do on a headless box that’s lost its wifi connection. I mitigated this by writing a cron job that restarted networking (or failing that, rebooted) if the network went away for more than a minute or two. Some searching led me to believe that this might be a bug in the kernel network driver, and very weirdly, it seems to be completely specific to talking over wifi between a Raspberry Pi (Either 3 or Zero W) and a Samsung phone. It hasn’t happened in a while, so I’m hopeful that some kernel update fixed it.
It’s not a perfect solution, even so. Streaming from one Zero to another is more theoretical than practical because of the CPU usage involved. And the streaming isn’t very tightly synchronized. Leah’s immediate reaction on Christmas was to be annoyed that the music in the family room was half a second ahead of the dining room. You can stream audio between devices with lower latency using other methods, but I wasn’t able to get it working in a way that still let me do all the other things I wanted.
The last leg before I gave it to Dylan was to make it controllable via the attached gamepad. For that, I banged out a perl program to translate gamepad input into commands to MPD. If you are interested, here it is. It requires the Input::Joystick module from CPAN, but is otherwise pretty much self-contained (Just plugged the gamepad in and it showed up as an input device. I’ve been using Linux long enough that it still amazes me when I plug something in and it Just Works). It works by invoking the command line MPC client. I know that’s a little suspect (It also logs by making shell calls to the command line “logger” command, which is even more suspect. I am lazy). There are a couple of MPD modules in CPAN, and I’ve worked with them before, but one of them had a lot of dependencies I didn’t want to bother with, and I had some strange performance issues with the other. In addition to translating input from the controller, it implements a sleep timer and a volume limiter. I also designed it with the ability to cycle through a filtered subset of all playlists. You can just toss a new m3u file in your playlist directory whenever you like, and it’ll instantly be available (provided the name of the file matches the filter. In my case, that means that the name starts with “DYLAN”). Since I set up the playlist directory to be on the NAS, I don’t even have to log into the music player to edit the playlists. And, of course, I can remotely control the player myself via MALP or from any computer with MPC on it.
Dylan was incredibly impressed (aside from a couple of times when he ran in to tell me it wasn’t working. Pretty sure that any time it took more than a tenth of a second to queue up the next song, he’d start hammering buttons and confuse the poor thing. It always resolved itself if he just stopped futzing with it for a second), and asked if I could add a screen. Which isn’t out of the question, but his goal here is a backdoor into being able to stay up all night watching TV on the DL.
I hope to build some more of these. I had this idea that maybe when Evie is ready for one, instead of a game controller, I’d hook up a camera and print cards with QR codes on them to control hers that way. Leah points out that she’d just lose the cards immediately (Though I suppose I could tether them to something).