upstairs – Technical background


13 July 2011

For an audio-based connection between spaces, obviously sound has to be transferred from one place to another. This post describes the technical details.


Hardware setup

We picked up the vibrations with AKG C411 contact microphones. They have a decent sound quality and are hard to break. Their capacitive layout required phantom powered microphone input channels on the computer which we had available as part of an RME, respectively MOTU firewire interface.


Software overview

For sound capturing and filtering, we decided to use SuperCollider as we are most familiar with it. The basic patch looked like this:

(
q = (); // a dictionary to put things
q.inChans = [0,1]; // the input channels

s.options.numInputBusChannels  = 18;
s.options.numOutputBusChannels = 18;
s.options.blockSize            = 32;
s.options.hardwareBufferSize   = 32;
s.boot;
)

(
Spec.add(\delay, [0, 5]);
Spec.add(\lFreq, [10, 2000, \exp]);
Spec.add(\hFreq, [10, 2000, \exp]);
Spec.add(\postAmp, [0, 20]);
Spec.add(\wet, [0, 1]);
Spec.add(\rq, [0, 1]);

Ndef(\steps, {|hFreq = 2000, lFreq = 100, wet=0, postAmp = 1, delay = 0|
  var src, filter;
  src = SoundIn.ar(q.inChans);
  src = DelayC.ar(src, 5, delay); // delay for testing purposes
  filter = LPF.ar(HPF.ar(src, lFreq), hFreq);
  src = SelectX.ar(wet, [src, filter]);
  src.sum * postAmp
});

Ndef('steps').set('wet', 1.0, 'delay', 0.0, 'hFreq', 310.63624365782, 'lFreq', 50.845992766792);
)

Ndef('steps').play; // start

Ndef(\steps).clear;  // get rid of everything

After capturing and processing the signal, it is routed to Darkice using jack. The patching inside jack is shown in the figure above. Darkice is a software that streams audio signals from a jack port to an Icecast2 server. The combination of Icecast2/Darkice proved to be sufficient to achieve about one second latency around the globe without major hassle.


“Almost low latency” configuration of an Icecast2 server

(In this example, we’re talking about an Icecast2 server running on Debian 6.0 Linux.)

We had to do only two major edits to the default icecast.xml file in order to squeeze the latency to a minimum:

<burst-on-connect>0</burst-on-connect>
<burst-size>0</burst-size>

Yay, that was easy!

Darkice client configuration running on OSX

The configuration file for the darkice client looked like this:

[general]
duration=0     # keep it streaming
reconnect=yes  # if you loose connecting, try again
bufferSecs=1   # size of internal slip buffer, in seconds
reconnect=yes  # reconnect to the server(s) if disconnected

[input]
device=jack
sampleRate=44100  # sample rate in Hz. try 11025, 22050 or 44100
bitsPerSample=16  # bits per sample. try 16
channel=2         # number of channels. 2==stereo [ for now, we're using two channels

[icecast2-0]
format=mp3
bitrateMode=vbr # variable bitrate
bitrate=128
quality=1.0 # best quality
server=server.example.org  # enter you server hostname here
mountPoint=node1          # or node2 for the other side.
port=8000                  #  icecast port (default = 8000)
password=$password         # the icecast password you configured (icecast.xml)
name=node1
description=A sample upstairs node.

We decided to use the mp3 codec, mostly out of compatibility reasons. An audio buffer of about a second is sufficient for this application and almost every player supports mp3. It seems that the major part of our latency is caused by the Darkice minimum buffer size of one second. An attempt to patch the source to accept 0 seconds buffer size resulted in not that much of a difference. A noticeable decrease of latency would require more effort, which we decided to postpone for now; if it turns out that we need less latency, we’ll investigate further into that direction.

To eventually play back the resulting audio stream, we used mplayer:

$ mplayer -nocache http://example.org:8000/node1

Two nodes running mplayer, darkice, SuperCollider, and jack are connected by a server running icecast and apache.