![[We support real
stereo]](smallstereo_white.gif)
[Overview] [Player] [RACE] [LoCo] [DRC]
audacious and aplayplay_simple--extra-bytes-per-second parameter for playhrtwriteloop and catloopvolracelisten_loop script)ssh)--extra-bytes-per-second parameter for bufhrtbufhrtimprovefileThese utilities should help to achieve "ultimate" (ok, that's impossible, but we can try to get closer) sound quality when one or several computers running Linux are used for playback of music files. I mainly have in mind playback via a "high end" stereo system, but I have used some of these programs also for improving playback on a notebook.
The aim is not to provide yet another graphical user interface to your music collection. In fact, the programs I provide are meant to be used via the command line or within custom scripts. (Of course, they could be called by fancier user interfaces.)
While developing these programs I was surprised to learn how much influence playback software can have on sound quality. The software is just as important as the computer hardware and the other components of your stereo setup. If the manufacturer of your DAC or soundcard claims it were immune against "jitter", do not believe it!
The software here is free software, it comes with full source code, you can change and redistribute it, and it can be downloaded and used without costs. The precise conditions are described in the GNU GPL. Of course, in particular: using the software distributed here is completely on your own risk. As with any new component in your stereo system, keep the volume very low during first tests.
This package is developed on Linux systems and is mainly meant to be run on Linux. (But some code may compile and can be used on other systems as well.)
The software is available via this git-repository.
For bug reports, problem reports and suggestions use its
issue
tracker.
Installation instructions are in the file INSTALL inside
the code directory. This includes a description of software packages
needed for the installation.
When all needed packages are installed on your Linux system, the installation should just look like:
git clone https://github.com/frankl-audio/frankl_stereo.git
cd frankl_stereo
make REFRESH=X8664 # on x86 machines, or
make REFRESH=AA64 # on arch64 machines (e.g. Raspberry Pi 4)
The compiled programs are then in the subdirectory ./bin/.
Copy or link them somewhere in your standard
PATH (e.g., /usr/local/bin/).
Music stored on a computer is encoded in a sequence of bits, 0's and 1's. To play it these bits need to be sent to a DAC or soundcard which translates them to an analogue wave form which is then amplified and made audible through speakers or headphones. So, do we "just" need to get the correct 0's and 1's quickly enough to the DAC to get an optimal playback on a given system? Unfortunately, it is not that easy. The point is that in real hardware the 0's and 1's are also stored and transfered by analogue (e.g., electrical or magnetic) signals. (With "analogue" I mean here something that can have many states, e.g. a voltage, a charge, a magnetisation, which physically represent one of the two abstract states 0 or 1.) Let me illustrate my vague understanding of this with some (made up) pictures. Say, we want to transfer some bits by an electrical signal, each bit is encoded by a voltage which is for a certain amount of time above or below some threshold. The ideal signal I have in mind would look like this to encode bits 1101000101:
But a real electrical signal cannot have infinitely steep slopes, maybe it looks more like that:
Well, probably the power supply for this signal is far from perfect, maybe this is more realistic:
And for some components in a computer even the following heavily distorted looking version is still good enough to correctly(!) recover or interpret the bits from the signal:
There are also other signal depending distortions like reflections in a cable.
Similar remarks apply to the storage of bits on a hard disk by magnetisation of particles along a track on the disk. Or to bits stored on an SSD or USB-stick in charged cells. Or to bits stored in computer memory (RAM) whose cells we can imagine as small caps which are charged above or below some threshold to store a 1 or 0, and the charge is steadily leaking and needs to be refreshed regularly.
The strength of digital storage and processing is that distortions as imagined above do not matter; as long as all components can correctly recognize a 0 or 1, absolutely no information is lost.
A DAC chip will also work "correctly" within a certain specification if the bits in the incoming signal can be correctly recognized. But in practice the audible quality of the outgoing signal does depend on the wave form of the incoming signal, and not just on the bits it encodes! One aspect is the precise time when the DAC chip notices that the incoming signal is switching from above a threshold to below or vice versa. With the perfect square wave such switches occur only at precise equidistant points of time. With a wave form as in the second example above the switches are also recognized clearly, but there is a variation of the time differences between the switches. These timing errors are called jitter and higher jitter leads to worse sound quality of the analogue audio output signal. With the more distorted wave forms also the time when a switch is recognized is less clear. Another aspect is that distortions of the digital signal, say from a not so clean power supply, cannot be filtered out completely by the DAC chip, there will also be some corresponding distortion on the output wave form.
Summary: Bits in a computer and DAC are stored and transferred by various forms of analogue signals. The task of this project is to experiment if and how we can get a cleaner signal to the DAC by player software.
Starting from the vague and largely simplified model of data storage and transport in computer and DAC as explained above, I experimented with two main ideas for the programs distributed here.
The first idea is to process and transfer audio data in chunks of
constant size, each within an interval of time of constant length.
For this we use the high resolution timers on modern computer hardware which
are made available in Linux by
clock_nanosleep and related functions.
For example, the program playhrt which sends audio data to
a DAC or soundcard has a main loop where in each round a chunk of data is read,
then the program sleeps until a certain point of (wall clock) time and after
wakeup sends the data to the hardware driver. So, the hardware driver
receives (almost) constant sized chunks of input data in very regular
time intervals.
The second idea is to refresh the representation of audio data in RAM or on a hard disk by (logically unnecessary) reading and rewriting of data. In the model explained above the hope is to increase the amplitude of the signals (and so also the steepness of slopes and so to reduce the related jitter).
I'm aware that the model I have explained above is very much simplified. In reality, the processing of data is very complicated. The processed bits are transformed between various physical representations and encodings, data are buffered in various places on hard- and software level (e.g., several levels of caches on a harddisk, network card or in RAM, software buffers in application programs and in hardware drivers, and so on). Having this in mind it seems completely hopeless to produce audible differences between programs which all perform a "bit-perfect" music playback. Fortunately, I started my experiments despite such doubts. (Ok, and encouraged by knowing that others are trying similar things for the Windows or Mac platform, e.g. jplay, MQn, Amarra.)
I have evaluated and compared various versions of my (and other)
programs only by listening tests. The starting reference were the standard
Linux programs audacious and
aplay.
It would be nice to do some sort of technical measurements, which underpin and explain audible differences. But that is probably extremely challenging and certainly outside my technical possibilities.
I was repeatedly surprised how significant audible differences can be in this context. And this not only in my "high-end" stereo setup, but even on a notebook computer or monitor with its lower quality speakers behind the display.
Here is an overview of the programs and example scripts provided by this project.
After installation you can run each program with option --help
to read its full documentation
(or click on the function names in the overview below).
The first two, playhrt and bufhrt are the heart
of the project.
playhrtbufhrtcat64libsndfile library) and outputs its samples in 64-bit
floating point formatresample_soxrsox only
uses 32-bit integer encoding internally); this includes the functionality of
cat64 and volrace
writeloop
and catloopvolracecptoshm and
shmcathighrestestplayhrt and
bufhrt indicate that a high res timer is used)
play_XXXXimprovefilebufhrt
can be used to "improve" music files stored on a hard disk
We assume that you are on a Linux system with the programs provided here
installed. Furthermore, you should be able to play sounds via a soundcard or
external sound device with some installed player program (e.g., audacious).
You should also have a few other programs: sox,
aplay,
flac,
and maybe
brutefir
(if you want to play with convolving), or some version of
nc (if you
want a two-computer playback setup).
On Debian and related distributions (Debian, Ubuntu, Mint, Raspian, ...)
you may use (as root user, or with a sudo in front):
apt-get install sox libsox-fmt-mp3 brutefir flac netcat-openbsd
On other systems there is certainly a similar command.
Furthermore, we assume that your Linux kernel supports the high resolution
timers of modern hardware. This is usually the case, you can test it by
calling the small utility progam highrestest which is contained
in this package. It will print some information.
We provide example scripts which demonstrate the use of the programs in this package. Since a few parameters are system dependent it may be necessary to adjust them before a script works on your system. We comment on a few parameters which can be adjusted close to the top of each script.
hw:0.0 is often alright. If not, use
aplay -l to find out the correct card and device number.
We suggest to use the hardware devices directly (and not some virtual or
plughw-devices). This is used for the --device option of
playhrt. --extra-bytes-per-second option of playhrt and
bufhrt, respectively. This is to synchronize the speed of
different computers or of a computer and a DAC or soundcard.
It is explained below how to find good values.Introduce more such variables yourself if you want to play with other
parameters. For some sound cards the default value of --hw-buffer
may not be valid.
All scripts discussed here can also be found in the subdirectory
scripts of the software distribution.
audacious and aplay
Say, you want to play a music file mymusic.flac which is in
44.1/16 format as music from CDs (other formats instead of flac should also
work, e.g., mymusic.wav or even mymusic.mp3).
First check if your sound device is working with (you may use any other
player you know instead of audacious):
audacious mymusic.flac
Now use aplay. But don't let aplay decode the
flac file. Do the decoding with sox such that it sends
the raw audio data to its standard output (denoted by -).
Send this output through a
pipe | to the standard input of aplay.
Then aplay must be told the format of the input (raw input with
samples in S16_LE (16 bit CD) format, two channels and a sample rate of 44100).
Adjust the -D parameter if your sound card is not card 0 and
device 0, find the correct parameter of your card with aplay -l.
sox mymusic.flac -t raw - | aplay -t raw -f S16_LE -c 2 -r 44100 -D hw:0,0
Stop the playback by pressing Ctrl-C.
Use the manpage of
sox and the
manpage of aplay if you want to learn more about these
programs.
The sox program can also decode many other formats. For
flac-files you can alternatively use flac which has
--skip and --until options to decode only a part
of a file. Here is an example (a backslash before a newline character means
that the next line should be appended, this allows to format very long input
lines nicely).
flac --totally-silent --force-raw-format --sign=signed --endian=little \
-d -c mymusic.flac | aplay -t raw -f S16_LE -c 2 -r 44100 -D hw:0,0
pulseaudiopulseaudio
there can be problem with shown commands if they want to access a hardware device (because the sound server is blocking it). In that case use the
player command with pasuspender prepended, e.g., instead of
playhrt use pasuspender -- playhrt.
play_simple
Now use the program playhrt from this project instead of
aplay. Here is the first simple shell script.
#!/bin/bash
#########################################################################
## frankl (C) 2015 play_simple
##
## See README or http://frankl.luebecknet.de/stereoutils/player.html
## for explanations.
#########################################################################
CARD="hw:0,0"
EXTRABYTESPLAY=0
VERBOSE="--verbose --verbose"
sox "$1" -t raw -r 44100 -c 2 -e signed -b 16 - | playhrt \
--stdin \
--device=${CARD} \
--sample-rate=44100 \
--sample-format=S16_LE \
--loops-per-second=1000 \
--mmap \
--non-blocking-write \
${VERBOSE} \
--extra-bytes-per-second=${EXTRABYTESPLAY} \
--hw-buffer=16384
Here $1 is substituted by the first argument of the call of
this script, that is mymusic.flac if we call:
play_simple mymusic.flac
Compared to the earlier example we now tell sox that it should
generate output with two channels, a sample rate of 44100 and with 16 bit
samples. This will also work if the file we want to play is not in 44.1/16
format, sox will resample to 44.1/16 if needed.
The script calls playhrt with the recommended options
--mmap
and --non-blocking-write. Here
playhrt computes from the options how many bytes are needed by
the sound device per second (44100 x 2 x 2 = 176400 bytes), and it writes
exactly this amount per second to the memory of the driver of your sound
device. Chunks of (almost) the same
size are written 1000 times per second with quite precisely 1/1000 of a
second time distance. In practice it often happens that your computer and
your sound device have a slightly different idea about how long a second is.
In the next section we describe how to synchronize the speed of
playhrt to your sound device.
--extra-bytes-per-second parameter for playhrt
If playhrt is called with --mmap and with double
--verbose --verbose argument it will print lines like
playhrt: average available buffer: 8495 (1031175 sec 282972195 nsec)
In the beginning the available buffer should be a bit bigger than half
of the --hw-buffer-size. When it gets too large or small
playhrt will try to adjust its speed once and print a
hint about a sensible value for the --extra-bytes-per-second
parameter. If at the end of playback the available buffer size is
much different compared to
the beginning then playhrt will also print a hint for
a better value of the --extra-bytes-per-second
parameter. This will work more precisely with longer files, and it may
be necessary to improve that parameter in several iterations.
writeloop and catloopSometimes it can be useful to decouple two programs (or chains of programs) via a buffer. For example, the first program reads its data from a not so reliable network or the data are processed in a long chain of different programs, but the second program needs to read data regularly.
Here is a variant of the simple player script where the decoded data are
first written into a buffer with writeloop and then
they are read with catloop and sent to playhrt.
For the buffer there are two possibilities. You can either use two or
more regular files, usually on a ramdisk; or you can use blocks of shared
memory which has the advantage that it avoids file system overhead and
additional buffering by the operating system. We use the second variant,
the convention is to specify names for these shared memory blocks starting
with a slash /. The first command is
sent into background with &, such that the second is also
started.
#!/bin/bash
#########################################################################
## frankl (C) 2015 play_writecatloop
##
## See README or http://frankl.luebecknet.de/stereoutils/player.html
## for explanations.
#########################################################################
CARD="hw:0,0"
EXTRABYTESPLAY=0
VERBOSE="--verbose --verbose"
trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT
rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}*
sox "$1" -t raw -r 44100 -c 2 -e signed -b 16 - | \
writeloop --block-size=1024 --file-size=4096 --shared /bl1 /bl2 /bl3 &
# maybe the start of the background command takes a little time
sleep 0.1
catloop --block-size=1024 --shared /bl1 /bl2 /bl3 | \
playhrt \
--stdin \
--device=${CARD} \
--sample-rate=44100 \
--sample-format=S16_LE \
--loops-per-second=1000 \
--mmap \
--non-blocking-write \
${VERBOSE} \
--extra-bytes-per-second=${EXTRABYTESPLAY} \
--hw-buffer=16384
The shared memory blocks can be seen as
virtual files in the directory /dev/shm. There are also
files sem.bl1* which are used for synchronization between
the two programs.
The script can be interrupted with Ctrl-C. Without the
trap line the background processes would remain running.
volrace
In this example we introduce volume control during playback and
the RACE algorithm. The parameters are
given in the file /tmp/VOLRACE which is created when
it does not exist. Once the music is running you can edit that file
(or change or overwrite it by some script)
to change the parameters on the fly (and volrace
will fade to the new values). If the file contains only one parameter
then RACE is disabled. The first parameter for the volume can be negative
in which case the phase of the music will be inverted.
The default parameters may be interesting on a notebook with builtin
speakers, it creates a 3D-image as in
Ambiophonics.
In a standard
stereo setup try 0.6 13 0.22, for headphones use only the
first volume parameter or set the third parameter to 0.0.
This script also demonstrates how sox can convert
the music to 64-bit floating point samples, this is needed by volrace,
and would also be useful for other filters (e.g., a convolver for room
correction). And vice versa it shows how to cut the samples to 16-bit
integers with the application of dithering and noise shaping.
Alternatively, instead of sox one could also use
cat64 or resample_soxr from this package.
#!/bin/bash
#########################################################################
## frankl (C) 2015 play_volrace
##
## See README or http://frankl.luebecknet.de/stereoutils/player.html
## for explanations.
#########################################################################
CARD="hw:0,0"
EXTRABYTESPLAY=0
VERBOSE="--verbose --verbose"
trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT
rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}*
if ! test -e /tmp/VOLRACE ; then
echo "0.6 4 0.75" > /tmp/VOLRACE
fi
sox "$1" -t raw -r 44100 -c 2 -e float -b 64 - | \
volrace --param-file=/tmp/VOLRACE | \
sox -t raw -r 44100 -c 2 -e float -b 64 - \
-t raw -e signed -b 16 - dither -f high-shibata | \
writeloop --block-size=1024 --file-size=4096 --shared /bl1 /bl2 /bl3 &
# maybe the start of the background command takes a little time
sleep 0.1
catloop --block-size=1024 --shared /bl1 /bl2 /bl3 | \
playhrt \
--stdin \
--device=${CARD} \
--sample-rate=44100 \
--sample-format=S16_LE \
--loops-per-second=1000 \
--mmap \
--non-blocking-write \
${VERBOSE} \
--extra-bytes-per-second=${EXTRABYTESPLAY} \
--hw-buffer=16384
In this example we apply further processing to the music signal before playing it. First we resample any input to a specified sample rate (192000) and then we use a convolver, which may be fed with a room correction filter and/or a LoCo filter, or which may implement a crossover in the digital domain (in the latter case you would need a multi channel sound card for the output).
Resampling should be done with high quality, in our example script we use
resample_soxr from this package (this is true 64-bit processing,
while for example sox would only use 32-bit integers internally).
Adjust the value of the variable SAMPLERATE if you want other
output than 192000.
The convolving is done with
brutefir which is called with a configuration file. You can find an example of such a
configuration file on the LoCo
page. Specify your config file in the variable BRUTEFIRCONFIG
in the script. In the example we assume that brutefir produces
output samples in S32_LE format.
Of course, if you just want to try the upsampling and no convolving then
delete or comment the line with brutefir in the script.
A further change compared to the previous example scripts is that we
first copy the music file into RAM and play it from there.
Of course this could be done with simple copying into a ramdisk, like
(assuming /tmp is a ramdisk):
FILENAME=`basename $1`
cp $1 /tmp/${FILENAME}
and then playing /tmp/${FILENAME} instead of
$1.
But in our example we use the programs cptoshm and
shmcat provided by this package (and assuming that we
want to play a flac-file). This avoids some operating
system and file system overhead.
#!/bin/bash ######################################################################### ## frankl (C) 2015 play_upsample_convolve ## ## See README or http://frank_l.bitbucket.org/stereoutils/player.html ## for explanations. ######################################################################### CARD="hw:0,0" EXTRABYTESPLAY=0 SAMPLERATE=192000 # configure brutefir with output format needed by your DAC # (here we assume S32_LE) BRUTEFIRCONFIG=VERBOSE="--verbose --verbose" # find input sample rate - only needed when resample_soxr is not used below ORIGRATE=`sox --i "$1" | grep "Sample Rate" | cut -d: -f2 | sed -e "s/ //g"` trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT cptoshm --file="$1" --shmname=/play.flac rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}* if ! test -e /tmp/VOLRACE ; then echo "0.6 4 0.75" > /tmp/VOLRACE fi # old version, without resample_soxr #shmcat --shmname=/play.flac | \ # sox -t flac - -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - | \ # sox -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - \ # -t raw - vol 0.8 rate -v -I ${SAMPLERATE} | \ # volrace --param-file=/tmp/VOLRACE | \ # brutefir ${BRUTEFIRCONFIG} -quiet | \ # writeloop --block-size=1024 --file-size=4096 --shared /bl1 /bl2 /bl3 & resample_soxr --shmname=/play.flac --param-file=/cache/VOLRACE \ --outrate=${SAMPLERATE} --fading-length=100000 | \ brutefir ${BRUTEFIRCONFIG} -quiet | \ writeloop --block-size=1024 --file-size=4096 --shared /bl1 /bl2 /bl3 & # allow some startup time for background command sleep 0.5 # if this is started from an environment that uses the pulseaudio # sound server, you can substitute 'playhrt' below by # 'pasuspender -- playhrt' catloop --block-size=1024 --shared /bl1 /bl2 /bl3 | \ playhrt \ --stdin \ --device=${CARD} \ --sample-rate=${SAMPLERATE} \ --sample-format=S32_LE \ --loops-per-second=1000 \ --mmap \ --non-blocking-write \ ${VERBOSE} \ --extra-bytes-per-second=${EXTRABYTESPLAY} \ --hw-buffer=8192
listen_loop script)In my experience it can be good for the sound quality to do the processing of music data on one sufficiently powerful machine (call this the convolver machine) and to do the actual playing on another dedicated machine (call this the audio machine). The data are transfered between the machines via the (preferably Ethernet) network.
We demonstrate how to achieve this using the program bufhrt
from this package. It buffers incoming data and can send them over the
network to another machine. Similarly to what playhrt does,
the data are sent in chunks of (almost) constant size within short
time intervals of constant length. If parameters are chosen carefully
it can be avoided that the network buffers on the two machines are filled
up, thereby avoiding that writing or reading data is blocking processes.
The first variant works by starting once a
simple script listen_loop on the audio machine
that runs a loop which listens on the network for some input and then executes
this input. (Warning: do not use this on a computer which can be connected
from untrusted machines because everyone with network connection to the
audio machine can start arbitrary commands on it.)
The communication between the two computers is done with the
UNIX utility nc
(also called netcat).
The script looks as follows:
#!/bin/bash ######################################################################### ## frankl (C) 2015 listen_loop ## ## See README or http://frankl.luebecknet.de/stereoutils/player.html ## for explanations. ######################################################################### while true; do command=`nc -l -p 5501` eval "$command" done
Login to the audio machine and start this script in the background.
You may do this in a
tmux or
screen
session, then you can logout and login again and still check the output of
the remote commands.
listen_loop &
A corresponding player script on the convolver machine sends an
appropriate call of playhrt
to be executed on the audio machine. We modify
play_upsample_convolve from above to get an example.
Instead of playhrt we now call
bufhrt on the convolver machine.
The other example scripts from above can be adjusted similarly to
play on a remote machine.
HINT: there is an error in this script: change
shmcat /play.flac 32768 to
shmcat --shmname=/play.flac
#!/bin/bash
#########################################################################
## frankl (C) 2015 play_remote
##
## See README or http://frankl.luebecknet.de/stereoutils/player.html
## for explanations.
#########################################################################
CARD="hw:0,0"
VERBOSE="--verbose --verbose"
EXTRABYTESBUF=10
EXTRABYTESPLAY=0
AUDIOCOMPUTER="<put here name of your audio machine>"
SAMPLERATE=192000
SAMPLEFORMAT=S32_LE
# this is 192000 * 4 * 2 (32 bit samples, 2 channels)
BYTESPERSECOND=1536000
BRUTEFIRCONFIG="<path to your brutefir config>"
HOST=`hostname`
PORT=5570
# find input sample rate
ORIGRATE=`sox --i "$1" | grep "Sample Rate" | cut -d: -f2 | sed -e "s/ //g"`
trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT
cptoshm --file="$1" --shmname=/play.flac
rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}*
if ! test -e /tmp/VOLRACE ; then
echo "0.6 4 0.75" > /tmp/VOLRACE
fi
shmcat /play.flac 32768 |
sox -t flac - -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - | \
sox -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - \
-t raw - vol 0.8 rate -v -I ${SAMPLERATE} | \
volrace --param-file=/tmp/VOLRACE | \
brutefir ${BRUTEFIRCONFIG} -quiet | \
writeloop --block-size=1536 --file-size=9216 --shared /bl1 /bl2 /bl3 &
sleep 0.5
# tell audio computer what to do, short sleep as startup time for sender
REMOTE="sleep 0.3;\
playhrt ${VERBOSE} --host=${HOST} --port=${PORT} \
--sample-rate=${SAMPLERATE} \
--sample-format=${SAMPLEFORMAT} \
--loops-per-second=1000 \
--device=${CARD} --hw-buffer=7680 \
--extra-bytes-per-second=${EXTRABYTESPLAY} \
--non-blocking-write --mmap "
echo "${REMOTE}" | nc -q 0 ${AUDIOCOMPUTER} 5501
# instead of
# catloop --block-size=1024 --shared /bl1 /bl2 /bl3 | \
# we use the --shared option of bufhrt.
bufhrt --bytes-per-second=${BYTESPERSECOND} \
--loops-per-second=2000 --port=${PORT} \
--extra-bytes-per-second=${EXTRABYTESBUF} \
${VERBOSE} \
--shared /bl1 /bl2 /bl3
A few more comments on the play_remote script.
If bufhrt is called with --shared option then the
size of the shared memory chunks should be divisible by the number of
bytes to be sent per loop (in our example we send 1536 bytes per loop and
the size of the shared memory blocks is 6*1536 = 9216 bytes).
ssh)In my setup the audio computer is only connected to the convolver
computer, and the listen_loop approach above is no security
risk. For a more secure access to the audio computer one can use the
secure shell ssh.
There are several possibilities to configure ssh such that
no password is needed for login. Consult the web if you do not know how to
do this.
The remote player script becomes a little more tricky in this case,
because remote background processes started via ssh are by
default terminated when the shell terminates. To avoid this the remote
programs must be called with nohup and standard input and
output must be redirected. Here is the
play_remote_ssh script as alternative to
play_remote above.
HINT: there is an error in this script: change
shmcat /play.flac 32768 to
shmcat --shmname=/play.flac
#!/bin/bash
#########################################################################
## frankl (C) 2015 play_remote_ssh
##
## See README or http://frankl.luebecknet.de/stereoutils/player.html
## for explanations.
#########################################################################
CARD="hw:0,0"
EXTRABYTESBUF=200
EXTRABYTESPLAY=0
VERBOSE="--verbose --verbose"
AUDIOCOMPUTER=<name of your audio computer>
SAMPLERATE=192000
SAMPLEFORMAT=S32_LE
# this is 192000 * 4 * 2 (32 bit samples, 2 channels)
BYTESPERSECOND=1536000
BRUTEFIRCONFIG=<path to your brutefir configuration>
HOST=`hostname`
PORT=5570
PLAYHRTLOGFILE=/tmp/playhrt.log
# find input sample rate
ORIGRATE=`sox --i "$1" | grep "Sample Rate" | cut -d: -f2 | sed -e "s/ //g"`
trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT
cptoshm --file="$1" --shmname=/play.flac
rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}*
if ! test -e /tmp/VOLRACE ; then
echo "0.6 4 0.75" > /tmp/VOLRACE
fi
shmcat /play.flac 32768 |
sox -t flac - -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - | \
sox -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - \
-t raw - vol 0.8 rate -v -I ${SAMPLERATE} | \
volrace --param-file=/tmp/VOLRACE | \
brutefir ${BRUTEFIRCONFIG} -quiet | \
writeloop --block-size=1536 --file-size=9216 --shared /bl1 /bl2 /bl3 &
sleep 0.5
# tell audio computer what to do
REMOTE="nohup sleep 0.8 >/dev/null 2>/dev/null </dev/null ;\
nohup playhrt ${VERBOSE} --host=${HOST} --port=${PORT} \
--sample-rate=${SAMPLERATE} \
--sample-format=${SAMPLEFORMAT} \
--loops-per-second=1000 \
--device=${CARD} --hw-buffer=7680 \
--extra-bytes-per-second=${EXTRABYTESPLAY} \
--non-blocking-write --mmap \
>/dev/null 2>${PLAYHRTLOGFILE} </dev/null "
#echo "${REMOTE}" | nc -q 0 ${AUDIOCOMPUTER} 5501
REMOTE='sh -c "( ( '${REMOTE}' ) & )"'
echo "${REMOTE}"
ssh ${AUDIOCOMPUTER} "${REMOTE}"
bufhrt --bytes-per-second=${BYTESPERSECOND} \
--loops-per-second=2000 --port-to-write=${PORT} \
--extra-bytes-per-second=${EXTRABYTESBUF} \
${VERBOSE} \
--shared /bl1 /bl2 /bl3
--extra-bytes-per-second parameter for bufhrtThe parameters of the bufhrt utility should be chosen such
that the program writes out exactly the amount of data needed by the
receiving side. In the remote player examples above we can use
the UNIX command netstat -tpn on the convolver and on the
audio machine repeatedly while the music is playing (on the convolver
machine we look at the entry of the Send-Q column for
bufhrt and on the audio machine at the entry of
the Recv-Q column for playhrt. If these values
remain roughly constant while the music is playing all settings are fine.
If not, here is an example what to do: Say,
the value on the audio computer decreases (respectively increases) by about
3600 bytes during 5 minutes. Then we can add (respectively subtract) 12 to the
current value of the --extra-bytes-per-second parameter of
bufhrt (5 minutes are 300 seconds and 300 * 12 = 3600).
Further finetuning is possible by playing around with explicitly setting
input and output buffer sizes, see parameters --in-net-buffer-size
of playhrt and --out-net-buffer-size of
bufhrt. It could be good to use small buffers, but keep in mind
that an uninterrupted data flow is more important.
bufhrt
When I used a convolver machine further away from the stereo system I have
made good experience with using a small computer like a Raspberry Pi as
buffer between the convolver and audio machine. The buffering was done by
running bufhrt with input and output from and to the network.
(In my current setup everything is close to the stereo system and only one short high quality network cable is used between convolver and audio machine.)
improvefileIn some experiments I noticed that two bit-identical copies of the same music file on the same SSD, harddisk or other medium can sound differently(!). To my surprise this can still be true when the files are heavily processed (upsampled, convolved, ...) for playback. My guess is that on a hard disk there is a huge bandwidth of possibilities how the same bit pattern can be represented by magnetized particles on a harddisk (similarly for voltages in memory cells of SSD, USB-stick or memory card) and that different representations influence the timing and amplitude of the output signal of the harddisk when the data are read. (I do not think that fragmentation of the file system or error correction plays a role here). There is a longer thread on this topic in this forum (in German).
After a lot of testing I can improve the sound quality of music files on
the SSD in my stereo system quite significantly with the help of
bufhrt. This is done as in the following script
improvefile.
The current version and further changes of the underlying code came out of discussions and
extensive listening tests by several users (thanks for all the testing and the feedback!)/
#!/bin/bash ######################################################################### ## frankl (C) 2015-2024 ## ## USAGE: ## improvefile## This script generates a (bit-identical) copy of a file to ## the file . ## ######################################################################### if test -e "$2" ; then echo "the file $2 already exists, please delete it first" exit fi ## This utility is mainly meant for music files (and maybe certain filter and ## program files for music playback). ## With these example parameters the output file will be written ## with 8 MB/sec. These will be written in 1024 loops, that is ## 8 kB per loop. ## Before writing out the data, they are copied 3 times to a temporary ## buffer and back, this is done with 24 MB/sec. ## Once a second the written data are synced to the media containing ## the file. bufhrt --interval --file $1 --outfile=$2 \ --buffer-size=536870912 --loops-per-second=1024 \ --bytes-per-second=8388608 --number-copies=3 \ --ram-loops-per-second=1024 --ram-bytes-per-second=25165824 \ --dsyncs-per-second=1 ## You may experiment with these parameters. ## Of course, you can vary the bytes and loops per second for faster ## or slower writing. ## Or try more, but faster copies (like --number-copies=16 or higher ## and without the --ram... options). ## The sychronization could be done more often (like ## --dsyncs-per-second=32) ## For best results you should reduce the --bytes-per-second, very good ## results on some flash media (CF, SD cards, SSD) were achieved with low ## values like 131072 - of course this takes a long time. ## It can be sensible (but is not needed) that the --bytes-per-second ## are divisible by the value of --loops-per-second and that the number of ## bytes written per loop is a multiple of 512 (the usual block size on ## flash cards, SSDs). But you could also try non-divisible values. ## Here are example parameters found in a cooperation of several users. ## The output is written very slowly, so some patience is needed. So far, ## they yield the best results on the authors playback system. # bufhrt --interval --file="$1" --outfile="$2" \ # --buffer-size=419430400 --loops-per-second=10 \ # --bytes-per-second=188044 --number-copies=10 \ # --ram-loops-per-second=100 --ram-bytes-per-second=1880440 \ # --dsyncs-per-second=1 ## For best results you should use a computer optimized for audio ## (PC or small computers from Odroid or Raspberry Pi with audiophile ## power supply). If you have several CPU cores, try to isolate some ## of them such that they can be used for audio programs exclusively. ## E.g., if CPU 2 is isolated, start bufhrt on it with high priority: ## chrt -f 60 taskset -c 2 bufhrt .....
The script needs to be used only once (not before every playback). The effect on sound quality will probably decrease when the file is copied (with some system program) to another disk or sent over the network to another computer.
The script uses the --interval option of
bufhrt. This causes that the program in intervals only reads
data until the (large) buffer is filled and then writes out the buffer
(without reading more input) in
well timed blocks which are refreshed in RAM before writing. If you have a
lot of RAM you can increase the --buffer-size and you can
experiment with the --bytes-per-second parameter (which should
be somewhat lower than the maximal write speed of your disk).
I use bufhrt with similar arguments to store pre-convolved
files on my SSD (that is, in a script like
play_upsample_convolve above I substitute the final call of
playhrt with a call to bufhrt with output file on
the harddisk).
In general it can be advantageous to run as few processes as possible on your convolver and/or audio computer. Stop all jobs and processes which are not needed. If possible, use your audio computer remotely with no graphical user interface running or monitor connected.
It can be useful to give playhrt and bufhrt a
high process priority with chrt. (If you are not allowed
to do this on your system, start your player program with sudo
or change the setuid bit of chrt once with
sudo chmod 4755 `which chrt`.) For example, you could
change in one of the scripts above the call playhrt ... to
chrt -f 70 playhrt ....
If you want to copy many music files into RAM you either need a huge
ramdisk or a large space for shared memory blocks.
To create a large ramdisk add (as root or with sudo)
a line like
none /ramdisk tmpfs defaults,size=800M,mode=1777,exec,nosuid 0 0
in your file /etc/fstab. If not yet done create the directory
with sudo mkdir /ramdisk and mount with
sudo mount /ramdisk (the latter is automatic on reboot).
Adjust the size to some value which is somewhat smaller than the amount of
physical RAM.
To enlarge the space that can be used for shared memory, use a command like
sysctl -w "kernel.shmmax=1782579200"
or some even higher value if you have more than 2GB of RAM. It depends on
your system setup how to set this automatically after reboot. (On my Debian
systems one can put a line kernel.shmmax=1782579200 in a file
/etc/sysctl.d/shmmax.conf to achieve this.
On a multicore processor it is possible to prescribe the core on which
a process is running, using the taskset utility. For example,
on a 4-core processor (cores 0,1,2,3) one could put brutefir
and bufhrt or playhrt on different cores
(with taskset -a -c 1 brutefir ... or
chrt -r 99 taskset -c 3 bufhrt ... and so on.
The speed of modern processors (or the separate cores) is usually
adjusted dynamically by the hardware depending on the load. On some systems
this dynamic change of the speed can be avoided and the speed can be
prescribed by the user. For example, on my convolver machine I delegate the
speed setting to userspace by sudo cpufreq-set -g userspace.
In the beginning of my player scripts I push up the speed with
cpufreq-set -f 1.2GHz and at the end I set it back to 0.25GHz
(use cpufreq-info to find out the possible values).
A very useful trick is to make some CPU cores unavailable to system
programs with the isolcpus parameter to the Linux kernel. For
example, on my convolver computer, a Raspberry Pi 4 with four CPU cores
numbered 0, 1, 2, 3, I use the kernel parameter isolcpus=1,2,3.
Then the system only uses core 0 for all processes, except when they are started
explicitly with taskset on one of the cores 1, 2 or 3.
This way I can reserve cores 1, 2, 3 exclusively for the audio programs.