Audio PC


Digital Room Correction

Ronald Verlaan

Contents

Chapter 1  Introduction

This document describes my audio pc. However it not just an audio pc, it serves other purposes as well. The purpose of this document is to document the audio related part of the configuration information as well as providing a real world example of how to implement DIY room correction to help others.


Figure 1.1: Stereo set

Linux has been choosen as the operating system for the audio pc. The author has choosen to use the Debian stable linux distribution. The main audio related functionalities of the audio pc are:

Chapter 2  My stereo setup

This chapter shortly describes the hardware I am using in my stereo setup.

2.1  Digital source

2.1.1  Audio PC

It is outside the scope of this section to describe the audio pc in detail, therefore only the HTPC casing will be shortly described. The Antec Fusion seamlessly blends Quiet Computing, versatility and impeccable style to media PCs. Utilizing an innovative three chamber thermal design and Quiet Computing features like vibration-dampening silicone grommets, it is so quiet you'll be able to make out the softest passages in your favorite movies or music.


Figure 2.1: Antec Fusion HTPC

2.1.2  Squeezebox 3

The Squeezebox 3 is a stylish streaming based digital audio source. I use it to play my cd's (ripped to harddisk in losless flac format). There are two categories of music where I do allow the mp3 format which is Thai music (for my wife) and Italo Disco music. Currently I do not use any other format.


Figure 2.2: Squeezebox 3

The Squeezebox is connected to the audio pc using the spdif coaxial output (which sounds slightly better then the optical output).

2.1.3  Monarchy Audio DIP Classic

This DIP Classic is the best performing DIP for playing conventional CD's, with decoding capability from 16 to 24 bits, and outputs a precision 44.1KHz clock for all conventional DAC's. It elevates the Squeezebox as a digital transport to a higher level, which is clearly audible.


Figure 2.3: Monarchy Audio DIP Classic

The audio pc's spdif output is connected to the DIP. The DIP's AES/EBU output is in turn connected to the Apogee Mini DAC.

2.1.4  Apogee Mini DAC

This professional quality,192kHz D/A converter, is the ultimate portable and compact solution for studio playback, reference monitoring, USB connectivity to your DAW, and premium home audio systems. It sounds a lot better then the analog output of the Squeezebox (which I disabled anyway).


Figure 2.4: Apogee Mini DAC

The DAC is connected to the amplifier using its balanced outputs.

2.2  Integrated stereo amplifier

By combining analog audio circuitry with digital control the SDAI 2175 integrated amplifier offers very high performance and amazing value for money. The open natural sound reproduction and massive power output for impressive grip and drive simply makes this the best integrated amplifier in it's class!


Figure 2.5: Lyngdorf SDAi2175

2.3  Speakers

The Avalon Monitor speakers are the monitor version (same drivers) of the more well known Avalon Avatar.


Figure 2.6: Avalon Monitor

2.4  Cabling

2.4.1  Apogee Wide Eye digital interconnect

Apogee's premium Wyde Eye cable offers superior connectivity and uncompromised quality for Word Clock, AES, SPDIF and Analog formats. This cable is used to connected the Squeezebox to the Audio PC and also to connect the Audio PC to the Monarchy Audio DIP.


Figure 2.7: Apogee Wide Eye

2.4.2  LAT International IC-300 Signature XLR

This cable excels in a number of areas; most prevalent are the outstanding dynamics, focus, inner detail, and harmonic accuracy and most important, it is enjoyably musical! This cable is used to connect the Apogee Mini DAC to the Lyngdorf SDAi2175 amplifier.


Figure 2.8: LAT International IC-300 Signature

2.4.3  Nordost Blue Heaven biwire speakercable

Blue Heaven Revision II loudspeaker cable consists of 72 separate conductors of 99.99999This cable is used to connect the Avalon Monitors to the Lyngdorf SDAi2175 amplifier.


Figure 2.9: Nordost Blue Heaven

Chapter 3  Measuring

In order to be able to calculate room correction filters we first need to create the impulse response of the room on the listening position. This has to be done per channel. The impulse response can be calculated from the measured log sweep.


Figure 3.1: Microphone positioning

3.1  Measuring equipment

In order to get good room correction results it is essential to use a good measuring microphone. I choose the Linearx M31 which has very linear frequency response and comes with a calibration file.


Figure 3.2: Linearx M31 measurement microphone


Figure 3.3: M31 frequency response

The M31 does not rely on phantom power, but instead needs a dedicated 9V DC power supply for which a 9V battery can be used.


Figure 3.4: M31 wiring

A simple adapter can be build that contains a 9V battery and a unbalanced jack connector that goes into the microphone pre-amp.


Figure 3.5: M31 adapter

A relatively cheap Behringer UB-802 can perfectly be used as a microphone amplifier.


Figure 3.6: Behringer UB-802 mic pre-amp

3.2  Recording

Use the measure script that comes with the DRC distribution to record a logarithmic sine sweep and calculate the impulse response of the room.

measure 16 44100 1 22050 60 2 plughw plughw impulse-left.pcm
measure 16 44100 1 22050 60 2 plughw plughw impulse-right.pcm

It is important to prevent furniture from resonating and the speaker from distorting. On the other hand we want a signal to noise ratio that is workable. In my case the best settings turned out to be the Lyngdorf amplifier at volume level 75 (+6 dB in the menu) and the M-Audio DAC level at maximum. To compensate for the resulting somewhat low sound level I extended the log sweep from the suggested 45 to 60 seconds.

Make sure the maximum amplitude of the measurement channel and the reference channels is not higher then 0.50 (to prevent soft clipping of the soundcard) but also not lower then 0.1.

By adapting the measure script a little bit we are able to save the measured sine sweep pcm sweep. The screenshot below shows the measured left (upper graph) and right (lower graph) channels. As expected the low frequeny boost is highest in the left channel as a result of the left speaker being placed (more or less) in a corner.


Figure 3.7: Sine sweep recording

The measurement script outputs the impulse response as a 32 bits float mono pcm file.

3.3  ETC graphs

ETC = energy time curve. It's a fancy name for what is more properly called the "envelope" of the signal, or its "instantaneous amplitude". It's usually called ETC when it's plotted on a log magnitude scale. It's the absolute value of the analytical signal, which is derived from the signal itself using the Hilbert transform of the signal as the imaginary part of the analytical signal.

It is good practice to create the ETC graphs directly after measuring in order to check the quality of these measurements. SNR should be 90 dB or better. The ETC graphs can be generated using octave.

octave> ir = loadpcm("/home/ronald/work/impulse-response.pcm");
octave> etc = abs(hilbert(ir));
octave> plot((1:length(etc))/44100,20 * log10(etc))

Be aware that octave must be able to find the ir octave script that comes with he DRC distribution. Also make sure the DISPLAY variable is set correctly as the plot command will issue gnuplot in order to display the graph.


Figure 3.8: ETC graph left channel


Figure 3.9: ETC graph right channel

Figure 3.10 shows what a bad ETC graph looks like, when the recorder signal has been (accidentally) resampled (from 44.1kHz to 48kHz) by the soundcard or by windows kmixer.


Figure 3.10: Bad ETC

Before creating the ETC graphs the pcm file has to be edited in order to reduce the number of samples before and after the main spike. If we don't do this gnuplot will choke and no graph will be produced. Use any sound editor (that supports 32 bit float pcm) and leave about 100.000 samples (= about 2.5 seconds of silence before and after the main spike.

3.4  Marking the speaker position

Of course after measuring the impulse response of both speakers the speakers should not be moved anymore. In order to ensure correct speaker placement I used a 1 mm drill to drill a very small hole that marks the position of the speakers inner spike. The amount of toe-in has been determined carefully and is easy to remember thanks to the structure in the laminate floor.


Figure 3.11: Toe-in of my speakers

Chapter 4  DRC

DRC is a program used to generate correction filters for acoustic compensation of HiFi and audio systems in general, including listening room compensation. DRC generates just the FIR correction filters, which can be used with a real time or offline convolver to provide real time or offline correction. The DRC website can be found at:

http://drc-fir.sourceforge.net/

The current drc release is 2.6.2.

4.1  Brutefir specific DRC settings

Because we use brutefir as the convolution engine, it is advisable to use 1 for the PLNormFactor and S for PLNormType in the drc configuration files.

4.2  DRC configuration file

The DRC configuration file currently in use is the new 44.1kHz soft correction template, with the following modifications:

BCBaseDir = /usr/local/roomcorrection/
BCInFile = m31d-left.pcm
PLNormFactor = 1.0
PLNormType = S
PSPointsFile = tact-35f.txt
MCPointsFile = m31.txt
MCOutFile = filter-left.pcm

The right channel configuration file is basically the same, except for one thing. Instead of:

BCImpulseCenterMode = A
BCImpulseCenter = 0

These settings are used:

BCImpulseCenterMode = M
BCImpulseCenter = 132734 

The value of BCImpulseCenter can be derived from running drc for the left channel, it will tell you the impulsecenter which is filled in, in the right channel drc configuration in order to have correct time alignment:

hammie:/usr/local/roomcorrection# drc ronald-left.drc

DRC 2.6.2: Digital Room Correction
Copyright (C) 2002-2005 Denis Sbragion

Compiled with single precision arithmetic.
Using the GNU Scientific Library FFT routines.

This program may be freely redistributed under the terms of
the GNU GPL and is provided to you as is, without any warranty
of any kind. Please read the file "COPYING" for details.

Input configuration file: ronald-left.drc
Parsing configuration file...
Parsing completed.
Adding command line options...
Configuration parameters check.
Seeking impulse center on: m31d-left.pcm
Impulse center found at sample 132734.

4.3  Target curve

The target curve I have choosen to use is the tact35f.txt that comes from a Tact RCS room correction box. From all the target curves I've tried (including the ones that come with the DRC distribution) I like this one the most.

0.00 -80.00
1.00 -80.00
4.90 -36.30
12.20 -17.50
14.40 -14.14
16.70 -11.02
18.90 -8.20
20.70 -6.41
23.00 -4.30
26.10 -1.95
28.70 -0.16
30.70 0.82
32.40 1.44
36.50 1.75
41.40 1.62
48.40 1.56
57.50 1.38
66.70 1.19
79.90 0.82
97.30 0.33
118.30 0.08
145.10 0.02
175.20 -0.04
209.80 -0.10
243.50 -0.10
307.90 -0.14
694.50 -0.31
1524.70 -0.55
2791.60 -0.70
3461.40 -0.86
4298.60 -0.97
5189.10 -1.21
6166.40 -1.58
7385.60 -2.14
8845.90 -2.94
10107.70 -3.55
11824.60 -4.60
13942.00 -5.59
16310.10 -6.69
20000.00 -8.05
22050 -10.0

