Skip to content

Instantly share code, notes, and snippets.

@samdoshi
Last active Jan 30, 2022
Embed
What would you like to do?
Running SuperCollider/Jack2 inside Docker with ALSA pass-through

Running SuperCollider/Jack2 inside Docker with ALSA pass-through

This is a sister article to my original gist, but this time instead of using NetJack2, we're going to directly use ALSA and /dev/snd by passing them into the container. We should see native performance but at the downside of having to dedicate a soundcard for exclusive use to the container and the loss of high performance OS X/Windows compatibility.

Assumptions

  • The host OS is Linux
  • You've got a free ALSA soundcard with no daemon attached to it (i.e. no PulseAudio or Jack), however you can have a daemon running on other soundcards.
  • You've got Docker setup correctly and are able to run docker run hello-world

For reference I'm running Arch Linux.

Sharing ALSA inside a container

Because we're running a container rather than a virtual machine, the kernel inside the container is the host kernel, in particular for us /dev/snd is the same both inside and out if we give access via (--device /dev/snd).

This will cause an issue with group IDs though, as frequently the host OS and the container OS will have difference values for the audio group in /etc/group. Assuming we don't want the container processes to run as root we will need to give our user membership of the host audio group specified as a gid.

We can do this with the following line added to our docker run:

--group-add $(getent group audio | cut -d: -f3)

See more on Jess Frazelle's GitHub repo.

Realtime considerations

There are a variety of flags for configuring realtime behaviour with Docker. I can't claim to understand them, but if you have a realtime kernel installed then add the following to your docker run line:

--cpu-rt-runtime=950000

See the "configure the realtime scheduler" section on the Docker website for more details.

D-Bus

The default compile of Jack2 on most distros needs D-Bus to be running (even if it's not the jackdbus version), this is normally started by your desktop environment, but here we are manually starting a session with dbus-run-session. The alternative to compile Jack2 manually without the requirement for D-Bus.

Summary of Makefile targets

  • build: build the Docker image norns/supercollider.
  • run: run the image, leaves you at an sclang terminal, Ctrl-D to exit sclang and the container.
  • run-bash: runs the image, but doesn't start sclang, instead leaves you at a bash prompt.
  • shell: gives you a bash prompt on an already running instance (e.g. either from make run or make run-bash).

Getting started

  1. Clone this Gist, and cd to it.

  2. Copy jackdrc_sample to jackdrc and change hw:X (e.g. hw:2) to the index of the soundcard you wish to use (use aplay -l to list soundcards and their indices).

  3. Make sure that the soundcard you wish to use in the container has no sound daemon attached to it, for example if you're using Jack then stop it by running:

    jack_control stop
    
  4. In a terminal run:

    make build
    make run
    

    This should leave you at an sclang prompt. (Type Ctrl-D if you need to exit sclang, that will also exit the Docker container.)

  5. In sclang we need to boot scsynth by running:

    s.boot;
    

    Watch the output messages, it can be somewhat tricky figuring out if it hasn't succeeded.

    (You may need to press enter to get the sc3> prompt back.)

  6. Now let's generate some audio from SuperCollider, in the sclang terminal:

    { SinOsc.ar(440)!2 }.play;
    

    then to stop (all) sound:

    s.freeAll;
    

    Fingers crossed it worked and there were no audio glitches.

Testing audio performance

Try some of the sc140 compositions (be-warned some of them are deliberately glitchy). After pasting one into the sclang command line you'll need to type s.freeAll; to stop audio.

No 11 works well:

play{VarSaw.ar((Hasher.ar(Latch.ar(SinOsc.ar((1..4)!2),Impulse.ar([5/2,5])))*300+300).round(60),0,LFNoise2.ar(2,1/3,1/2))/5}//#supercollider
FROM debian:9
ENV LANG=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
ENV SC_VERSION=3.9.3
RUN apt-get update -q && \
apt-get install -qy apt-utils && \
apt-get dist-upgrade && \
groupadd we -g 1000 && \
useradd we -g 1000 -u 1000 -m -G audio
RUN apt-get install -qy \
alsa-utils \
build-essential \
bzip2 \
ca-certificates \
cmake \
git \
jackd2 \
libasound2-dev \
libavahi-client-dev \
libcwiid-dev \
libfftw3-dev \
libicu-dev \
libjack-jackd2-dev \
libreadline6-dev \
libsndfile1-dev \
libudev-dev \
libxt-dev \
pkg-config \
unzip \
wget
RUN mkdir -p /tmp/sc && \
cd /tmp/sc && \
wget -q https://github.com/supercollider/supercollider/releases/download/Version-$SC_VERSION/SuperCollider-$SC_VERSION-Source-linux.tar.bz2 -O sc.tar.bz2 && \
tar xvf sc.tar.bz2
RUN cd /tmp/sc/SuperCollider-Source && \
mkdir -p build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE="Release" \
-DBUILD_TESTING=OFF \
-DSUPERNOVA=OFF \
-DNATIVE=OFF \
-DSC_WII=OFF \
-DSC_QT=OFF \
-DSC_ED=OFF \
-DSC_EL=OFF \
-DSC_VIM=OFF \
.. && \
make -j && \
make install
USER we
COPY ["jackdrc", "/home/we/.jackdrc"]
CMD dbus-run-session sclang
/usr/bin/jackd -R -d alsa -d hw:X -p2048 -n3
build: Dockerfile jackdrc
docker build -t norns/supercollider .
run: build
docker run --rm -it \
--device /dev/snd \
--group-add $$(getent group audio | cut -d: -f3) \
--cap-add=sys_nice \
--ulimit rtprio=95 --ulimit memlock=-1 --shm-size=256m \
--name norns \
norns/supercollider
run-bash: build
docker run --rm -it \
--device /dev/snd \
--group-add $$(getent group audio | cut -d: -f3) \
--cap-add=sys_nice \
--ulimit rtprio=95 --ulimit memlock=-1 --shm-size=256m \
--name norns \
norns/supercollider \
bash
shell:
docker exec -it norns bash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment