JACK2 control

jackdmp control scripts.

About

Recent versions of Jack2 aka. jackdmp can be controlled via Dbus and most interestingly, jackdmp allows the backend-driver to be replaced dynamically even while jackd is running!

There's two main use-cases that motivated me:

  • to be able to quickly switch between the internal and an external soundcard.
  • to have JACK sessions survive system suspend/resume cycles using the “dummy” driver.

I hardly ever re-boot my laptop and I keep jackd running anytime 1). So far I use Qjackctl's Dbus interface to start/stop jackd during suspend; disadvantage is that it terminates all existing jack sessions; it's not a big deal since most apps can simply re-connect but if jackd can be kept running: why not do so?

I did the switch: jackd here is now jackdmp.. while audio-applications are ignorant about the change and everything there works just like it was before with jack1, suspend-resume and qjackctl is now somewhat broken. qjackctl (v0.3.6.22) sometimes gets stuck when switching backends; this is a major bummer since I like it's connection patchbay. patchage (v0.4.4) suffers a similar issue. It does not update the ports correctly. Anyway those problems can be fixed in future releases (and were in qjackctl 0.3.6.24). But I hit another little snag: Calling jack_control sm - which is needed to switch the backend - drops existing connections to system:* i/o ports. OK. The ports may be different in some setups but they're not here: switching between two stereo cards.

Long story short, here are two shell-scripts. The first to switch between different sound-cards retaining connections. and a second to take care of switching to the dummy-driver and back during suspend/resume cycles. They're rather pragmatic hacks, but good enough to get started.

Setup

Installation steps:

  1. install latest qjackctl (>= 0.3.6.24) and jackdmp (>=1.9.6) and enable DBus-interface in qjackctl's Setup→Misc.
  2. copy the myjackctl.sh script to some folder and make it executable (chmod +x myjackctl.sh).
  3. save the 90myjackctl.sh script to /etc/pm/sleep.d or a similar folder where it gets executed as hook during suspend/resume
  4. edit the 90myjackctl.sh and set the path to myjackctl.sh
  5. optionally create a ~/.myjackctl file (example below) which allows to override the settings when resuming the system. By default the parameters used before suspend are restored.
  6. test it:
    1. start jackdmp: either via qjactctl, or eg with: myjackctl.sh alsa hw:0 1024 48000 3
    2. start some music, fi. mplayer
    3. sudo etc/pm/sleep.d/90jack2ctl.sh suspend → qjackctl is disconnected and jackdmp-dummy audiodriver is loaded.
    4. sudo etc/pm/sleep.d/90jack2ctl.sh resume → previous audio settings are restored and qjackctl reconnected.
    5. call sudo pm-suspend (or activate your computer's suspend-button aka close-lid) to test a real suspend/resume cycle.

myjackctl features and limitations

  • easy switch between jackdmp backends retaining system port connections
  • suspend script works for multiple-users, but at most one jackdmp per user.
  • dummy driver is created with identical numeber of I/O ports as previous interface.
  • tested on Debian.

The Source

~/bin/myjackctl.sh:

#!/bin/sh

DSESSIONBUS=`ls -t ~/.dbus/session-bus/* | head -n1`
if [ -n "$DSESSIONBUS" -a -f "$DSESSIONBUS" ]; then
 . $DSESSIONBUS
 export DBUS_SESSION_BUS_ADDRESS
fi

function jackctl {
  dbus-send --session --print-reply --dest=org.jackaudio.service \
            /org/jackaudio/Controller \
             org.jackaudio.JackControl.$1
}

function jackcfg {
  dbus-send --session --print-reply --dest=org.jackaudio.service \
            /org/jackaudio/Controller \
             org.jackaudio.Configure.SetParameterValue \
            array:string:$1,$2 \
            variant:$3:"$4" >/dev/null
}

function jackget {
  dbus-send --session --print-reply --dest=org.jackaudio.service \
            /org/jackaudio/Controller \
             org.jackaudio.Configure.GetParameterValue \
            array:string:$1,$2
}

function jackports {
  dbus-send --session --print-reply --dest=org.jackaudio.service \
            /org/jackaudio/Controller \
             org.jackaudio.JackPatchbay.GetAllPorts \
  | awk '/string "(.*)"/{match($0, /string "(.*)"/, a); printf "\"%s\"\n", a[1]; }'
}

function jackconnections {
  dbus-send --session --print-reply --dest=org.jackaudio.service \
            /org/jackaudio/Controller \
             org.jackaudio.JackPatchbay.GetGraph \
            uint64:0 \
  | awk 'BEGIN{i=0;} /^   array/{i++;} /^      }/{if (i==2) printf "\n";} /string "(.*)"/ {if (i==2) {match($0, /string "(.*)"/, a); printf "string:\"%s\" ", a[1]; } }'
}

function connect_ports {
  eval `echo \
  dbus-send \
    --session \
    --print-reply \
    --dest=org.jackaudio.service \
    /org/jackaudio/Controller \
    org.jackaudio.JackPatchbay.ConnectPortsByName \
    $@` > /dev/null
}

function switch2 {
  CONNECTIONS=$(jackconnections)
  # TODO: remember device name of old interface
  jackctl SwitchMaster > /dev/null
  jack_wait -w -t 5 > /dev/null
  # TODO: get device name of new interface

    # TODO restore connections to physical ports: 'system' and similar phyical IDs!!)
  CONNECTIONS=$(echo "$CONNECTIONS" | grep '"system"')
    # TODO Map connections e.g.
  # replace    string:"system" string:"playback_1" -> string:"ffado" string:"playback_7"
  # replace    string:"system" string:"playback_2" -> string:"ffado" string:"playback_8"
  IFS=$'\n'
  for line in $CONNECTIONS; do
    #echo "connect ${line}"
        connect_ports ${line}
  done
}

function start {
  DRIVER=${1-"alsa"}
  DEVICE=${2-"hw:0"}
  if [ "$DRIVER" == "dummy" ];then
        PNUMP=${3-"2"}
        PNUMC=${4-"2"}
        PERIOD=0
        RATE=0
        NPERIODS=0
        MIDI=""
    else
        PERIOD=${3-"1024"}
        RATE=${4-"48000"}
        NPERIODS=${5-"3"}
        MIDI=${6-"none"}
  fi

  jackcfg engine driver string "$DRIVER"
  test "$DRIVER" == "dummy" -o -z "$DEVICE"|| jackcfg driver device string "$DEVICE"
  test "$DRIVER" == "dummy" -a -n "$PNUMP" && jackcfg driver playback uint32 "$PNUMP"
  test "$DRIVER" == "dummy" -a -n "$PNUMC" && jackcfg driver capture  uint32 "$PNUMC"

  test $RATE -eq 0   || jackcfg driver rate uint32 $RATE
  test $PERIOD -eq 0 || jackcfg driver period uint32 $PERIOD
  test $NPERIODS -eq 0 -o "$DRIVER" == "dummy" || \
                            jackcfg driver nperiods  uint32 $NPERIODS
  test "$DRIVER" == "dummy" -o -n "$MIDI" || jackcfg driver midi-driver string "$MIDI"

  jackctl IsStarted | grep "boolean false" >/dev/null && (
        jackcfg engine realtime boolean true
        jackcfg engine realtime-priority int32 70
        jackcfg engine sync boolean true
      jackctl StartServer > /dev/null
    )
}

if [ "$1" == "config" ];then
  jackget engine driver | tail -n1 | awk '{printf "SNDDRV=%s\n",$3;}'
  jackget driver device | tail -n1 | awk '{printf "SNDDEV=%s\n",$3;}'
    #echo "PNUMC=\"`jack_lsp | grep system:capture  | wc -l`\""
  echo "PNUMC=\"`jackports |  grep system:capture  | wc -l`\""
    #echo "PNUMP=\"`jack_lsp | grep system:playback | wc -l`\""
    echo "PNUMP=\"`jackports | grep system:playback | wc -l`\""
  exit
fi

#dbus-send --system /org/rncbc/qjackctl org.rncbc.qjackctl.stop
start $@
switch2

#test "$1" == "dummy" || \
#  dbus-send --system /org/rncbc/qjackctl org.rncbc.qjackctl.start

/etc/pm/sleep.d/90myjackctl.sh:

#!/bin/bash
#
# this is: /etc/pm/sleep.d/90myjackctl.sh
# a system suspend/resume hook script invoked by pm-utils
#
# on suspend it switches a running jackdmp instance's backend to 'dummy'
# in order to make jackd survive the suspend/resume cycle and it restores
# previous settings on resume.
# see https://rg42.org/wiki/jack2contol for more information
#

MYJACKCTL=/usr/local/bin/myjackctl.sh
CONFIGDIR=/var/cache/pm-utils/

mkdir -p $CONFIGDIR

case $1 in
 hibernate|suspend)
  for JPID in $(pidof jackdbus);do
   JUSER=`ps -wp $JPID -o user h`
   su -l $JUSER -c "$MYJACKCTL config"> $CONFIGDIR/qjackctl_$JUSER
   test -r $CONFIGDIR/qjackctl_$JUSER && . $CONFIGDIR/qjackctl_$JUSER
   su -l $JUSER -c "$MYJACKCTL dummy $PNUMP $PNUMC" >/dev/null
  done
 ;;
 thaw|resume)
  (sleep 6
   /etc/init.d/rtirq start >/dev/null; \
   for JPID in $(pidof jackdbus);do
    POSTSCRIPT=""
    JUSER=`ps -wp $JPID -o user h`
    SNDDEV="hw:0" SNDDRV="alsa"
    test -r $CONFIGDIR/qjackctl_$JUSER && . $CONFIGDIR/qjackctl_$JUSER
    eval FOO_HOME=~$JUSER
    test -r ${FOO_HOME}/.myjackctl && . ${FOO_HOME}/.myjackctl
    su -l $JUSER -c "$MYJACKCTL $SNDDRV $SNDDEV" >/dev/null
    test -n "$POSTSCRIPT" && su -l $JUSER -c "$POSTSCRIPT"
   done
  )&
 ;;
 *)
 ;;