Be aware that each target curve (for DRC) should start with a 0 Hz entry and should end with a 0.5*fs (half the sample frequency) entry, which is in this case 22050 Hz.


Figure 4.1: Target Curves

The blue colored curve in figure 4.1 represents the tact-30f target curve.

A script to convert Tact target curves to drc target curves:

#!/bin/bash
for i in `ls tact-curves/*.COR`
do
echo "0.00 -80.00" > $i.txt
cat $i | paste - - | sed 's/\t/ /g' | grep -v "0.00 0.00" >> $i.txt
echo "22050 -10.0" >> $i.txt
done

4.4  Microphone calibration file

In the DRC configuration a microphone compensation file (=calibration file) can be specified. The same rule applies here for begin and start entry as with the target curve. This file basically represents the frequency response of the microphone.

4.5  Creating the octave graphs

Create a working directory and copy the octave scripts that come with the drc distribution in there. Execute octave in this directory so it will be able to find the needed octave scripts during the creation of the octave graphs. In order to create the octave graphs execute the following commands in octave:

ru = loadpcm("/pathtopcm/RUncorrected.pcm");
rc = loadpcm("/pathtopcm/RCorrected.pcm");
createdrcplots(ru,-1,"R Uncorrected",rc,-1,"R Corrected","/pathtographs/","R");

