ALSA Dummy Driver Events

Introduction

My most recent diyAudio projects have centered around digital to analog converters (DACs). More specifically, DACs that can be controlled through a microcontroller. In my case, I'm actually using a Raspberry Pi (a particular kind of single board computer aka SBC) as a microcontroller. The DAC I've spent the most time working with is the Soekris dam1021. The dam1021 presents a serial interface that allows, among other things, volume control. (Note that the dam1021 also allows volume adjustment via potentiometer, but digital control via serial opens up many possibilities that will be explored here.) Next on my list of "programmable" DACs is the DIYINHK es9018k2m DAC, which can be controlled through an I2C interface.

The more "traditional", i.e. standalone, DAC sits between some kind of playback device (computer, CD player) and an amplifier. However, with the recent proliferation (and low cost) of SBCs, it is natural to merge the DAC with the playback device. Combined with some useful softare (such as mpd), the DAC is essentially transformed into a music server. At least for me (being a computer geek), this seems like a natural architecture. I keep all my music on a central storage server (tucked away in a basement closet). The SBC, through its network connection, can access the entire music library. The SBC is then responsible for playback control (e.g. playlist manipulation, song selection, possibly volume control) and shuffling the digital audio bits to the DAC (through I2S).

On the topic of volume control: all my life I've been rolling knobs or pushing up/down buttons to control volume on various devices, never realizing that it's actually a nuanced feature. First, where do you adjust volume, in the digital or analog domain? And once you decide on that, how (what mechanism) do you use? Here we focus on volume control in the digital domain, using the mechanisms that are native to the DAC implementation we're using (i.e. Soekris dam1021 or es9018k2m). The actual mechanics of how these volume controls work internally is beyond the scope of this article. We will instead focus on how to manipulate those controls.

Desired Config

What want is a device that allows for both software and physical volume control. More specifically, I want to be able to control volume through software via MPD client applications, such as Auremo or MPD Control. At the same time, I also want a physical knob that I can turn with my hands.

This may sound fancy but it's reasonably simple. Ultimately, the volume control will be through commands sent from the SBC to the DAC. In the Soekris case, volume control commands will be sent over the serial interface; in the es9018k2m case, those instructions will be sent over I2C. That's the easy part.

The "magic" comes from bridging the two desired control mechanisms. In the physical knob case, I will use a rotary encoder. The Raspberry Pi will interpret signals from the rotary encoder as "volume up" or "volume down" commands, translate as necessary, and send to the DAC. This might be the topic of a future article; but for now, please see Reading a Rotary Encoder from a Raspberry Pi.

The software case is more interesting, and the subject of this article. I will be using MPD, which has a volume control interface. In the simple (likely most common) use-case, MPD's volume control is directly tied to the underlying audio device. MPD supports multiple audio platforms, such as PulseAudio, Jack, and of course, ALSA. Audio purists know that ALSA is generally the right choice, as it's the easiest to configure for bit-perfect playback. ALSA is actually the driver layer on Linux, all other "platforms" are actually built on top of ALSA[#oss]_. Point is, audiophiles know that they just want to push the bits directly to the DAC, without any manipulation (i.e. bit perfect), so the simplest way to do that is to speak directly to the driver.

Since we are using I2S to send audio data from the computer to the DAC, we don't have a "traditional" audio chip. That is, there is no hardware control of volume. Using the ALSA vernacular, this means that an I2S DAC does not have a mixer. So how are we to control volume?

The first option is to use software volume control. This involves manipulating the bits that are sent to the DAC. This is quick and easy for casual listening, but not acceptable for those of us obsessed with audio quality.

The second option is to leave the audio bits alone, but use the DAC's builtin mechanism for volume control. Granted, depending on the DAC we're using, the hardware could simply manipulate the incoming bits the same way software would, and the result would be more or less the same. But as we're using higher-end devices (Soekris, ESS Sabre), we're assuming their volume manipulation mechanisms are superior to what software would do.

[1]OK, you could also use OSS instead of ALSA. Not sure how many people are doing that, though.

Nuts and Bolts

To summarize the lengthy intro above:

  • We are using ALSA to pass PCM data through I2S from the computer to the DAC
  • In this config, ALSA will not present a hardware mixer device
  • We want to use MPD for volume control
  • Specifically, we want MPD to manipulate a hardware mixer (that we don't have)

To me, this begs for an MPD enhancement: a way to add "hooks" to some MPD operations (at least volume control in this case). That is, I envision a mechanism whereby in the MPD configuration file, you can say something to the effect of, on volume up, run this script; and on volume down, run this script. That feature doesn't exist, but when I started researching it, I found the following MPD forum post: Control volume with custom scripts.

In this thread, user Muffinman presents his project Synchronator. This is exactly the solution we're looking for! In Muffinman's own words (slightly edited for context):

In short: by activating the Alsa dummy soundcard you can enable MPD volume control without actually touching the actual audiosignal. Synchronator in turn listens for mixer events on that mixer and sends the appropriate commands to the amp via serial, tcp, or i2c. The advantage of this setup, you can run other audio applications that make use of the same approach simultaneously, e.g. Shairport, Squeezelite.

Currently configuration files exist for Arcam, Cambridge Audio, NAD, Lyngdorf, Dynaudio, Classe, among others. This should work for many more (most?) controllable amplifiers though.

If you don't care about the details, grab Synchronator, build, install, configure it and be done! But I created this article as means to provide a bit more detail and background, to shine a bit of light on what's going on behind the scenes.

My first question about Synchronator was, how do I activate the dummy soundcard? You can read Matrix:Module-dummy from the ALSA documentation, or just do this:

modprobe snd-dummy

Once the snd-dummy module is loaded, you can see it via "aplay -L":

$ aplay -L
null
    Discard all samples (playback) or generate zero samples (capture)

Also, when alsamixer is started, you should see the following in the upper right:

Card: Dummy
Chip: Dummy Mixer

To be continued...

Platform Notes

I'm doing all my testing on my CentOS server. This is a true server, hidden in a closet in my basement. That means stability is a primary goal, and as such I try to avoid installing any packages I don't need. So to test this out, I actually had to install several ALSA packages. In particular, I have the following installed on my system:

  • alsa-firmware
  • alsa-lib
  • alsa-lib-devel
  • alsa-tools-firmware
  • alsa-utils

I also had to add my normal user to the "audio" group, otherwise I got "permission denied" when trying to run alsamixer:

$ alsamixer
cannot open mixer: Permission denied