esac

~/.myjackctl: optional

# This is a POSIX shell fragment
#
# it is sourced from /etc/pm/sleep.d/90myjackctl.sh as $HOME/.myjackctl
# and allows to override $SNDDRV and $SNDDEV variables which are
# used to configure the resumed jackdmp server.
# by default the previously running config - saved on suspend - is used
#
# This script also provides for addidional hooks.

# do not use saved setup but rather check which cards are connected:
SNDDRV="alsa";
SNDDEV="hw:0";
POSTSCRIPT=""
grep UA25 /proc/asound/cards >/dev/null && SNDDEV="hw:1"

# change qjackctl's preset accordingly:
PRESET=""
if [ "${SNDDEV:0:4}" == "hw:1" ]; then PRESET="ua25-1024"; fi
if [ "${SNDDEV:0:4}" == "hw:0" ]; then PRESET="int-1024"; fi
test -n "${PRESET}" && \
  dbus-send --system /org/rncbc/qjackctl \
                      org.rncbc.qjackctl.preset string:$PRESET

Notes

myjackctl.sh does intentionally not use jack_control. The reason for this is that calling jack_control from a pm-suspend hook cancels the suspend and the system returns without sleeping. I have no idea why this happens, but simply using dbus-send works. (addendum: I experience similar behaviour when using which myjackctl.sh in the pm hook script instead of hardcoding its path.)

The DSESSIONBUS environment variable at the top of myjackctl.sh is required to allow the script to be run in a setuid (su) session.

What's left ToDo?

  • The port-reconnection should become optional and can be improved (see note in source)
  • multiple jack-servers of the same user could be supported - if that is possible jack-dbus at all.
  • jack-realtime parameters need to be unhardcoded.
  • the suspend/resume script should check for rtirq before it calls it; also use rtctl or similar.
1) jackd - especially at low latencies - increases power-consumption quite a bit: so the only time I'm not running jackd is when traveling to places where there's no power-outlet fi. here.
 
wiki/jack2contol.txt · Last modified: 14.03.2019 02:44 (external edit)