HOWTO - Soekris dam1021 DAC and MPD on Raspberry Pi

Introduction

See the related article, ALSA Dummy Driver Events as MPD Hooks for additional context.

This article describes how to use a Soekris dam1021 DAC with Volumio. In particular, it allows you to use MPD's volume control to use the dam1021's built-in hardware volume control. It does so by adding a "hook" to the ALSA dummy mixer device that actually sends volume control commands to the DAC.

To give credit where credit is due, this is basically a poor man's version of Muffinman's Synchronator. From Synchronator's README:

Synchronator brings bit perfect volume control to Hi-Fi systems
with Linux as source.

This enables control of your Hi-Fi amplifier volume level for
Airplay, DLNA, OpenHome, MPD, Squeezelite, and Roon a.o.

...In Linux it is not uncommon that audio applications, such as
MPD and Shairport, allow audio data and mixer/volume data to be
send to different (audio) devices. By sending mixer data to a
dummy/virtual soundcard, volume control can be enabled without
touching the audio data. Synchronator in turn can synchronise
that volume level with any Hi-Fi system/amplifier that can be
externally controlled (RS232/I2C/TCP/IR). In addition, changes
in volume level at the amplifier side are synced back.

Synchronator is indeed a more robust, full-featured version of what is being done here. You could presumably install, build and configure Synchronator to accomplish the same goal, and learn a tool that might be useful in other applications as well. The approach described here uses the same exact concept, but it is simpler since it is designed to be very specific, i.e. link the dam1021's builtin hardware volume control to MPD.

NOTE: I originally had this set up on Archphile, which is now defunct. I haven't had time to re-create the setup under a new distribution. So these instructions haven't been well-tested. But at a minimum they hopefully provide enough foundational info.

HOWTO

Needed Packages

Normunds Input/Switch Board Fixup

If you are using the Normunds Input/Switch Board, then you will need to set the I2S select pin appropriately. I did this with the gpio utility from the wiringpi_ package. I wrapped it up in a little script I call fixup.sh, see below. (Note pin15 == GPIO 22 == wiringPi pin 3, see explanation of Raspberry Pi GIO pin numbering.)

    #!/bin/bash

    GPIO=/usr/bin/gpio

    $GPIO mode 3 out
    $GPIO mode 3 down

ALSA Dummy Module

Might need to install ALSA packages, depending on distribution: alsa-utils alsa-lib alsa-firmware

install alsa dummy module: modrobe snd_dummy auto-load at boot, add "snd-dummy" to: /etc/modules-load.d/modules.conf

listener.py script

Download here: listener.py, or copy and paste:


    #!/usr/bin/python2

    import alsaaudio as alsa
    import select, dam1021

    ##############################################################################
    # * dam1021 true range: [-80,10]
    #   -> treat as [-80,0]
    # * alsa range is [-50,100]
    #   -> treat as [0,100] because this is what MPD and alsamixer
    #      produce when changing volume (FIXME: why the discrepancy?)
    # * limit dam1021 volume to -30 in case of mistakes, save
    #   ears/speakers
    def vol_convert_dam1021(alsavol):
        LIMIT = -10
        pct = float(alsavol)/100.0
        damvol = int(pct*80.0+0.5) - 80
        if (damvol > LIMIT): damvol = LIMIT
        return damvol
    
    ##############################################################################
    if __name__ == '__main__':
        conn = dam1021.Connection('/dev/ttyAMA0')
        mixer = alsa.Mixer(device='hw:Dummy', control='Master')
        poll = select.poll()
        descriptors = mixer.polldescriptors()
        poll.register(descriptors[0][0])
        print 'begin poll...'
        while True:
            events = poll.poll()
            mixer.handleevents()
            print '  event:'
            for e in events:
                fd = e[0]
                event = e[1]
                alsavol = mixer.getvolume()
                damvol = vol_convert_dam1021(alsavol[0])
                print '    fd=%d event=%d alsavol=%s damvol=%d' % \
                    (fd, event, alsavol, damvol)
                conn.set_current_volume_level(damvol)

mpd.conf

You will need something like this in /etc/mpd.conf::

audio_output {
    enabled         "yes"
    type            "alsa"
    name            "MY DAC"
    device          "hw:1,0"
    format          "*:32:*"
    auto_resample   "no"
    auto_channels   "no"
    auto_format     "no"
    dop             "no" # default yes
    mixer_type     "hardware"
    mixer_device   "hw:Dummy"
    mixer_control  "Master"
}