Where RUncorrected.pcm is the impulse response pcm file created by the measurement script and RCorrected.pcm is the test convolution file called rtc.pcm created by drc. Again it is important to edit the RUncorrected.pcm file first in order to reduce the number of samples before and after the main spike (see section Creating the ETC graphs in the Measurement chapter. Of course this has to be done only once, so if you already took care of this in order to create the ETC graphs, you will be fine!

When octave is done doing its calculations and generating graphs change directory to the /pathtographs/ and execute:

hevea drc-graphs.tex
imagen -res 150 -png drc-graphs

The -res 150 is needed in order to avoid tiny low resolution graphs and get nice graphs instead. Before executing hevea, you might want to edit the drc-graphs.tex file first and change these lines:

% Replace with your title and name
\title{Room Correction Graphs Left Channel}
\author{Audioloog}

Finally copy the resulting html file plus all the png files to your webserver documents directory in order to publish them on your website. The whole process has to be repeated if you want to have graphs for another channel as well.


Figure 4.2: Octave generated Impulse Response graph


Figure 4.3: Octave generated Magnitude response graph

4.6  Generating filters in batch mode

This simple script will generate a filter for each present drc file and target curve in the working directory:

#!/bin/bash
BASEDIR=/usr/local/roomcorrection
cd $BASEDIR
for i in `ls *.txt | grep -v m31.txt`
do
for j in `ls *.drc`
do
correction=`echo $j | cut -d. -f1`
targetcurve=`echo $i | cut -d. -f1`
mkfir $correction $targetcurve
done
done

It calls this script (called mkfir):

#!/bin/bash
BASEDIR=/usr/local/roomcorrection
FILTERDIR=filters
IR=m31e
IMPULSECENTER=1984935
MICCALFILE=m31.txt
#
drc --BCBaseDir="$BASEDIR/" --BCInFile="$IR-left.pcm" --PLNormFactor="1.0"
--PLNormType="S" --PSFilterType="L" --PSPointsFile="$2.txt" --MCFilterType="M"
--MCPointsFile="$MICCALFILE" --MCOutFile="$FILTERDIR/$1-$2-left.pcm"
--MCOutFileType="F" $BASEDIR/$1.drc
#
drc --BCBaseDir="$BASEDIR/" --BCInFile="$IR-right.pcm" --PLNormFactor="1.0"
--PLNormType="S" --PSFilterType="L" --PSPointsFile="$2.txt" --MCFilterType="M"
--MCPointsFile="$MICCALFILE" --MCOutFile="$FILTERDIR/$1-$2-right.pcm"
--MCOutFileType="F" --BCImpulseCenterMode="M" --BCImpulseCenter=$IMPULSECENTER
$BASEDIR/$1.drc

Chapter 5  Brutefir

BruteFIR is a software convolution engine, a program for applying long FIR filters to multi-channel digital audio, either offline or in realtime. Its basic operation is specified through a configuration file, and filters, attenuation and delay can be changed in runtime through a simple command line interface. The Brutefir website can be found at:

http://www.ludd.luth.se/~torger/brutefir.html

There are basically two choices of where to run brutefir in the audio chain:

5.1  Correcting the sound using custom-convert

This comes down to correcting the sound before it goes to the Squeezebox. The disadvantage is that brutefir will be restarted at each track, thus causing short drop-outs between tracks.

5.1.1  brutefir homedir

The homedir of the slimserver user is /usr/share/slimserver. When brutefir starts it tries to write to two hidden files in the homedir of the owner of the process. Since brutefir is started as the user slimserver it needs to be able to write to /usr/share/slimserver. I had to change the owner of this directory manually from root to slimserver in order to get brutefir to work. If unsure check the brutefir logfile to see if there are problems or not.

5.1.2  brutefir configuration

The brutefir configuration file resides in /etc/slimserver/brutefir.cfg. Please note that in this example my DRC generated FIR filters are called left.pcm and right.pcm, are 64k samples long and that they do reside in /etc/slimserver. Brutefir output is set to 24 bits (S24_LE) output for processing by the flac encoder.

Brutefir (in this configuration) uses about 9% cpu on my 32 bits 1,8 GHz AMD machine. There is of course also the (small) overhead of the extra flac encoder process.

Check /etc/slimserver/brutefir.log to make sure no clipping occurs. Be aware that this logfile is overwritten each time a new song is played.

## DEFAULT GENERAL SETTINGS ##
float_bits: 64;             # internal floating point precision
sampling_rate: 44100;       # sampling rate in Hz of audio interfaces
filter_length: 1024,64;       # length of filters
overflow_warnings: true;    # echo warnings to stderr if overflow occurs
show_progress: false;        # echo filtering progress to stderr
max_dither_table_size: 0;   # maximum size in bytes of precalculated dither
allow_poll_mode: false;     # allow use of input poll mode
modules_path: ".";          # extra path where to find BruteFIR modules
monitor_rate: false;        # monitor sample rate
powersave: true;           # pause filtering when input is zero
lock_memory: true;          # try to lock memory if realtime prio is set
convolver_config: "/etc/slimserver/.brutefir_convolver"; # location of convolver
 config file

logic: "cli" { port: 3000; };

coeff "nocorrection_l" {
        filename: "dirac pulse";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "nocorrection_r" {
        filename: "dirac pulse";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

coeff "drc_l" {
        filename: "/etc/slimserver/left.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "drc_r" {
        filename: "/etc/slimserver/right.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

## INPUT DEFAULTS ##

input  "l_in","r_in" {
        device: "file" {path: "/dev/stdin";};  # module and parameters to get au
dio
        sample: "S16_LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
};

## OUTPUT DEFAULTS ##

output "l_out","r_out" {
        device: "file" {path: "/dev/stdout";};  # module and parameters to put a
udio
        sample: "S24_LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
        dither: true;      # apply dither
};

## FILTER DEFAULTS ##

filter "l_filter" {
        from_inputs: "l_in"/8.0;
        to_outputs: "l_out"/0.0;
        process: 0;        # process index to run in (-1 means auto)
        coeff: "drc_l";
        delay: 0;           # predelay, in blocks
        crossfade: false;   # crossfade when coefficient is changed
};
filter "r_filter" {
        from_inputs: "r_in"/8.0;
        to_outputs: "r_out"/0.0;
        process: 0;        # process index to run in (-1 means auto)
        coeff: "drc_r";
        delay: 0;           # predelay, in blocks
        crossfade: false;   # crossfade when coefficient is changed
};

5.2  Correcting the sound using a soundcard

This is the brutefir implementation I am currently using. It comes down to correcting the sound after it leaves the Squeezebox, which means it will be routed through the pc again using a RME Digi 96/8 soundcard. This has several advantages:

5.2.1  brutefir homedir

In this case the brutefir homedir is /etc/brutefir, this directory contains a subdir containing the filters. Symbolic links are used for a flexible assignment of filters:

lrwxrwxrwx 1 root root    30 2008-01-19 14:30 1-left.pcm -> filters/soft-44khz-bk-left.pcm
lrwxrwxrwx 1 root root    31 2008-01-19 14:30 1-right.pcm -> filters/soft-44khz-bk-right.pcm
lrwxrwxrwx 1 root root    32 2008-01-19 14:30 2-left.pcm -> filters/soft-44khz-bk-2-left.pcm
lrwxrwxrwx 1 root root    33 2008-01-19 14:30 2-right.pcm -> filters/soft-44khz-bk-2-right.pcm
lrwxrwxrwx 1 root root    32 2008-01-19 14:31 3-left.pcm -> filters/soft-44khz-bk-3-left.pcm
lrwxrwxrwx 1 root root    33 2008-01-19 14:31 3-right.pcm -> filters/soft-44khz-bk-3-right.pcm
lrwxrwxrwx 1 root root    39 2008-01-19 14:31 4-left.pcm -> filters/soft-44khz-bk-3-spline-left.pcm
lrwxrwxrwx 1 root root    40 2008-01-19 14:31 4-right.pcm -> filters/soft-44khz-bk-3-spline-right.pcm
lrwxrwxrwx 1 root root    36 2008-01-19 14:32 5-left.pcm -> filters/soft-44khz-tact-35f-left.pcm
lrwxrwxrwx 1 root root    37 2008-01-19 14:32 5-right.pcm -> filters/soft-44khz-tact-35f-right.pcm
lrwxrwxrwx 1 root root    38 2008-01-19 15:24 6-left.pcm -> filters/strong-44khz-tact-35f-left.pcm
lrwxrwxrwx 1 root root    39 2008-01-19 15:24 6-right.pcm -> filters/strong-44khz-tact-35f-right.pcm

5.2.2  brutefir configuration

The brutefir configuration file /etc/brutefir/brutefir.cfg:

## DEFAULT GENERAL SETTINGS ##
float_bits: 64;             # internal floating point precision
sampling_rate: 44100;       # sampling rate in Hz of audio interfaces
filter_length: 1024,64;       # length of filters
overflow_warnings: true;    # echo warnings to stderr if overflow occurs
show_progress: false;        # echo filtering progress to stderr
max_dither_table_size: 0;   # maximum size in bytes of precalculated dither
allow_poll_mode: true;     # allow use of input poll mode
modules_path: ".";          # extra path where to find BruteFIR modules
monitor_rate: false;        # monitor sample rate
powersave: true;           # pause filtering when input is zero
lock_memory: true;          # try to lock memory if realtime prio is set
convolver_config: "/etc/slimserver/.brutefir_convolver"; # location of convolve
r config file

logic: "cli" { port: 3000; };

coeff "nocorrection_l" {
        filename: "dirac pulse";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "nocorrection_r" {
        filename: "dirac pulse";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

coeff "1_l" {
        filename: "/etc/brutefir/1-left.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "1_r" {
        filename: "/etc/brutefir/1-right.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

coeff "2_l" {
        filename: "/etc/brutefir/2-left.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "2_r" {
        filename: "/etc/brutefir/2-right.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

coeff "3_l" {
        filename: "/etc/brutefir/3-left.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "3_r" {
        filename: "/etc/brutefir/3-right.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

coeff "4_l" {
        filename: "/etc/brutefir/4-left.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "4_r" {
        filename: "/etc/brutefir/4-right.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

coeff "5_l" {
        filename: "/etc/brutefir/5-left.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "5_r" {
        filename: "/etc/brutefir/5-right.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

coeff "6_l" {
        filename: "/etc/brutefir/6-left.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "6_r" {
        filename: "/etc/brutefir/6-right.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};

## INPUT DEFAULTS ##

input  "l_in","r_in" {
        device: "alsa" {param: "pcm.rme96";};  # module and parameters to get au
dio
        sample: "S16_LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
};

## OUTPUT DEFAULTS ##

output "l_out","r_out" {
        device: "alsa" {param: "pcm.rme96";};  # module and parameters to put au
dio
        sample: "S24_4LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
        dither: true;      # apply dither
};

## FILTER DEFAULTS ##

filter "l_filter" {
        from_inputs: "l_in"/8.0;
        to_outputs: "l_out"/0.0;
        process: 0;        # process index to run in (-1 means auto)
        coeff: "5_l";
        delay: 0;           # predelay, in blocks
        crossfade: false;   # crossfade when coefficient is changed
};
filter "r_filter" {
        from_inputs: "r_in"/8.0;
        to_outputs: "r_out"/0.0;
        process: 0;        # process index to run in (-1 means auto)
        coeff: "5_r";
        delay: 0;           # predelay, in blocks
        crossfade: false;   # crossfade when coefficient is changed
};

5.3  Digital cross-over

It is also possible to implement digital cross-over functionality using brutefir. This enables one to use a subwoofer that is connected to the analogue output(s) of the soundcard of the HTPC. I am currently not using this functionality because I have now way of controlling the volume of the subwoofer synchronously with the volume of the front speakers in this setup example.

5.3.1  Generating x-over filters

This script will generate the x-over filters:

#!/usr/bin/octave
# Robert Scheer 10/6/2004
# Chris Birkinshaw 06/05/05
# GTS October 2005

# Beginning of user parameters

n=11; # exponent for filter size
fxo_low=45; # ir_low crossover frequency in Hz
fs=44100; # sample rate in Hz

# End of user parameters


k=2^n; # order of filter
fn_low=2*fxo_low/fs; # normalized ir_lowsonic cutoff frequency

i=linspace(1,k,k); # k_tap filter array

ir_hi=fir1(k,fn_low,'high','scale'); # high_pass impulse response
ir_low=fir1(k,fn_low,'low','scale'); # low_pass ir

ir_hitxt=ir_hi(i); # my klugey way of taking k elements
ir_lowtxt=ir_low(i);

save -ascii ./high.txt ir_hitxt
save -ascii ./low.txt ir_lowtxt

5.3.2  Brutefir configuration

## DEFAULT GENERAL SETTINGS ##
float_bits: 64;             # internal floating point precision
sampling_rate: 44100;       # sampling rate in Hz of audio interfaces
filter_length: 32768,2;       # length of filters
overflow_warnings: true;    # echo warnings to stderr if overflow occurs
show_progress: false;        # echo filtering progress to stderr
max_dither_table_size: 0;   # maximum size in bytes of precalculated dither
allow_poll_mode: true;     # allow use of input poll mode
modules_path: ".";          # extra path where to find BruteFIR modules
monitor_rate: false;        # monitor sample rate
powersave: true;           # pause filtering when input is zero
lock_memory: true;          # try to lock memory if realtime prio is set
convolver_config: "/etc/brutefir/.brutefir_convolver"; # location of convolver config file

logic: "cli" { port: 3000; };

#########################
# xover coeffs
coeff "xover_low" {
        filename: "/etc/brutefir/low.txt";
 format: "text";
 attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;
};
coeff "xover_high" {
        filename: "/etc/brutefir/high.txt";
 format: "text";
 attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;
};

#########################
# drc coeffs
coeff "drc_l" {
        filename: "/etc/brutefir/1-left.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "drc_r" {
        filename: "/etc/brutefir/1-right.pcm";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};
coeff "drc_s" {
        filename: "dirac pulse";
        format: "FLOAT_LE";     # file format
        attenuation: 0.0;   # attenuation in dB
        blocks: -1;         # how long in blocks
        skip: 0;            # how many bytes to skip
        shared_mem: false;  # allocate in shared memory
};


#########################
## INPUT DEFAULTS ##
input  "l_in","r_in" {
        device: "alsa" {param: "pcm.rme96";};  # module and parameters to get audio
        sample: "S16_LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
};

#########################
## OUTPUT DEFAULTS ##
output "l_out","r_out" {
        device: "alsa" {param: "pcm.rme96";};  # module and parameters to put audio
        sample: "S24_4LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
        dither: true;      # apply dither
};
output "sub_l_out","sub_r_out" {
        device: "alsa" {param: "pcm.analogout";};  # module and parameters to put audio
        sample: "S16_LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
 dither: true;
};

###################################
# xover filtering
filter "high_left" {
        from_inputs: "l_in";
        to_filters: "l_filter";
        coeff: "xover_high";
};
filter "high_right" {
        from_inputs: "r_in";
        to_filters: "r_filter";
        coeff: "xover_high";
};
filter "low_left" {
       from_inputs: "l_in";
       to_filters: "s_l_filter";
       coeff: "xover_low";
};
filter "low_right" {
       from_inputs: "r_in";
       to_filters: "s_r_filter";
       coeff: "xover_low";
};

##################################
# drc filtering
filter "l_filter" {
        from_filters: "high_left"/8.0;
 to_outputs: "l_out";
        coeff: "drc_l";
        delay: 0;           # predelay, in blocks
        crossfade: false;   # crossfade when coefficient is changed
};
filter "r_filter" {
        from_filters: "high_right"/8.0;
 to_outputs: "r_out";
        coeff: "drc_r";
        delay: 0;           # predelay, in blocks
        crossfade: false;   # crossfade when coefficient is changed
};
filter "s_l_filter" {
        from_filters: "low_left"/8.0;
        to_outputs: "sub_l_out";
        coeff: "drc_s";
        delay: 0;           # predelay, in blocks
        crossfade: false;   # crossfade when coefficient is changed
};
filter "s_r_filter" {
        from_filters: "low_right"/8.0;
        to_outputs: "sub_l_out";
        coeff: "drc_s";
        delay: 0;           # predelay, in blocks
        crossfade: false;   # crossfade when coefficient is changed
};

5.4  Switching between filters

Switching between filters can be done in the following way:

This is an example of such a script:

#!/bin/bash
echo "cfc 0 0; cfc 1 1; quit" | nc localhost 3000

In this case switching to filter coefficients 0 and 1 would for instance mean that correction is turned off as the dirac pulse statement is used to define a predefined filter that does do nothing.

5.5  daemontools

There is one little problem running brutefir all the time. When not playing any music brutefir will die because of buffer underflow, which is normal behaviour. In order to solve this daemontools can be installed. The website of daemontools can be found at:

http://cr.yp.to/daemontools.html

The run file for brutefir:

#!/bin/sh
sleep 2
#exec brutefir /etc/brutefir/brutefir.cfg 2>> /etc/brutefir/brutefir.log
exec brutefir /etc/brutefir/brutefir.cfg

Chapter 6  Squeezecenter


Figure 6.1: Squeezebox 3

6.1  Brutefir/Squeezecenter integration

The file /etc/slimserver/custom-convert.conf can be used to transcode between different audio formats, however it can also be used to insert our convolution engine brutefir.

flc flc * *
        [flac] -dcs --force-raw-format --endian=little --sign=signed --skip=$START$ --until=$END$ -
- $FILE$ | /usr/bin/brutefir /etc/slimserver/brutefir.cfg 2> /etc/slimserver/brutefir.log | [flac] 
-cs -0 --force-raw-format --endian=little --sign=signed --channels=2 --bps=24 --sample-rate=44100 -

mp3 flc * *
        [lame] --mp3input --resample 44100 --decode -t --silent $FILE$ - - | /usr/bin/brutefir /etc
/slimserver/brutefir.cfg 2> /etc/slimserver/brutefir.log | [flac] -cs -0 --force-raw-format --endia
n=little --sign=signed --channels=2 --bps=24 --sample-rate=44100 -

wma flc * *
[mplayer] -really-quiet -vc null -vo null -cache 64 -af volume=0,resample=44100:0:1,channels=2 -ao 
pcm:nowaveheader:file=/dev/fd/4 $FILE$ 4>&1 1>/dev/null | [flac] -cs -0 --force-raw-format --endian
=little --sign=signed --channels=2 --bps=16 --sample-rate=44100 -

This would convert a 16 bits flac file to 16 bits pcm (wav), then pipe it to brutefir for room correction, brutefir uses 64 bits accuracy and finally outputs 24 bits pcm which is being piped to the flac encoder again in order to generate 24 bits flac file for processing by the Squeezebox hardware.

It does the same for mp3 files and for listening to internet radio stations which are broadcasting in wma format.

Repeat this for every format you would like to have room correction enabled for and then restart slimserver.

Don't forget to edit "file types" in the slimserver server settings! In this example you should enable flac to flac, mp3 to flac and wma to flac.

6.2  CLI scripts

A usefull script to rescan the squeezecenter's database looking for new and modified music:

# perl script to initiate a rescan via the cli
# uses code from Max Spicer's shutdown.pl

use strict;
use IO::Socket;
use POSIX qw(strftime);

# Print debug output if true.  Higher values increase verbosity.
my $debug = 0;


# Change server details below if necessary
my $socket = IO::Socket::INET->new (PeerAddr => '127.0.0.1',
                                    PeerPort => 9090,
                                    Proto    => 'tcp',
                                    Type     => SOCK_STREAM)
or die 'Couldn\'t connect to server';

# Iniate a rescan
print "Initiating a rescan...\n";
my $rescan = sendAndReceive('rescan');

$debug && print "rescan returned $rescan\n";

close $socket;

# Send given cmd to $socket and return answer with original command removed from
# front if present.  Routine nicked from code by Felix Mueller. :-)
sub sendAndReceive {
  my $cmd = shift;

  return if( $cmd eq "");

  print $socket "$cmd\n";
  $debug > 1 && print "Sent $cmd to server\n";
  my $answer = <$socket>;
  $debug > 1 && print "Server replied: $answer\n";
  $answer =~ s/$cmd //i;
  $answer =~ s/\n//;

  return $answer;
}

I will use this to automatically add music after "ripping" an audio cd.

Chapter 7  LIRC

LIRC is a package that allows you to decode and send infra-red signals of many (but not all) commonly used remote controls. The LIRC website can be found at:

http://www.lirc.org

7.1  IR receiver

For a few euro's one can build a serial infrared receiver.


Figure 7.1: DIY serial infrared receiver

Using the following command one can determine whether the receiver works or not:

mode2 -d /dev/lirc0

7.2  lirc hardware settings

Make sure the hardware settings in /etc/lirc/hardware.conf are correct:

# /etc/lirc/hardware.conf
#
# Arguments which will be used when launching lircd
LIRCD_ARGS=""

#Don't start lircmd even if there seems to be a good config file
START_LIRCMD=false

#Try to load appropriate kernel modules
LOAD_MODULES=true

# Run "lircd --driver=help" for a list of supported drivers.
DRIVER="default"
# If DEVICE is set to /dev/lirc and devfs is in use /dev/lirc/0 will be
# automatically used instead
DEVICE="/dev/lirc0"
MODULES="lirc_serial"

# Default configuration files for your hardware if any
LIRCD_CONF=""
LIRCMD_CONF=""

7.3  remote control configuration

Using the following command to create a configuration file for your specific remote control:

 
irrecord -d /dev/lirc0 /etc/lirc/lircd.conf

The lircd.conf file for the TAPE section of my Marantz RC1400 remote control:

begin remote

  name  RC1400-TAPE
  bits           13
  flags RC5|CONST_LENGTH
  eps            30
  aeps          100

  one           844   925
  zero          844   925
  plead         829
  gap          112964
  toggle_bit      2


      begin codes
          menu                     0x148B
          01                       0x14AC
          02                       0x14AE
          03                       0x14AF
          04                       0x148B
          05                       0x14AD
          play                     0x14B5
          stop                     0x1CB6
          pause                    0x14B0
          rewind                   0x1CA1
          fastforward              0x14A0
          record                   0x1CB7
          1                        0x1481
          2                        0x1C82
          3                        0x1483
          4                        0x1C84
          5                        0x1485
          6                        0x1C86
          7                        0x1487
          8                        0x1C88
          9                        0x1489
          0                        0x1C80
          clear                    0x14BA
          memo                     0x1CA9
      end codes

end remote

7.4  irexec

In /etc/lirc/lircrc actions can be coupled to remote control buttons:

begin
button = stop
prog = irexec
repeat = 0
config = /usr/local/bin/nocorrection
end

begin
button = 1
prog = irexec
repeat = 0
config = /usr/local/bin/correction1
end

begin
button = 2
prog = irexec
repeat = 0
config = /usr/local/bin/correction2
end

begin
button = 3
prog = irexec
repeat = 0
config = /usr/local/bin/correction3
end

begin
button = 4
prog = irexec
repeat = 0
config = /usr/local/bin/correction4
end

begin
button = 5
prog = irexec
repeat = 0
config = /usr/local/bin/correction5
end

begin
button = 6
prog = irexec
repeat = 0
config = /usr/local/bin/correction6
end

begin
button = play
prog = irexec
repeat = 0
config = /usr/local/bin/correction5
end

begin
button = record
prog = irexec
repeat = 0
config = /usr/local/bin/ripcd
end

begin
button = play
prog = irexec
repeat = 0
config = /etc/init.d/brutefir start
end

begin
button = clear
prog = irexec
repeat = 0
config = killall -9 brutefir.orig
end

In order to make this work irexec has to be running as a daemon, this can be accomplished by the following command:

/usr/bin/irexec -d

Create a simple init script that does this at boottime.


Figure 7.2: The result on my Harmony 885 remote control

Chapter 8  ALSA

The Advanced Linux Sound Architecture (ALSA) provides audio and MIDI functionality to the Linux operating system. The ALSA website can be found at:

http://www.alsa-project.org/main/index.php/Main_Page

8.1  ALSA configuration M-Audio Audiophile 2496


Figure 8.1: M-Audio Audiophile 2496 soundcard

For M-audio Audiophile 2496 soundcard add the following to /usr/share/alsa/alsa.conf.

pcm.ice1712 {
    type hw
    card 1
    device 0
}
ctl.ice1712 {
    type hw
    card 1
}
pcm.ice_spdif {
    type plug
    ttable.0.8 1
    ttable.1.9 1
    slave.pcm {
        type hw
        card 1
        device 0
    }
}
pcm.analogout {
    type plug
    ttable.0.0 1 # (Out Left)
    ttable.0.1 1 # (Out Right)
    slave.pcm ice1712
}

The following command can be used to launch a graphical mixer for the M-Audio soundcard:

envy24control &

Don't forget to set the DISPLAY variable when applicable.

8.2  ALSA configuration RME Digi 96/8


Figure 8.2: RME Digi 96/8 soundcard

For RME Digi 96/8 soundcard add the following to /usr/share/alsa/alsa.conf.

pcm.rme96 {
          type hw
          card 0
       }

       ctl.rme96 {
          type hw
          card 0
       }

The following command can be used to launch a CLI mixer for the RME Digi 96/8:

alsamixer

8.3  Using the M-audio soundcard with brutefir

Here is an example of how to enable brutefir to send its output to the spdif output of the M-Audio soundcard. Note that S32_LE is used since S24_LE did not work with this soundcard. It's also possible to use S24_4LE which makes it possible to keep using dithering (which does not work on 32 bits output), however I did not test S24_4LE with the M-Audio soundcard (it does work wel however on the RME Digi 96/8).

## OUTPUT DEFAULTS ##

output "l_out","r_out" {
        device: "alsa" {param: "pcm.ice_spdif";};  # module and parameters to pu
t audio
        sample: "S32_LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
        dither: false;      # apply dither
};

8.4  Using the RME soundcard with brutefir

Here is an example of how to enable brutefir to send its output to the spdif output of the RME soundcard.

## OUTPUT DEFAULTS ##

output "l_out","r_out" {
        device: "alsa" {param: "pcm.ice_spdif";};  # module and parameters to pu
t audio
        sample: "S24_4LE";   # sample format
        channels: 2/0,1;    # number of open channels / which to use
        delay: 0,0;         # delay in samples for each channel
        maxdelay: -1;       # max delay for variable delays
        mute: false,false;  # mute active on startup for each channel
        dither: true;      # apply dither
};

Please note that poll mode should be enabled in brutefir in order to get the RME card working.

8.5  Initializing soundcard at boottime

To initialize both soundcards with the correct settings at boottime the following script is run from /etc/rc.local

#!/bin/bash
echo "Initializing RME Digi 96/8 soundcard"
# Setting clock to autosync 
amixer -c0 sset 'Sample Clock Source',0 'AutoSync'
# Setting input to coaxial
amixer -c0 sset 'Input Connector',0 'Coaxial'
#
echo "Initializing M-Audio Audiophile 2496 soundcard"
# Setting volume to 100%
amixer -c1 sset 'DAC',0 127
amixer -c1 sset 'DAC',1 127
# Define analog output to be SPDIF input 
amixer -c1 sset 'H/W',0 'IEC958 In L'
amixer -c1 sset 'H/W',1 'IEC958 In R'
# Setting clock to SPDIF input
amixer -c1 sset 'Multi Track Internal Clock',0 '96000'
amixer -c1 sset 'Multi Track Internal Clock',0 'IEC958 Input'

Please note that at this point in time the RME card is used for room correction while the M-Audio card is used as a DAC for my satellite receiver (and for measuring purposes when needed).

Chapter 9  Audio cd ripping

In order to rip audio cd's to the harddisk I am using rubyripper. The website of rubyripper can be found at:

http://wiki.hydrogenaudio.org/index.php?title=Rubyripper

9.1  configuration

The rubyripper configuration file can be found at /etc/rubyripper.cfg

vorbissettings=-q 4
flacsettings=--best
cd=#<Cddb:0xb7c74f18>
naming_normal=%a/%b/%n-%t
max_tries=5
wav=false
vorbis=false
flac=true
req_matches_all=2
req_matches_errors=2
naming_various=%b/%b/%n-%t
threading=false
edit=false
freedb=true
username=anonymous
mp3=false
eject=false
verbose=true
basedir=/music/nieuw
site=freedb.org
rippersettings=-Z
playlist=false
first_hit=true
hostname=my_secret.com
debug=true
offset=48
cdrom=/dev/hdc
othersettings=
mp3settings=-V 3
other=false
instance=#<Gui_CLI:0xb7cab518>

9.2  Ripping a cd

The script I use to rip an audio cd:

#!/bin/bash
echo "Ripping audio cd" > /dev/speech
/usr/local/bin/rrip_cli -v -f /etc/rubyripper.cfg -a
/usr/bin/eject
#/usr/bin/perl /usr/sbin/slimserver-scanner --logdir=/var/log/slimserver/
--logconfig=/etc/slimserver/log.conf --prefsdir=/etc/slimserver
--prefsfile=/etc/slimerver/slimserver.pref --priority=0 --rescan --cleanup
echo "Your new cd has been added" > /dev/speech

Please note that the slimserver-scan part is not working correctly yet! This script is initiated from irexec in order to enable the ripping of an audio cd to start using the infrared remote control.


This document was translated from LATEX by HEVEA.