Merge branch 'ucm'
diff --git a/.gitignore b/.gitignore
index 0319f44..d17c89a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,7 @@
 seq/aseqdump/aseqdump
 seq/aseqnet/aseqnet
 speaker-test/speaker-test
+alsaloop/alsaloop
 
 include/aconfig.h*
 include/stamp-*
diff --git a/Makefile.am b/Makefile.am
index d92e204..21d69ab 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -15,6 +15,9 @@
 endif
 if HAVE_PCM
 SUBDIRS += aplay iecset speaker-test
+if ALSALOOP
+SUBDIRS += alsaloop
+endif
 endif
 if HAVE_SEQ
 SUBDIRS += seq
@@ -34,3 +37,9 @@
 	else \
 		$(TAR) --create --verbose --file=- $(distdir) | bzip2 -c -9 > $(distdir).tar.bz2 ; \
 	fi
+
+install-data-hook:
+	$(MKDIR_P) -m 0755 $(DESTDIR)$(ASOUND_STATE_DIR)
+
+DISTCHECK_CONFIGURE_FLAGS = \
+        --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
diff --git a/alsaconf/alsaconf.in b/alsaconf/alsaconf.in
index ccc1b37..5c23787 100644
--- a/alsaconf/alsaconf.in
+++ b/alsaconf/alsaconf.in
@@ -956,8 +956,9 @@
 	  aplay -N $TESTSOUND
       fi
     fi
-    if [ ! -r /etc/asound.state ]; then
-	xecho "Saving the mixer setup used for this in /etc/asound.state."
+    mkdir -p -m 0755 @ASOUND_STATE_DIR@
+    if [ ! -r @ASOUND_STATE_DIR@/asound.state ]; then
+	xecho "Saving the mixer setup used for this in @ASOUND_STATE_DIR@/asound.state."
 	$sbindir/alsactl store
     fi
     clear
diff --git a/alsactl/.gitignore b/alsactl/.gitignore
new file mode 100644
index 0000000..56ab3a2
--- /dev/null
+++ b/alsactl/.gitignore
@@ -0,0 +1,3 @@
+alsa-store.service
+alsa-restore.service
+90-alsa-restore.rules
diff --git a/alsactl/90-alsa-restore.rules.in b/alsactl/90-alsa-restore.rules.in
new file mode 100644
index 0000000..0bcee5b
--- /dev/null
+++ b/alsactl/90-alsa-restore.rules.in
@@ -0,0 +1,2 @@
+ACTION=="add", SUBSYSTEM=="sound", KERNEL=="controlC*", KERNELS=="card*", \
+        RUN+="@sbindir@/alsactl restore $attr{number}"
diff --git a/alsactl/Makefile.am b/alsactl/Makefile.am
index 359f73a..5cfc415 100644
--- a/alsactl/Makefile.am
+++ b/alsactl/Makefile.am
@@ -8,7 +8,52 @@
 EXTRA_DIST=alsactl.1 alsactl_init.xml
 
 alsactl_SOURCES=alsactl.c state.c utils.c init_parse.c
+alsactl_CFLAGS=$(AM_CFLAGS) -DSYS_ASOUNDRC=\"$(ASOUND_STATE_DIR)/asound.state\"
 noinst_HEADERS=alsactl.h list.h init_sysdeps.c init_utils_string.c init_utils_run.c init_sysfs.c
 
+dist_udevrules_DATA = \
+	90-alsa-restore.rules
+
+if HAVE_SYSTEMD
+
+systemdsystemunit_DATA = \
+	alsa-store.service \
+	alsa-restore.service
+
+install-data-hook:
+	$(MKDIR_P) -m 0755 \
+		$(DESTDIR)$(systemdsystemunitdir)/basic.target.wants \
+		$(DESTDIR)$(systemdsystemunitdir)/shutdown.target.wants
+	( cd $(DESTDIR)$(systemdsystemunitdir)/basic.target.wants && \
+	        rm -f alsa-restore.service && \
+		$(LN_S) ../alsa-restore.service alsa-restore.service )
+	( cd $(DESTDIR)$(systemdsystemunitdir)/shutdown.target.wants && \
+	        rm -f alsa-store.service && \
+		$(LN_S) ../alsa-store.service alsa-store.service )
+
+endif
+
+edit = \
+	sed $(SED) -r 's,@sbindir\@,$(sbindir),g' < $< > $@ || rm $@
+
+alsa-store.service: alsa-store.service.in
+	$(edit)
+
+alsa-restore.service: alsa-restore.service.in
+	$(edit)
+
+90-alsa-restore.rules: 90-alsa-restore.rules.in
+	$(edit)
+
+EXTRA_DIST += \
+	alsa-store.service.in \
+	alsa-restore.service.in \
+	90-alsa-restore.rules.in
+
+CLEANFILES = \
+	alsa-store.service \
+	alsa-restore.service \
+	90-alsa-restore.rules
+
 %.7: %.xml
 	xmlto man $?
diff --git a/alsactl/alsa-restore.service.in b/alsactl/alsa-restore.service.in
new file mode 100644
index 0000000..e97d196
--- /dev/null
+++ b/alsactl/alsa-restore.service.in
@@ -0,0 +1,11 @@
+[Unit]
+Description=Restore Sound Card State
+DefaultDependencies=no
+After=sysinit.target
+Before=shutdown.target
+Conflicts=shutdown.target
+
+[Service]
+Type=oneshot
+ExecStart=-@sbindir@/alsactl restore
+StandardOutput=syslog
diff --git a/alsactl/alsa-store.service.in b/alsactl/alsa-store.service.in
new file mode 100644
index 0000000..0e2823c
--- /dev/null
+++ b/alsactl/alsa-store.service.in
@@ -0,0 +1,9 @@
+[Unit]
+Description=Store Sound Card State
+DefaultDependencies=no
+Before=shutdown.target
+
+[Service]
+Type=oneshot
+ExecStart=@sbindir@/alsactl store
+StandardOutput=syslog
diff --git a/alsactl/alsactl.1 b/alsactl/alsactl.1
index eb3cbd1..eb5968c 100644
--- a/alsactl/alsactl.1
+++ b/alsactl/alsactl.1
@@ -43,7 +43,7 @@
 
 .TP
 \fI\-f, \-\-file\fP
-Select the configuration file to use. The default is /etc/asound.state.
+Select the configuration file to use. The default is /var/lib/alsa/asound.state.
 
 .TP
 \fI\-F, \-\-force\fP
@@ -90,7 +90,7 @@
 is used.
 
 .SH FILES
-\fI/etc/asound.state\fP (or whatever file you specify with the
+\fI/var/lib/alsa/asound.state\fP (or whatever file you specify with the
 \fB\-f\fP flag) is used to store current settings for your
 soundcards. The settings include all the usual soundcard mixer
 settings.  More importantly, alsactl is
diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c
index 3b5dfda..c2120bd 100644
--- a/alsactl/alsactl.c
+++ b/alsactl/alsactl.c
@@ -30,7 +30,9 @@
 #include <alsa/asoundlib.h>
 #include "alsactl.h"
 
-#define SYS_ASOUNDRC "/etc/asound.state"
+#ifndef SYS_ASOUNDRC
+#define SYS_ASOUNDRC "/var/lib/alsa/asound.state"
+#endif
 
 int debugflag = 0;
 int force_restore = 1;
diff --git a/alsaloop/Makefile.am b/alsaloop/Makefile.am
new file mode 100644
index 0000000..f76eafd
--- /dev/null
+++ b/alsaloop/Makefile.am
@@ -0,0 +1,14 @@
+INCLUDES = -I$(top_srcdir)/include
+LDADD = -lm
+CFLAGS += -D_GNU_SOURCE
+if HAVE_SAMPLERATE
+LDADD += -lsamplerate
+endif
+# LDFLAGS = -static
+# CFLAGS += -g -Wall
+
+bin_PROGRAMS = alsaloop
+alsaloop_SOURCES = alsaloop.c pcmjob.c control.c
+noinst_HEADERS = alsaloop.h
+man_MANS = alsaloop.1
+EXTRA_DIST = alsaloop.1
diff --git a/alsaloop/alsaloop.1 b/alsaloop/alsaloop.1
new file mode 100644
index 0000000..048d1e0
--- /dev/null
+++ b/alsaloop/alsaloop.1
@@ -0,0 +1,206 @@
+.TH ALSALOOP 1 "5 Aug 2010"
+.SH NAME
+alsaloop \- command-line PCM loopback
+.SH SYNOPSIS
+\fBalsaloop\fP [\fI\-option\fP] [\fIcmd\fP]
+.SH DESCRIPTION
+
+\fBalsaloop\fP allows create a PCM loopback between a PCM capture device
+and a PCM playback device.
+
+\fBalsaloop\fP supports multiple soundcards, adaptive clock synchronization,
+adaptive rate resampling using the samplerate library (if available in
+the system). Also, mixer controls can be redirected from one card to
+another (for example Master and PCM).
+
+.SH OPTIONS
+
+.TP
+\fI\-h\fP | \fI\-\-help\fP
+
+Prints the help information.
+
+.TP
+\fI\-g <file>\fP | \fI\-\-config=<file>\fP
+
+Use given configuration file. The syntax of this file is simple: one line
+contains the command line options for one job. The '#' means comment and
+rest of line is ignored. Example:
+
+  # First line - comment, second line - first job
+  -C hw:1,0 -P hw:0,0 -t 50000 -T 1
+  # Third line - comment, fourth line - second job
+  -C hw:1,1 -P hw:0,1 -t 40000 -T 2
+
+.TP
+\fI\-d\fP | \fI\-\-daemonize\fP
+
+Daemonize the main process and use syslog for messages.
+
+.TP
+\fI\-P <device>\fP | \fI\-\-pdevice=<device>\fP
+
+Use given playback device.
+
+.TP
+\fI\-C <device>\fP | \fI\-\-cdevice=<device>\fP
+
+Use given capture device.
+
+.TP
+\fI\-X <device>\fP | \fI\-\-pctl=<device>\fP
+
+Use given CTL device for playback.
+
+.TP
+\fI\-Y <device>\fP | \fI\-\-cctl=<device>\fP
+
+Use given CTL device for capture.
+
+.TP
+\fI\-l <latency>\fP | \fI\-\-latency=<frames>\fP
+
+Requested latency in frames.
+
+.TP
+\fI\-t <usec>\fP | \fI\-\-tlatency=<usec>\fP
+
+Requested latency in usec (1/1000000sec).
+
+.TP
+\fI\-f <format>\fP | \fI\-\-format=<format>\fP
+
+Format specification (usually S16_LE S32_LE). Use -h to list all formats.
+Default format is S16_LE.
+
+.TP
+\fI\-c <channels>\fP | \fI\-\-channels=<channels>\fP
+
+Channel count specification. Default value is 2.
+
+.TP
+\fI\-c <rate>\fP | \fI\-\-rate=<rate>\fP
+
+Rate specification. Default value is 48000 (Hz).
+
+.TP
+\fI\-n\fP | \fI\-\-resample\fP
+
+Allow rate resampling using alsa-lib.
+
+.TP
+\fI\-A <converter>\fP | \fI\-\-samplerate=<converter>\fP
+
+Use libsamplerate and choose a converter:
+
+  0 or sincbest     - best quality
+  1 or sincmedium   - medium quality
+  2 or sincfastest  - lowest quality
+  3 or zerohold     - hold zero samples
+  4 or linear       - worst quality - linear resampling
+  5 or auto         - choose best method
+
+.TP
+\fI\-B <size>\fP | \fI\-\-buffer=<size>\fP
+
+Buffer size in frames.
+
+.TP
+\fI\-E <size>\fP | \fI\-\-period=<size>\fP
+
+Period size in frames.
+
+.TP
+\fI\-s <secs>\fP | \fI\-\-seconds=<secs>\fP
+
+Duration of loop in seconds.
+
+.TP
+\fI\-b\fP | \fI\-\-nblock\fP
+
+Non-block mode (very early process wakeup). Eats more CPU.
+
+.TP
+\fI\-S <mode>\fP | \fI\-\-sync=<mode>\fP
+
+Sync mode specification for capture to playback stream:
+  0 or none       - do not touch the stream
+  1 or simple     - add or remove samples to keep
+                    both streams synchronized
+  2 or captshift  - use driver for the capture device
+                    (if supported) to compensate
+                    the rate shift
+  3 or playshift  - use driver for the playback device
+                    (if supported) to compensate
+                    the rate shift
+  4 or samplerate - use samplerate library to do rate resampling
+  5 or auto       - automatically selects the best method
+                    in this order: captshift, playshift,
+                    samplerate, simple
+
+.TP
+\fI\-T <num>\fP | \fI\-\-thread=<num>\fP
+
+Thread number (-1 means create a unique thread). All jobs with same
+thread numbers are run within one thread.
+
+.TP
+\fI\-m <mixid>\fP | \fI\-\-mixer=<midid>\fP
+
+Redirect mixer control from the playback card to the capture card. Format of
+\fImixid\fP is SRCID(PLAYBACK)[@DSTID(PLAYBACK)]:
+
+  "name='Master Playback Switch'@name='Another Switch'"
+  "name='PCM Playback Volume'"
+
+Known attributes:
+
+  name      - control ID name
+  index     - control ID index
+  device    - control ID device
+  subdevice - control ID subdevice
+  iface     - control ID interface
+  numid     - control ID numid
+
+.TP
+\fI\-O <ossmixid>\fP | \fI\-\-ossmixer=<midid>\fP
+
+Redirect mixer control from the OSS Mixer emulation layer (capture card)
+to the ALSA layer (capture card). Format of \fIossmixid\fP is
+ALSAID[,INDEX]@OSSID:
+
+  "Master@VOLUME"
+  "PCM,1@ALTPCM"
+
+Known OSS attributes:
+
+  VOLUME, BASS, TREBLE, SYNTH, PCM, SPEAKER, LINE, MIC, CD, IMIX, ALTPCM,
+  RECLEV, IGAIN, OGAIN, LINE1, LINE2, LINE3, DIGITAL1, DIGITAL2, DIGITAL3,
+  PHONEIN, PHONEOUT, VIDEO, RADIO, MONITOR
+
+.TP
+\fI\-v\fP | \fI\-\-verbose\fP
+
+Verbose mode. Use multiple times to increase verbosity.
+
+
+.TP
+\fI\-U\fP | \fI\-\-xrun\fP
+
+Verbose xrun profiling.
+
+.TP
+\fI\-W <timeout>\fP | \fI\-\-wake=<timeout>\fP
+
+Set process wake timeout.
+
+.SH EXAMPLES
+
+.TP
+\fBalsaloop \-C hw:0,0 \-P hw:1,0 \-t 50000\fR
+
+.SH BUGS
+None known.
+.SH AUTHOR
+\fBalsaloop\fP is by Jaroslav Kysela <perex@perex.cz>.
+This document is by Jaroslav Kysela <perex@perex.cz>.
diff --git a/alsaloop/alsaloop.c b/alsaloop/alsaloop.c
new file mode 100644
index 0000000..8710dd1
--- /dev/null
+++ b/alsaloop/alsaloop.c
@@ -0,0 +1,923 @@
+/*
+ *  A simple PCM loopback utility with adaptive sample rate support
+ *
+ *     Author: Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+#include <sys/time.h>
+#include <math.h>
+#include <pthread.h>
+#include <syslog.h>
+#include <sys/signal.h>
+#include "alsaloop.h"
+
+struct loopback_thread {
+	int threaded;
+	pthread_t thread;
+	int exitcode;
+	struct loopback **loopbacks;
+	int loopbacks_count;
+	snd_output_t *output;
+};
+
+int quit = 0;
+int verbose = 0;
+int workarounds = 0;
+int daemonize = 0;
+int use_syslog = 0;
+struct loopback **loopbacks = NULL;
+int loopbacks_count = 0;
+char **my_argv = NULL;
+int my_argc = 0;
+struct loopback_thread *threads;
+int threads_count = 0;
+pthread_t main_job;
+int arg_default_xrun = 0;
+int arg_default_wake = 0;
+
+static void my_exit(struct loopback_thread *thread, int exitcode)
+{
+	int i;
+
+	for (i = 0; i < thread->loopbacks_count; i++)
+		pcmjob_done(thread->loopbacks[i]);
+	if (thread->threaded) {
+		thread->exitcode = exitcode;
+		pthread_exit(0);
+	}
+	exit(exitcode);
+}
+
+static int create_loopback_handle(struct loopback_handle **_handle,
+				  const char *device,
+				  const char *ctldev,
+				  const char *id)
+{
+	char idbuf[1024];
+	struct loopback_handle *handle;
+
+	handle = calloc(1, sizeof(*handle));
+	if (handle == NULL)
+		return -ENOMEM;
+	if (device == NULL)
+		device = "hw:0,0";
+	handle->device = strdup(device);
+	if (handle->device == NULL)
+		return -ENOMEM;
+	if (ctldev) {
+		handle->ctldev = strdup(ctldev);
+		if (handle->ctldev == NULL)
+			return -ENOMEM;
+	} else {
+		handle->ctldev = NULL;
+	}
+	snprintf(idbuf, sizeof(idbuf)-1, "%s %s", id, device);
+	idbuf[sizeof(idbuf)-1] = '\0';
+	handle->id = strdup(idbuf);
+	handle->access = SND_PCM_ACCESS_RW_INTERLEAVED;
+	handle->format = SND_PCM_FORMAT_S16_LE;
+	handle->rate = handle->rate_req = 48000;
+	handle->channels = 2;
+	handle->resample = 0;
+	*_handle = handle;
+	return 0;
+}
+
+static int create_loopback(struct loopback **_handle,
+			   struct loopback_handle *play,
+			   struct loopback_handle *capt,
+			   snd_output_t *output)
+{
+	struct loopback *handle;
+
+	handle = calloc(1, sizeof(*handle));
+	if (handle == NULL)
+		return -ENOMEM;
+	handle->play = play;
+	handle->capt = capt;
+	play->loopback = handle;
+	capt->loopback = handle;
+	handle->latency_req = 0;
+	handle->latency_reqtime = 10000;
+	handle->loop_time = ~0UL;
+	handle->loop_limit = ~0ULL;
+	handle->output = output;
+	handle->state = output;
+#ifdef USE_SAMPLERATE
+	handle->src_enable = 1;
+	handle->src_converter_type = SRC_SINC_BEST_QUALITY;
+#endif
+	*_handle = handle;
+	return 0;
+}
+
+static void set_loop_time(struct loopback *loop, unsigned long loop_time)
+{
+	loop->loop_time = loop_time;
+	loop->loop_limit = loop->capt->rate * loop_time;
+}
+
+static void setscheduler(void)
+{
+	struct sched_param sched_param;
+
+	if (sched_getparam(0, &sched_param) < 0) {
+		logit(LOG_WARNING, "Scheduler getparam failed.\n");
+		return;
+	}
+	sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
+	if (!sched_setscheduler(0, SCHED_RR, &sched_param)) {
+		if (verbose)
+			logit(LOG_WARNING, "Scheduler set to Round Robin with priority %i\n", sched_param.sched_priority);
+		return;
+	}
+	if (verbose)
+		logit(LOG_INFO, "!!!Scheduler set to Round Robin with priority %i FAILED!\n", sched_param.sched_priority);
+}
+
+void help(void)
+{
+	int k;
+	printf(
+"Usage: alsaloop [OPTION]...\n\n"
+"-h,--help      help\n"
+"-g,--config    configuration file (one line = one job specified)\n"
+"-d,--daemonize daemonize the main process and use syslog for errors\n"
+"-P,--pdevice   playback device\n"
+"-C,--cdevice   capture device\n"
+"-X,--pctl      playback ctl device\n"
+"-Y,--cctl      capture ctl device\n"
+"-l,--latency   requested latency in frames\n"
+"-t,--tlatency  requested latency in usec (1/1000000sec)\n"
+"-f,--format    sample format\n"
+"-c,--channels  channels\n"
+"-r,--rate      rate\n"
+"-n,--resample  resample in alsa-lib\n"
+"-A,--samplerate use converter (0=sincbest,1=sincmedium,2=sincfastest,\n"
+"                               3=zerohold,4=linear)\n"
+"-B,--buffer    buffer size in frames\n"
+"-E,--period    period size in frames\n"
+"-s,--seconds   duration of loop in seconds\n"
+"-b,--nblock    non-block mode (very early process wakeup)\n"
+"-S,--sync      sync mode(0=none,1=simple,2=captshift,3=playshift,4=samplerate,\n"
+"                         5=auto)\n"
+"-a,--slave     stream parameters slave mode (0=auto, 1=on, 2=off)\n"
+"-T,--thread    thread number (-1 = create unique)\n"
+"-m,--mixer	redirect mixer, argument is:\n"
+"		    SRC_SLAVE_ID(PLAYBACK)[@DST_SLAVE_ID(CAPTURE)]\n"
+"-O,--ossmixer	rescan and redirect oss mixer, argument is:\n"
+"		    ALSA_ID@OSS_ID  (for example: \"Master@VOLUME\")\n"
+"-e,--effect    apply an effect (bandpass filter sweep)\n"
+"-v,--verbose   verbose mode (more -v means more verbose)\n"
+"-w,--workaround use workaround (serialopen)\n"
+"-U,--xrun      xrun profiling\n"
+"-W,--wake      process wake timeout in ms\n"
+);
+	printf("\nRecognized sample formats are:");
+	for (k = 0; k < SND_PCM_FORMAT_LAST; ++k) {
+		const char *s = snd_pcm_format_name(k);
+		if (s)
+			printf(" %s", s);
+	}
+	printf("\n\n");
+	printf(
+"Tip #1 (usable 500ms latency, good CPU usage, superb xrun prevention):\n"
+"  alsaloop -t 500000\n"
+"Tip #2 (superb 1ms latency, but heavy CPU usage):\n"
+"  alsaloop -t 1000\n"
+);
+}
+
+static long timediff(struct timeval t1, struct timeval t2)
+{
+	signed long l;
+
+	t1.tv_sec -= t2.tv_sec;
+	l = (signed long) t1.tv_usec - (signed long) t2.tv_usec;
+	if (l < 0) {
+		t1.tv_sec--;
+		l = 1000000 + l;
+		l %= 1000000;
+	}
+	return (t1.tv_sec * 1000000) + l;
+}
+
+static void add_loop(struct loopback *loop)
+{
+	loopbacks = realloc(loopbacks, (loopbacks_count + 1) *
+						sizeof(struct loopback *));
+	if (loopbacks == NULL) {
+		logit(LOG_CRIT, "No enough memory\n");
+		exit(EXIT_FAILURE);
+	}
+	loopbacks[loopbacks_count++] = loop;
+}
+
+static int init_mixer_control(struct loopback_control *control,
+			      char *id)
+{
+	int err;
+
+	err = snd_ctl_elem_id_malloc(&control->id);
+	if (err < 0)
+		return err;
+	err = snd_ctl_elem_info_malloc(&control->info);
+	if (err < 0)
+		return err;
+	err = snd_ctl_elem_value_malloc(&control->value);
+	if (err < 0)
+		return err;
+	err = control_parse_id(id, control->id);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int add_mixers(struct loopback *loop,
+		      char **mixers,
+		      int mixers_count)
+{
+	struct loopback_mixer *mixer, *last = NULL;
+	char *str1;
+	int err;
+
+	while (mixers_count > 0) {
+		mixer = calloc(1, sizeof(*mixer));
+		if (mixer == NULL)
+			return -ENOMEM;
+		if (last)
+			last->next = mixer;
+		else
+			loop->controls = mixer;
+		last = mixer;
+		str1 = strchr(*mixers, '@');
+		if (str1)
+			*str1 = '\0';
+		err = init_mixer_control(&mixer->src, *mixers);
+		if (err < 0) {
+			logit(LOG_CRIT, "Wrong mixer control ID syntax '%s'\n", *mixers);
+			return -EINVAL;
+		}
+		err = init_mixer_control(&mixer->dst, str1 ? str1 + 1 : *mixers);
+		if (err < 0) {
+			logit(LOG_CRIT, "Wrong mixer control ID syntax '%s'\n", str1 ? str1 + 1 : *mixers);
+			return -EINVAL;
+		}
+		if (str1)
+			*str1 = '@';
+		mixers++;
+		mixers_count--;
+	}
+	return 0;
+}
+
+static int add_oss_mixers(struct loopback *loop,
+			  char **mixers,
+			  int mixers_count)
+{
+	struct loopback_ossmixer *mixer, *last = NULL;
+	char *str1, *str2;
+
+	while (mixers_count > 0) {
+		mixer = calloc(1, sizeof(*mixer));
+		if (mixer == NULL)
+			return -ENOMEM;
+		if (last)
+			last->next = mixer;
+		else
+			loop->oss_controls = mixer;
+		last = mixer;
+		str1 = strchr(*mixers, ',');
+		if (str1)
+			*str1 = '\0';
+		str2 = strchr(str1 ? str1 + 1 : *mixers, '@');
+		if (str2)
+			*str2 = '\0';
+		mixer->alsa_id = strdup(*mixers);
+		if (str1)
+			mixer->alsa_index = atoi(str1);
+		mixer->oss_id = strdup(str2 ? str2 + 1 : *mixers);
+		if (mixer->alsa_id == NULL || mixer->oss_id == NULL) {
+			logit(LOG_CRIT, "Not enough memory");
+			return -ENOMEM;
+		}
+		if (str1)
+			*str1 = ',';
+		if (str2)
+			*str2 = ',';
+		mixers++;
+		mixers_count--;
+	}
+	return 0;
+}
+
+static int parse_config_file(const char *file, snd_output_t *output);
+
+static int parse_config(int argc, char *argv[], snd_output_t *output,
+			int cmdline)
+{
+	struct option long_option[] =
+	{
+		{"help", 0, NULL, 'h'},
+		{"config", 1, NULL, 'g'},
+		{"daemonize", 0, NULL, 'd'},
+		{"pdevice", 1, NULL, 'P'},
+		{"cdevice", 1, NULL, 'C'},
+		{"pctl", 1, NULL, 'X'},
+		{"cctl", 1, NULL, 'Y'},
+		{"latency", 1, NULL, 'l'},
+		{"tlatency", 1, NULL, 't'},
+		{"format", 1, NULL, 'f'},
+		{"channels", 1, NULL, 'c'},
+		{"rate", 1, NULL, 'r'},
+		{"buffer", 1, NULL, 'B'},
+		{"period", 1, NULL, 'E'},
+		{"seconds", 1, NULL, 's'},
+		{"nblock", 0, NULL, 'b'},
+		{"effect", 0, NULL, 'e'},
+		{"verbose", 0, NULL, 'v'},
+		{"resample", 0, NULL, 'n'},
+		{"samplerate", 1, NULL, 'A'},
+		{"sync", 1, NULL, 'S'},
+		{"slave", 1, NULL, 'a'},
+		{"thread", 1, NULL, 'T'},
+		{"mixer", 1, NULL, 'm'},
+		{"ossmixer", 1, NULL, 'O'},
+		{"workaround", 1, NULL, 'w'},
+		{"xrun", 0, NULL, 'U'},
+		{NULL, 0, NULL, 0},
+	};
+	int err, morehelp;
+	char *arg_config = NULL;
+	char *arg_pdevice = NULL;
+	char *arg_cdevice = NULL;
+	char *arg_pctl = NULL;
+	char *arg_cctl = NULL;
+	unsigned int arg_latency_req = 0;
+	unsigned int arg_latency_reqtime = 10000;
+	snd_pcm_format_t arg_format = SND_PCM_FORMAT_S16_LE;
+	unsigned int arg_channels = 2;
+	unsigned int arg_rate = 48000;
+	snd_pcm_uframes_t arg_buffer_size = 0;
+	snd_pcm_uframes_t arg_period_size = 0;
+	unsigned long arg_loop_time = ~0UL;
+	int arg_nblock = 0;
+	int arg_effect = 0;
+	int arg_resample = 0;
+	int arg_samplerate = SRC_SINC_FASTEST + 1;
+	int arg_sync = SYNC_TYPE_AUTO;
+	int arg_slave = SLAVE_TYPE_AUTO;
+	int arg_thread = 0;
+	struct loopback *loop = NULL;
+	char *arg_mixers[MAX_MIXERS];
+	int arg_mixers_count = 0;
+	char *arg_ossmixers[MAX_MIXERS];
+	int arg_ossmixers_count = 0;
+	int arg_xrun = arg_default_xrun;
+	int arg_wake = arg_default_wake;
+
+	morehelp = 0;
+	while (1) {
+		int c;
+		if ((c = getopt_long(argc, argv,
+				"hdg:P:C:X:Y:l:t:F:f:c:r:s:benvA:S:a:m:T:O:w:UW:",
+				long_option, NULL)) < 0)
+			break;
+		switch (c) {
+		case 'h':
+			morehelp++;
+			break;
+		case 'g':
+			arg_config = strdup(optarg);
+			break;
+		case 'd':
+			daemonize = 1;
+			use_syslog = 1;
+			openlog("alsaloop", LOG_NDELAY|LOG_PID, LOG_DAEMON);
+			break;
+		case 'P':
+			arg_pdevice = strdup(optarg);
+			break;
+		case 'C':
+			arg_cdevice = strdup(optarg);
+			break;
+		case 'X':
+			arg_pctl = strdup(optarg);
+			break;
+		case 'Y':
+			arg_cctl = strdup(optarg);
+			break;
+		case 'l':
+			err = atoi(optarg);
+			arg_latency_req = err >= 4 ? err : 4;
+			break;
+		case 't':
+			err = atoi(optarg);
+			arg_latency_reqtime = err >= 500 ? err : 500;
+			break;
+		case 'f':
+			arg_format = snd_pcm_format_value(optarg);
+			if (arg_format == SND_PCM_FORMAT_UNKNOWN) {
+				logit(LOG_WARNING, "Unknown format, setting to default S16_LE\n");
+				arg_format = SND_PCM_FORMAT_S16_LE;
+			}
+			break;
+		case 'c':
+			err = atoi(optarg);
+			arg_channels = err >= 1 && err < 1024 ? err : 1;
+			break;
+		case 'r':
+			err = atoi(optarg);
+			arg_rate = err >= 4000 && err < 200000 ? err : 44100;
+			break;
+		case 'B':
+			err = atoi(optarg);
+			arg_buffer_size = err >= 32 && err < 200000 ? err : 0;
+			break;
+		case 'E':
+			err = atoi(optarg);
+			arg_period_size = err >= 32 && err < 200000 ? err : 0;
+			break;
+		case 's':
+			err = atoi(optarg);
+			arg_loop_time = err >= 1 && err <= 100000 ? err : 30;
+			break;
+		case 'b':
+			arg_nblock = 1;
+			break;
+		case 'e':
+			arg_effect = 1;
+			break;
+		case 'n':
+			arg_resample = 1;
+			break;
+		case 'A':
+			if (strcasecmp(optarg, "sincbest") == 0)
+				arg_samplerate = SRC_SINC_BEST_QUALITY;
+			else if (strcasecmp(optarg, "sincmedium") == 0)
+				arg_samplerate = SRC_SINC_MEDIUM_QUALITY;
+			else if (strcasecmp(optarg, "sincfastest") == 0)
+				arg_samplerate = SRC_SINC_FASTEST;
+			else if (strcasecmp(optarg, "zerohold") == 0)
+				arg_samplerate = SRC_ZERO_ORDER_HOLD;
+			else if (strcasecmp(optarg, "linear") == 0)
+				arg_samplerate = SRC_LINEAR;
+			else
+				arg_samplerate = atoi(optarg);
+			if (arg_samplerate < 0 || arg_samplerate > SRC_LINEAR)
+				arg_sync = SRC_SINC_FASTEST;
+			arg_samplerate += 1;
+			break;
+		case 'S':
+			if (strcasecmp(optarg, "samplerate") == 0)
+				arg_sync = SYNC_TYPE_SAMPLERATE;
+			else if (optarg[0] == 'n')
+				arg_sync = SYNC_TYPE_NONE;
+			else if (optarg[0] == 's')
+				arg_sync = SYNC_TYPE_SIMPLE;
+			else if (optarg[0] == 'c')
+				arg_sync = SYNC_TYPE_CAPTRATESHIFT;
+			else if (optarg[0] == 'p')
+				arg_sync = SYNC_TYPE_PLAYRATESHIFT;
+			else if (optarg[0] == 'r')
+				arg_sync = SYNC_TYPE_SAMPLERATE;
+			else
+				arg_sync = atoi(optarg);
+			if (arg_sync < 0 || arg_sync > SYNC_TYPE_LAST)
+				arg_sync = SYNC_TYPE_AUTO;
+			break;
+		case 'a':
+			if (optarg[0] == 'a')
+				arg_slave = SLAVE_TYPE_AUTO;
+			else if (strcasecmp(optarg, "on") == 0)
+				arg_slave = SLAVE_TYPE_ON;
+			else if (strcasecmp(optarg, "off") == 0)
+				arg_slave = SLAVE_TYPE_OFF;
+			else
+				arg_slave = atoi(optarg);
+			if (arg_slave < 0 || arg_slave > SLAVE_TYPE_LAST)
+				arg_slave = SLAVE_TYPE_AUTO;
+			break;
+		case 'T':
+			arg_thread = atoi(optarg);
+			if (arg_thread < 0)
+				arg_thread = 10000000 + loopbacks_count;
+			break;
+		case 'm':
+			if (arg_mixers_count >= MAX_MIXERS) {
+				logit(LOG_CRIT, "Maximum redirected mixer controls reached (max %i)\n", (int)MAX_MIXERS);
+				exit(EXIT_FAILURE);
+			}
+			arg_mixers[arg_mixers_count++] = optarg;
+			break;
+		case 'O':
+			if (arg_ossmixers_count >= MAX_MIXERS) {
+				logit(LOG_CRIT, "Maximum redirected mixer controls reached (max %i)\n", (int)MAX_MIXERS);
+				exit(EXIT_FAILURE);
+			}
+			arg_ossmixers[arg_ossmixers_count++] = optarg;
+			break;
+		case 'v':
+			verbose++;
+			break;
+		case 'w':
+			if (strcasecmp(optarg, "serialopen") == 0)
+				workarounds |= WORKAROUND_SERIALOPEN;
+			break;
+		case 'U':
+			arg_xrun = 1;
+			if (cmdline)
+				arg_default_xrun = 1;
+			break;
+		case 'W':
+			arg_wake = atoi(optarg);
+			if (cmdline)
+				arg_default_wake = arg_wake;
+			break;
+		}
+	}
+
+	if (morehelp) {
+		help();
+		exit(EXIT_SUCCESS);
+	}
+	if (arg_config == NULL) {
+		struct loopback_handle *play;
+		struct loopback_handle *capt;
+		err = create_loopback_handle(&play, arg_pdevice, arg_pctl, "playback");
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to create playback handle.\n");
+			exit(EXIT_FAILURE);
+		}
+		err = create_loopback_handle(&capt, arg_cdevice, arg_cctl, "capture");
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to create capture handle.\n");
+			exit(EXIT_FAILURE);
+		}
+		err = create_loopback(&loop, play, capt, output);
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to create loopback handle.\n");
+			exit(EXIT_FAILURE);
+		}
+		play->format = capt->format = arg_format;
+		play->rate = play->rate_req = capt->rate = capt->rate_req = arg_rate;
+		play->channels = capt->channels = arg_channels;
+		play->buffer_size_req = capt->buffer_size_req = arg_buffer_size;
+		play->period_size_req = capt->period_size_req = arg_period_size;
+		play->resample = capt->resample = arg_resample;
+		play->nblock = capt->nblock = arg_nblock ? 1 : 0;
+		loop->latency_req = arg_latency_req;
+		loop->latency_reqtime = arg_latency_reqtime;
+		loop->sync = arg_sync;
+		loop->slave = arg_slave;
+		loop->thread = arg_thread;
+		loop->xrun = arg_xrun;
+		loop->wake = arg_wake;
+		err = add_mixers(loop, arg_mixers, arg_mixers_count);
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to add mixer controls.\n");
+			exit(EXIT_FAILURE);
+		}
+		err = add_oss_mixers(loop, arg_ossmixers, arg_ossmixers_count);
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to add ossmixer controls.\n");
+			exit(EXIT_FAILURE);
+		}
+#ifdef USE_SAMPLERATE
+		loop->src_enable = arg_samplerate > 0;
+		if (loop->src_enable)
+			loop->src_converter_type = arg_samplerate - 1;
+#else
+		if (arg_samplerate > 0) {
+			logit(LOG_CRIT, "No libsamplerate support.\n");
+			exit(EXIT_FAILURE);
+		}
+#endif
+		set_loop_time(loop, arg_loop_time);
+		add_loop(loop);
+		return 0;
+	}
+
+	return parse_config_file(arg_config, output);
+}
+
+static int parse_config_file(const char *file, snd_output_t *output)
+{
+	FILE *fp;
+	char line[2048], word[2048];
+	char *str, *ptr;
+	int argc, c, err = 0;
+	char **argv;
+
+	fp = fopen(file, "r");
+	if (fp == NULL) {
+		logit(LOG_CRIT, "Unable to open file '%s': %s\n", file, strerror(errno));
+		return -EIO;
+	}
+	while (!feof(fp)) {
+		if (fgets(line, sizeof(line)-1, fp) == NULL)
+			break;
+		line[sizeof(line)-1] = '\0';
+		my_argv = realloc(my_argv, my_argc + MAX_ARGS * sizeof(char *));
+		if (my_argv == NULL)
+			return -ENOMEM;
+		argv = my_argv + my_argc;
+		argc = 0;
+		argv[argc++] = strdup("<prog>");
+		my_argc++;
+		str = line;
+		while (*str) {
+			ptr = word;
+			while (*str && (*str == ' ' || *str < ' '))
+				str++;
+			if (*str == '#')
+				goto __next;
+			if (*str == '\'' || *str == '\"') {
+				c = *str++;
+				while (*str && *str != c)
+					*ptr++ = *str++;
+				if (*str == c)
+					str++;
+			} else {
+				while (*str && *str != ' ' && *str != '\t')
+					*ptr++ = *str++;
+			}
+			if (ptr != word) {
+				if (*(ptr-1) == '\n')
+					ptr--;
+				*ptr = '\0';
+				if (argc >= MAX_ARGS) {
+					logit(LOG_CRIT, "Too many arguments.");
+					goto __error;
+				}
+				argv[argc++] = strdup(word);
+				my_argc++;
+			}
+		}
+		/* erase runtime variables for getopt */
+		optarg = NULL;
+		optind = opterr = 1;
+		optopt = '?';
+
+		err = parse_config(argc, argv, output, 0);
+	      __next:
+		if (err < 0)
+			break;
+		err = 0;
+	}
+      __error:
+	fclose(fp);
+
+	return err;
+}
+
+static void thread_job1(void *_data)
+{
+	struct loopback_thread *thread = _data;
+	snd_output_t *output = thread->output;
+	struct pollfd *pfds = NULL;
+	int pfds_count = 0;
+	int i, j, err, wake = 1000000;
+
+	setscheduler();
+
+	for (i = 0; i < thread->loopbacks_count; i++) {
+		err = pcmjob_init(thread->loopbacks[i]);
+		if (err < 0) {
+			logit(LOG_CRIT, "Loopback initialization failure.\n");
+			my_exit(thread, EXIT_FAILURE);
+		}
+	}
+	for (i = 0; i < thread->loopbacks_count; i++) {
+		err = pcmjob_start(thread->loopbacks[i]);
+		if (err < 0) {
+			logit(LOG_CRIT, "Loopback start failure.\n");
+			my_exit(thread, EXIT_FAILURE);
+		}
+		pfds_count += thread->loopbacks[i]->pollfd_count;
+		j = thread->loopbacks[i]->wake;
+		if (j > 0 && j < wake)
+			wake = j;
+	}
+	if (wake >= 1000000)
+		wake = -1;
+	pfds = calloc(pfds_count, sizeof(struct pollfd));
+	if (pfds == NULL || pfds_count <= 0) {
+		logit(LOG_CRIT, "Poll FDs allocation failed.\n");
+		my_exit(thread, EXIT_FAILURE);
+	}
+	while (!quit) {
+		struct timeval tv1, tv2;
+		for (i = j = 0; i < thread->loopbacks_count; i++) {
+			err = pcmjob_pollfds_init(thread->loopbacks[i], &pfds[j]);
+			if (err < 0) {
+				logit(LOG_CRIT, "Poll FD initialization failed.\n");
+				my_exit(thread, EXIT_FAILURE);
+			}
+			j += err;
+		}
+		if (verbose > 10)
+			gettimeofday(&tv1, NULL);
+		err = poll(pfds, j, wake);
+		if (err < 0)
+			err = -errno;
+		if (verbose > 10) {
+			gettimeofday(&tv2, NULL);
+			snd_output_printf(output, "pool took %lius\n", timediff(tv2, tv1));
+		}
+		if (err < 0) {
+			if (err == -EINTR || err == -ERESTART)
+				continue;
+			logit(LOG_CRIT, "Poll failed: %s\n", strerror(-err));
+			my_exit(thread, EXIT_FAILURE);
+		}
+		for (i = j = 0; i < thread->loopbacks_count; i++) {
+			struct loopback *loop = thread->loopbacks[i];
+			if (j < loop->active_pollfd_count) {
+				err = pcmjob_pollfds_handle(loop, &pfds[j]);
+				if (err < 0) {
+					logit(LOG_CRIT, "pcmjob failed.\n");
+					exit(EXIT_FAILURE);
+				}
+			}
+			j += loop->active_pollfd_count;
+		}
+	}
+
+	my_exit(thread, EXIT_SUCCESS);
+}
+
+static void thread_job(struct loopback_thread *thread)
+{
+	if (!thread->threaded) {
+		thread_job1(thread);
+		return;
+	}
+	pthread_create(&thread->thread, NULL, (void *) &thread_job1,
+					      (void *) thread);
+}
+
+static void send_to_all(int sig)
+{
+	struct loopback_thread *thread;
+	int i;
+
+	for (i = 0; i < threads_count; i++) {
+		thread = &threads[i];
+		if (thread->threaded)
+			pthread_kill(thread->thread, sig);
+	}
+}
+
+static void signal_handler(int sig)
+{
+	quit = 1;
+	send_to_all(SIGUSR2);
+}
+
+static void signal_handler_state(int sig)
+{
+	pthread_t self = pthread_self();
+	struct loopback_thread *thread;
+	int i, j;
+
+	if (pthread_equal(main_job, self))
+		send_to_all(SIGUSR1);
+	for (i = 0; i < threads_count; i++) {
+		thread = &threads[i];
+		if (thread->thread == self) {
+			for (j = 0; j < thread->loopbacks_count; j++)
+				pcmjob_state(thread->loopbacks[j]);
+		}
+	}
+	signal(sig, signal_handler_state);
+}
+
+static void signal_handler_ignore(int sig)
+{
+	signal(sig, signal_handler_ignore);
+}
+
+int main(int argc, char *argv[])
+{
+	snd_output_t *output;
+	int i, j, k, l, err;
+
+	err = snd_output_stdio_attach(&output, stdout, 0);
+	if (err < 0) {
+		logit(LOG_CRIT, "Output failed: %s\n", snd_strerror(err));
+		exit(EXIT_FAILURE);
+	}
+	err = parse_config(argc, argv, output, 1);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to parse arguments or configuration...\n");
+		exit(EXIT_FAILURE);
+	}
+	while (my_argc > 0)
+		free(my_argv[--my_argc]);
+	free(my_argv);
+
+	if (loopbacks_count <= 0) {
+		logit(LOG_CRIT, "No loopback defined...\n");
+		exit(EXIT_FAILURE);
+	}
+
+	if (daemonize) {
+		if (daemon(0, 0) < 0) {
+			logit(LOG_CRIT, "daemon() failed: %s\n", strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+		i = fork();
+		if (i < 0) {
+			logit(LOG_CRIT, "fork() failed: %s\n", strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+		if (i > 0) {
+			/* wait(&i); */
+			exit(EXIT_SUCCESS);
+		}
+	}
+
+	/* we must sort thread IDs */
+	j = -1;
+	do {
+		k = 0x7fffffff;
+		for (i = 0; i < loopbacks_count; i++) {
+			if (loopbacks[i]->thread < k &&
+			    loopbacks[i]->thread > j)
+				k = loopbacks[i]->thread;
+		}
+		j++;
+		for (i = 0; i < loopbacks_count; i++) {
+			if (loopbacks[i]->thread == k)
+				loopbacks[i]->thread = j;
+		}
+	} while (k != 0x7fffffff);
+	/* fix maximum thread id */
+	for (i = 0, j = -1; i < loopbacks_count; i++) {
+		if (loopbacks[i]->thread > j)
+			j = loopbacks[i]->thread;
+	}
+	j += 1;
+	threads = calloc(1, sizeof(struct loopback_thread) * j);
+	if (threads == NULL) {
+		logit(LOG_CRIT, "No enough memory\n");
+		exit(EXIT_FAILURE);
+	}
+	/* sort all threads */
+	for (k = 0; k < j; k++) {
+		for (i = l = 0; i < loopbacks_count; i++)
+			if (loopbacks[i]->thread == k)
+				l++;
+		threads[k].loopbacks = malloc(l * sizeof(struct loopback *));
+		threads[k].loopbacks_count = l;
+		threads[k].output = output;
+		threads[k].threaded = j > 1;
+		for (i = l = 0; i < loopbacks_count; i++)
+			if (loopbacks[i]->thread == k)
+				threads[k].loopbacks[l++] = loopbacks[i];
+	}
+	threads_count = j;
+	main_job = pthread_self();
+ 
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+	signal(SIGABRT, signal_handler);
+	signal(SIGUSR1, signal_handler_state);
+	signal(SIGUSR2, signal_handler_ignore);
+
+	for (k = 0; k < threads_count; k++)
+		thread_job(&threads[k]);
+
+	if (j > 1) {
+		for (k = 0; k < threads_count; k++)
+			pthread_join(threads[k].thread, NULL);
+	}
+
+	if (use_syslog)
+		closelog();
+	exit(EXIT_SUCCESS);
+}
diff --git a/alsaloop/alsaloop.h b/alsaloop/alsaloop.h
new file mode 100644
index 0000000..8dc445a
--- /dev/null
+++ b/alsaloop/alsaloop.h
@@ -0,0 +1,218 @@
+/*
+ *  A simple PCM loopback utility
+ *  Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include "aconfig.h"
+#ifdef HAVE_SAMPLERATE_H
+#define USE_SAMPLERATE
+#include <samplerate.h>
+#else
+enum {
+	SRC_SINC_BEST_QUALITY	= 0,
+	SRC_SINC_MEDIUM_QUALITY	= 1,
+	SRC_SINC_FASTEST	= 2,
+	SRC_ZERO_ORDER_HOLD	= 3,
+	SRC_LINEAR		= 4
+};
+#endif
+
+#define MAX_ARGS	128
+#define MAX_MIXERS	64
+
+#if 0
+#define FILE_PWRITE "/tmp/alsaloop.praw"
+#define FILE_CWRITE "/tmp/alsaloop.craw"
+#endif
+
+#define WORKAROUND_SERIALOPEN	(1<<0)
+
+typedef enum _sync_type {
+	SYNC_TYPE_NONE = 0,
+	SYNC_TYPE_SIMPLE,	/* add or remove samples */
+	SYNC_TYPE_CAPTRATESHIFT,
+	SYNC_TYPE_PLAYRATESHIFT,
+	SYNC_TYPE_SAMPLERATE,
+	SYNC_TYPE_AUTO,		/* order: CAPTRATESHIFT, PLAYRATESHIFT, */
+				/*        SAMPLERATE, SIMPLE */
+	SYNC_TYPE_LAST = SYNC_TYPE_AUTO
+} sync_type_t;
+
+typedef enum _slave_type {
+	SLAVE_TYPE_AUTO = 0,
+	SLAVE_TYPE_ON = 1,
+	SLAVE_TYPE_OFF = 2,
+	SLAVE_TYPE_LAST = SLAVE_TYPE_OFF
+} slave_type_t;
+
+struct loopback_control {
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_value_t *value;
+};
+
+struct loopback_mixer {
+	unsigned int skip:1;
+	struct loopback_control src;
+	struct loopback_control dst;
+	struct loopback_mixer *next;
+};
+
+struct loopback_ossmixer {
+	unsigned int skip:1;
+	const char *alsa_id;
+	int alsa_index;
+	const char *oss_id;
+	struct loopback_ossmixer *next;
+};
+
+struct loopback_handle {
+	struct loopback *loopback;
+	char *device;
+	char *ctldev;
+	char *id;
+	int card_number;
+	snd_pcm_t *handle;
+	snd_pcm_access_t access;
+	snd_pcm_format_t format;
+	unsigned int rate;
+	unsigned int rate_req;
+	unsigned int channels;
+	unsigned int buffer_size;
+	unsigned int period_size;
+	snd_pcm_uframes_t avail_min;
+	unsigned int buffer_size_req;
+	unsigned int period_size_req;
+	unsigned int frame_size;
+	unsigned int resample:1;	/* do resample */
+	unsigned int nblock:1;		/* do block (period size) transfers */
+	unsigned int xrun_pending:1;
+	unsigned int pollfd_count;
+	/* I/O job */
+	char *buf;			/* I/O buffer */
+	snd_pcm_uframes_t buf_pos;	/* I/O position */
+	snd_pcm_uframes_t buf_count;	/* filled samples */
+	snd_pcm_uframes_t buf_size;	/* buffer size in frames */
+	snd_pcm_uframes_t buf_over;	/* capture buffer overflow */
+	/* statistics */
+	snd_pcm_uframes_t max;
+	unsigned long long counter;
+	unsigned long sync_point;	/* in samples */
+	snd_pcm_sframes_t last_delay;
+	double pitch;
+	snd_pcm_uframes_t total_queued;
+	/* control */
+	snd_ctl_t *ctl;
+	unsigned int ctl_pollfd_count;
+	snd_ctl_elem_value_t *ctl_notify;
+	snd_ctl_elem_value_t *ctl_rate_shift;
+	snd_ctl_elem_value_t *ctl_active;
+	snd_ctl_elem_value_t *ctl_format;
+	snd_ctl_elem_value_t *ctl_rate;
+	snd_ctl_elem_value_t *ctl_channels;
+};
+
+struct loopback {
+	char *id;
+	struct loopback_handle *capt;
+	struct loopback_handle *play;
+	snd_pcm_uframes_t latency;	/* final latency in frames */
+	unsigned int latency_req;	/* in frames */
+	unsigned int latency_reqtime;	/* in us */
+	unsigned long loop_time;	/* ~0 = unlimited (in seconds) */
+	unsigned long long loop_limit;	/* ~0 = unlimited (in frames) */
+	snd_output_t *output;
+	snd_output_t *state;
+	int pollfd_count;
+	int active_pollfd_count;
+	unsigned int linked:1;		/* linked streams */
+	unsigned int reinit:1;
+	unsigned int running:1;
+	unsigned int stop_pending:1;
+	snd_pcm_uframes_t stop_count;
+	sync_type_t sync;		/* type of sync */
+	slave_type_t slave;
+	int thread;			/* thread number */
+	unsigned int wake;
+	/* statistics */
+	double pitch;
+	double pitch_delta;
+	snd_pcm_sframes_t pitch_diff;
+	snd_pcm_sframes_t pitch_diff_min;
+	snd_pcm_sframes_t pitch_diff_max;
+	unsigned int total_queued_count;
+	snd_timestamp_t tstamp_start;
+	snd_timestamp_t tstamp_end;
+	/* xrun profiling */
+	unsigned int xrun:1;		/* xrun profiling */
+	snd_timestamp_t xrun_last_update;
+	snd_timestamp_t xrun_last_wake0;
+	snd_timestamp_t xrun_last_wake;
+	snd_timestamp_t xrun_last_check0;
+	snd_timestamp_t xrun_last_check;
+	snd_pcm_sframes_t xrun_last_pdelay;
+	snd_pcm_sframes_t xrun_last_cdelay;
+	snd_pcm_uframes_t xrun_buf_pcount;
+	snd_pcm_uframes_t xrun_buf_ccount;
+	unsigned int xrun_out_frames;
+	long xrun_max_proctime;
+	double xrun_max_missing;
+	/* control mixer */
+	struct loopback_mixer *controls;
+	struct loopback_ossmixer *oss_controls;
+	/* sample rate */
+	unsigned int use_samplerate:1;
+#ifdef USE_SAMPLERATE
+	unsigned int src_enable:1;
+	int src_converter_type;
+	SRC_STATE *src_state;
+	SRC_DATA src_data;
+	unsigned int src_out_frames;
+#endif
+#ifdef FILE_CWRITE
+	FILE *cfile;
+#endif
+#ifdef FILE_PWRITE
+	FILE *pfile;
+#endif
+};
+
+extern int verbose;
+extern int workarounds;
+extern int use_syslog;
+
+#define logit(priority, fmt, args...) do {		\
+	if (use_syslog)					\
+		syslog(priority, fmt, ##args);		\
+	else						\
+		fprintf(stderr, fmt, ##args);		\
+} while (0)
+
+int pcmjob_init(struct loopback *loop);
+int pcmjob_done(struct loopback *loop);
+int pcmjob_start(struct loopback *loop);
+int pcmjob_stop(struct loopback *loop);
+int pcmjob_pollfds_init(struct loopback *loop, struct pollfd *fds);
+int pcmjob_pollfds_handle(struct loopback *loop, struct pollfd *fds);
+void pcmjob_state(struct loopback *loop);
+
+int control_parse_id(const char *str, snd_ctl_elem_id_t *id);
+int control_id_match(snd_ctl_elem_id_t *id1, snd_ctl_elem_id_t *id2);
+int control_init(struct loopback *loop);
+int control_done(struct loopback *loop);
+int control_event(struct loopback_handle *lhandle, snd_ctl_event_t *ev);
diff --git a/alsaloop/control.c b/alsaloop/control.c
new file mode 100644
index 0000000..8383d79
--- /dev/null
+++ b/alsaloop/control.c
@@ -0,0 +1,424 @@
+/*
+ *  A simple PCM loopback utility
+ *  Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *     Author: Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <ctype.h>
+#include <syslog.h>
+#include <alsa/asoundlib.h>
+#include "alsaloop.h"
+
+static char *id_str(snd_ctl_elem_id_t *id)
+{
+	static char str[128];
+
+	sprintf(str, "%i,%s,%i,%i,%s,%i",
+		snd_ctl_elem_id_get_numid(id),
+		snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id)),
+		snd_ctl_elem_id_get_device(id),
+		snd_ctl_elem_id_get_subdevice(id),
+		snd_ctl_elem_id_get_name(id),
+		snd_ctl_elem_id_get_index(id));
+	return str;
+}
+
+int control_parse_id(const char *str, snd_ctl_elem_id_t *id)
+{
+	int c, size, numid;
+	char *ptr;
+
+	while (*str == ' ' || *str == '\t')
+		str++;
+	if (!(*str))
+		return -EINVAL;
+	snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);	/* default */
+	while (*str) {
+		if (!strncasecmp(str, "numid=", 6)) {
+			str += 6;
+			numid = atoi(str);
+			if (numid <= 0) {
+				logit(LOG_CRIT, "Invalid numid %d\n", numid);
+				return -EINVAL;
+			}
+			snd_ctl_elem_id_set_numid(id, atoi(str));
+			while (isdigit(*str))
+				str++;
+		} else if (!strncasecmp(str, "iface=", 6)) {
+			str += 6;
+			if (!strncasecmp(str, "card", 4)) {
+				snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
+				str += 4;
+			} else if (!strncasecmp(str, "mixer", 5)) {
+				snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+				str += 5;
+			} else if (!strncasecmp(str, "pcm", 3)) {
+				snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM);
+				str += 3;
+			} else if (!strncasecmp(str, "rawmidi", 7)) {
+				snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_RAWMIDI);
+				str += 7;
+			} else if (!strncasecmp(str, "timer", 5)) {
+				snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_TIMER);
+				str += 5;
+			} else if (!strncasecmp(str, "sequencer", 9)) {
+				snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_SEQUENCER);
+				str += 9;
+			} else {
+				return -EINVAL;
+			}
+		} else if (!strncasecmp(str, "name=", 5)) {
+			char buf[64];
+			str += 5;
+			ptr = buf;
+			size = 0;
+			if (*str == '\'' || *str == '\"') {
+				c = *str++;
+				while (*str && *str != c) {
+					if (size < (int)sizeof(buf)) {
+						*ptr++ = *str;
+						size++;
+					}
+					str++;
+				}
+				if (*str == c)
+					str++;
+			} else {
+				while (*str && *str != ',') {
+					if (size < (int)sizeof(buf)) {
+						*ptr++ = *str;
+						size++;
+					}
+					str++;
+				}
+			}
+			*ptr = '\0';
+			snd_ctl_elem_id_set_name(id, buf);
+		} else if (!strncasecmp(str, "index=", 6)) {
+			str += 6;
+			snd_ctl_elem_id_set_index(id, atoi(str));
+			while (isdigit(*str))
+				str++;
+		} else if (!strncasecmp(str, "device=", 7)) {
+			str += 7;
+			snd_ctl_elem_id_set_device(id, atoi(str));
+			while (isdigit(*str))
+				str++;
+		} else if (!strncasecmp(str, "subdevice=", 10)) {
+			str += 10;
+			snd_ctl_elem_id_set_subdevice(id, atoi(str));
+			while (isdigit(*str))
+				str++;
+		}
+		if (*str == ',') {
+			str++;
+		} else {
+			if (*str)
+				return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+int control_id_match(snd_ctl_elem_id_t *id1, snd_ctl_elem_id_t *id2)
+{
+	if (snd_ctl_elem_id_get_interface(id1) !=
+	    snd_ctl_elem_id_get_interface(id2))
+		return 0;
+	if (snd_ctl_elem_id_get_device(id1) !=
+	    snd_ctl_elem_id_get_device(id2))
+		return 0;
+	if (snd_ctl_elem_id_get_subdevice(id1) !=
+	    snd_ctl_elem_id_get_subdevice(id2))
+		return 0;
+	if (strcmp(snd_ctl_elem_id_get_name(id1),
+		   snd_ctl_elem_id_get_name(id2)) != 0)
+		return 0;
+	if (snd_ctl_elem_id_get_index(id1) !=
+	    snd_ctl_elem_id_get_index(id2))
+		return 0;
+	return 1;
+}
+
+static int control_init1(struct loopback_handle *lhandle,
+			 struct loopback_control *ctl)
+{
+	int err;
+
+	snd_ctl_elem_info_set_id(ctl->info, ctl->id);
+	snd_ctl_elem_value_set_id(ctl->value, ctl->id);
+	if (lhandle->ctl == NULL) {
+		logit(LOG_WARNING, "Unable to read control info for '%s'\n", id_str(ctl->id));
+		return -EIO;
+	}
+	err = snd_ctl_elem_info(lhandle->ctl, ctl->info);
+	if (err < 0) {
+		logit(LOG_WARNING, "Unable to read control info '%s': %s\n", id_str(ctl->id), snd_strerror(err));
+		return err;
+	}
+	err = snd_ctl_elem_read(lhandle->ctl, ctl->value);
+	if (err < 0) {
+		logit(LOG_WARNING, "Unable to read control value (init1) '%s': %s\n", id_str(ctl->id), snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+static int copy_value(struct loopback_control *dst,
+		      struct loopback_control *src)
+{
+	snd_ctl_elem_type_t type;
+	unsigned int count;
+	int i;
+
+	type = snd_ctl_elem_info_get_type(dst->info);
+	count = snd_ctl_elem_info_get_count(dst->info);
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		for (i = 0; i < count; i++)
+			snd_ctl_elem_value_set_boolean(dst->value,
+				i, snd_ctl_elem_value_get_boolean(src->value, i));
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		for (i = 0; i < count; i++) {
+			snd_ctl_elem_value_set_integer(dst->value,
+				i, snd_ctl_elem_value_get_integer(src->value, i));
+		}
+		break;
+	default:
+		logit(LOG_CRIT, "Unable to copy control value for type %s\n", snd_ctl_elem_type_name(type));
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int oss_set(struct loopback *loop,
+		   struct loopback_ossmixer *ossmix,
+		   int enable)
+{
+	char buf[128], file[128];
+	int fd;
+
+	if (loop->capt->card_number < 0)
+		return 0;
+	if (!enable) {
+		sprintf(buf, "%s \"\" 0\n", ossmix->oss_id);
+	} else {
+		sprintf(buf, "%s \"%s\" %i\n", ossmix->oss_id, ossmix->alsa_id, ossmix->alsa_index);
+	}
+	sprintf(file, "/proc/asound/card%i/oss_mixer", loop->capt->card_number);
+	if (verbose)
+		snd_output_printf(loop->output, "%s: Initialize OSS volume %s: %s", loop->id, file, buf);
+	fd = open(file, O_WRONLY);
+	if (fd >= 0 && write(fd, buf, strlen(buf)) == strlen(buf)) {
+		close(fd);
+		return 0;
+	}
+	if (fd >= 0)
+		close(fd);
+	logit(LOG_INFO, "%s: Unable to initialize OSS Mixer ID '%s'\n", loop->id, ossmix->oss_id);
+	return -1;
+}
+
+static int control_init2(struct loopback *loop,
+			 struct loopback_mixer *mix)
+{
+	snd_ctl_elem_type_t type;
+	unsigned int count;
+	int err;
+
+	snd_ctl_elem_info_copy(mix->dst.info, mix->src.info);
+	snd_ctl_elem_info_set_id(mix->dst.info, mix->dst.id);
+	snd_ctl_elem_value_clear(mix->dst.value);
+	snd_ctl_elem_value_set_id(mix->dst.value, mix->dst.id);
+	type = snd_ctl_elem_info_get_type(mix->dst.info);
+	count = snd_ctl_elem_info_get_count(mix->dst.info);
+	snd_ctl_elem_remove(loop->capt->ctl, mix->dst.id);
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		err = snd_ctl_elem_add_boolean(loop->capt->ctl,
+					       mix->dst.id, count);
+		copy_value(&mix->dst, &mix->src);
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		err = snd_ctl_elem_add_integer(loop->capt->ctl,
+				mix->dst.id, count,
+				snd_ctl_elem_info_get_min(mix->dst.info),
+				snd_ctl_elem_info_get_max(mix->dst.info),
+				snd_ctl_elem_info_get_step(mix->dst.info));
+		copy_value(&mix->dst, &mix->src);
+		break;
+	default:
+		logit(LOG_CRIT, "Unable to handle control type %s\n", snd_ctl_elem_type_name(type));
+		err = -EINVAL;
+		break;
+	}
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to create control '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+		return err;
+	}
+	err = snd_ctl_elem_unlock(loop->capt->ctl, mix->dst.id);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to unlock control info '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+		return err;
+	}
+	err = snd_ctl_elem_info(loop->capt->ctl, mix->dst.info);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to read control info '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+		return err;
+	}
+	if (snd_ctl_elem_info_is_tlv_writable(mix->dst.info)) {
+		unsigned int tlv[64];
+		err = snd_ctl_elem_tlv_read(loop->play->ctl,
+					    mix->src.id,
+					    tlv, sizeof(tlv));
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to read TLV for '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
+			tlv[0] = tlv[1] = 0;
+		}
+		err = snd_ctl_elem_tlv_write(loop->capt->ctl,
+					     mix->dst.id,
+					     tlv);
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to write TLV for '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
+			return err;
+		}
+	}
+	err = snd_ctl_elem_write(loop->capt->ctl, mix->dst.value);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to write control value '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+int control_init(struct loopback *loop)
+{
+	struct loopback_mixer *mix;
+	struct loopback_ossmixer *ossmix;
+	int err;
+
+	for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next)
+		oss_set(loop, ossmix, 0);
+	for (mix = loop->controls; mix; mix = mix->next) {
+		err = control_init1(loop->play, &mix->src);
+		if (err < 0) {
+			logit(LOG_WARNING, "%s: Disabling playback control '%s'\n", loop->id, id_str(mix->src.id));
+			mix->skip = 1;
+			continue;
+		}
+		err = control_init2(loop, mix);
+		if (err < 0)
+			return err;
+	}
+	for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next) {
+		err = oss_set(loop, ossmix, 1);
+		if (err < 0) {
+			ossmix->skip = 1;
+			logit(LOG_WARNING, "%s: Disabling OSS mixer ID '%s'\n", loop->id, ossmix->oss_id);
+		}
+	}
+	return 0;
+}
+
+int control_done(struct loopback *loop)
+{
+	struct loopback_mixer *mix;
+	struct loopback_ossmixer *ossmix;
+	int err;
+
+	if (loop->capt->ctl == NULL)
+		return 0;
+	for (ossmix = loop->oss_controls; ossmix; ossmix = ossmix->next) {
+		err = oss_set(loop, ossmix, 0);
+		if (err < 0)
+			logit(LOG_WARNING, "%s: Unable to remove OSS control '%s'\n", loop->id, ossmix->oss_id);
+	}
+	for (mix = loop->controls; mix; mix = mix->next) {
+		if (mix->skip)
+			continue;
+		err = snd_ctl_elem_remove(loop->capt->ctl, mix->dst.id);
+		if (err < 0)
+			logit(LOG_WARNING, "%s: Unable to remove control '%s': %s\n", loop->id, id_str(mix->dst.id), snd_strerror(err));
+	}
+	return 0;
+}
+
+static int control_event1(struct loopback *loop,
+			  struct loopback_mixer *mix,
+			  snd_ctl_event_t *ev,
+			  int capture)
+{
+	unsigned int mask = snd_ctl_event_elem_get_mask(ev);
+	int err;
+
+	if (mask == SND_CTL_EVENT_MASK_REMOVE)
+		return 0;
+	if ((mask & SND_CTL_EVENT_MASK_VALUE) == 0)
+		return 0;
+	if (!capture) {
+		snd_ctl_elem_value_set_id(mix->src.value, mix->src.id);
+		err = snd_ctl_elem_read(loop->play->ctl, mix->src.value);
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to read control value (event1) '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
+			return err;
+		}
+		copy_value(&mix->dst, &mix->src);
+		err = snd_ctl_elem_write(loop->capt->ctl, mix->dst.value);
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to write control value (event1) '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+			return err;
+		}
+	} else {
+		err = snd_ctl_elem_read(loop->capt->ctl, mix->dst.value);
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to read control value (event2) '%s': %s\n", id_str(mix->dst.id), snd_strerror(err));
+			return err;
+		}
+		copy_value(&mix->src, &mix->dst);
+		err = snd_ctl_elem_write(loop->play->ctl, mix->src.value);
+		if (err < 0) {
+			logit(LOG_CRIT, "Unable to write control value (event2) '%s': %s\n", id_str(mix->src.id), snd_strerror(err));
+			return err;
+		}
+	}
+	return 0;
+}
+
+int control_event(struct loopback_handle *lhandle, snd_ctl_event_t *ev)
+{
+	snd_ctl_elem_id_t *id2;
+	struct loopback_mixer *mix;
+	int capt = lhandle == lhandle->loopback->capt;
+	int err;
+
+	snd_ctl_elem_id_alloca(&id2);
+	snd_ctl_event_elem_get_id(ev, id2);
+	for (mix = lhandle->loopback->controls; mix; mix = mix->next) {
+		if (mix->skip)
+			continue;
+		if (control_id_match(id2, capt ? mix->dst.id : mix->src.id)) {
+			err = control_event1(lhandle->loopback, mix, ev, capt);
+			if (err < 0)
+				return err;
+		}
+	}
+	return 0;
+}
diff --git a/alsaloop/effect-sweep.c b/alsaloop/effect-sweep.c
new file mode 100644
index 0000000..4a0903d
--- /dev/null
+++ b/alsaloop/effect-sweep.c
@@ -0,0 +1,128 @@
+/*
+ *  Bandpass filter sweep effect
+ *  Copyright (c) Maarten de Boer <mdeboer@iua.upf.es>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <math.h>
+#include <alsa/asoundlib.h>
+
+struct effect_private {
+	/* filter the sweep variables */
+	float lfo,dlfo,fs,fc,BW,C,D,a0,a1,a2,b1,b2,*x[3],*y[3];
+	float lfo_depth, lfo_center;
+	unsigned int channels;
+};
+
+static int effect_init(struct lookback *loopback,
+		       void *private_data,
+		       snd_pcm_access_t access,
+		       unsigned int channels,
+		       unsigned int rate,
+		       snd_pcm_format_t format)
+{
+	struct effect_private *priv = private_data;
+	int i;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+	if (format != SND_PCM_FORMAT_S16_LE)
+		return -EIO;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+	if (format != SND_PCM_FORMAT_S16_BE)
+		return -EIO;
+#else
+	return -EIO;
+#endif
+	priv->fs = (float) rate;
+	priv->channels = channels;
+	for (i = 0; i < 3; i++) {
+		priv->x[i] = calloc(channels * sizeof(float));
+		priv->y[i] = calloc(channels * sizeof(float));
+	}
+	return 0;
+}
+
+static int effect_done(struct loopback *loopback,
+		       void *private_data)
+{
+	struct effect_private *priv = private_data;
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		free(priv->x[i]);
+		free(priv->y[i]);
+	}
+	return 0;
+}
+
+static int effect_apply(struct loopback *loopback,
+			void *private_data,
+			const snd_pcm_channel_area_t *areas,
+			snd_uframes_t offset,
+			snd_uframes_t frames)
+{
+	struct effect_private *priv = private_data;
+	short *samples = (short*)areas[0].addr + offset*priv->channels;
+	snd_uframes_t i;
+
+	for (i=0; i < frames; i++) {
+		int chn;
+
+		fc = sin(priv->lfo)*priv->lfo_depth+priv->lfo_center;
+		priv->lfo += priv->dlfo;
+		if (priv->lfo>2.*M_PI) priv->lfo -= 2.*M_PI;
+		priv->C = 1./tan(M_PI*priv->BW/priv->fs);
+		priv->D = 2.*cos(2*M_PI*fc/fs);
+		priv->a0 = 1./(1.+priv->C);
+		priv->a1 = 0;
+		priv->a2 = -priv->a0;
+		priv->b1 = -priv->C*priv->D*a0;
+		priv->b2 = (priv->C-1)*priv->a0;
+
+		for (chn=0; chn < priv->channels; chn++)
+		{
+			priv->x[chn][2] = priv->x[chn][1];
+			priv->x[chn][1] = priv->x[chn][0];
+
+			priv->y[chn][2] = priv->y[chn][1];
+			priv->y[chn][1] = priv->y[chn][0];
+
+			priv->x[chn][0] = samples[i*channels+chn];
+			priv->y[chn][0] = priv->a0*priv->x[0][chn]
+				+ priv->a1*priv->x[1][chn] + priv->a2*x[2][chn]
+				- priv->b1*priv->y[1][chn] - priv->b2*y[2][chn];
+			samples[i*channels+chn] = priv->y[chn][0];
+		}
+	}
+	return 0;
+}
+
+void effect_init_sweep(void)
+{
+	struct effect_private *priv;
+
+	priv = register_effect(effect_init,
+			       effect_apply,
+			       effect_done,
+			       sizeof(struct effectprivate));
+	if (priv) {
+		priv->lfo_center = 2000.;
+		priv->lfo_depth = 1800.;
+		priv->lfo_freq = 0.2;
+		priv->BW = 50;
+	}
+}
diff --git a/alsaloop/pcmjob.c b/alsaloop/pcmjob.c
new file mode 100644
index 0000000..0b84803
--- /dev/null
+++ b/alsaloop/pcmjob.c
@@ -0,0 +1,1925 @@
+/*
+ *  A simple PCM loopback utility
+ *  Copyright (c) 2010 by Jaroslav Kysela <perex@perex.cz>
+ *
+ *     Author: Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+#include <sys/time.h>
+#include <math.h>
+#include <syslog.h>
+#include <pthread.h>
+#include "alsaloop.h"
+
+#define XRUN_PROFILE_UNKNOWN (-10000000)
+
+static int set_rate_shift(struct loopback_handle *lhandle, double pitch);
+static int get_rate(struct loopback_handle *lhandle);
+
+#define SYNCTYPE(v) [SYNC_TYPE_##v] = #v
+
+static const char *sync_types[] = {
+	SYNCTYPE(NONE),
+	SYNCTYPE(SIMPLE),
+	SYNCTYPE(CAPTRATESHIFT),
+	SYNCTYPE(PLAYRATESHIFT),
+	SYNCTYPE(SAMPLERATE),
+	SYNCTYPE(AUTO)
+};
+
+#define SRCTYPE(v) [SRC_##v] = "SRC_" #v
+
+#ifdef USE_SAMPLERATE
+static const char *src_types[] = {
+	SRCTYPE(SINC_BEST_QUALITY),
+	SRCTYPE(SINC_MEDIUM_QUALITY),
+	SRCTYPE(SINC_FASTEST),
+	SRCTYPE(ZERO_ORDER_HOLD),
+	SRCTYPE(LINEAR)
+};
+#endif
+
+static pthread_mutex_t pcm_open_mutex =
+                                PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+
+static inline void pcm_open_lock(void)
+{
+	if (workarounds & WORKAROUND_SERIALOPEN)
+	        pthread_mutex_lock(&pcm_open_mutex);
+}
+ 
+static inline void pcm_open_unlock(void)
+{
+	if (workarounds & WORKAROUND_SERIALOPEN)
+	        pthread_mutex_unlock(&pcm_open_mutex);
+}
+
+static inline snd_pcm_uframes_t get_whole_latency(struct loopback *loop)
+{
+	return loop->latency;
+}
+
+static inline unsigned long long
+			frames_to_time(unsigned int rate,
+				       snd_pcm_uframes_t frames)
+{
+	return (frames * 1000000ULL) / rate;
+}
+
+static inline snd_pcm_uframes_t time_to_frames(unsigned int rate,
+					       unsigned long long time)
+{
+	return (time * rate) / 1000000ULL;
+}
+
+static int setparams_stream(struct loopback_handle *lhandle,
+			    snd_pcm_hw_params_t *params)
+{
+	snd_pcm_t *handle = lhandle->handle;
+	int err;
+	unsigned int rrate;
+
+	err = snd_pcm_hw_params_any(handle, params);
+	if (err < 0) {
+		logit(LOG_CRIT, "Broken configuration for %s PCM: no configurations available: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_set_rate_resample(handle, params, lhandle->resample);
+	if (err < 0) {
+		logit(LOG_CRIT, "Resample setup failed for %s (val %i): %s\n", lhandle->id, lhandle->resample, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_set_access(handle, params, lhandle->access);
+	if (err < 0) {
+		logit(LOG_CRIT, "Access type not available for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_set_format(handle, params, lhandle->format);
+	if (err < 0) {
+		logit(LOG_CRIT, "Sample format not available for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_hw_params_set_channels(handle, params, lhandle->channels);
+	if (err < 0) {
+		logit(LOG_CRIT, "Channels count (%i) not available for %s: %s\n", lhandle->channels, lhandle->id, snd_strerror(err));
+		return err;
+	}
+	rrate = lhandle->rate_req;
+	err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
+	if (err < 0) {
+		logit(LOG_CRIT, "Rate %iHz not available for %s: %s\n", lhandle->rate_req, lhandle->id, snd_strerror(err));
+		return err;
+	}
+	rrate = 0;
+	snd_pcm_hw_params_get_rate(params, &rrate, 0);
+	lhandle->rate = rrate;
+	if (
+#ifdef USE_SAMPLERATE
+	    !lhandle->loopback->src_enable &&
+#endif
+	    (int)rrate != lhandle->rate) {
+		logit(LOG_CRIT, "Rate does not match (requested %iHz, got %iHz, resample %i)\n", lhandle->rate, rrate, lhandle->resample);
+		return -EINVAL;
+	}
+	lhandle->pitch = (double)lhandle->rate_req / (double)lhandle->rate;
+	return 0;
+}
+
+static int setparams_bufsize(struct loopback_handle *lhandle,
+			     snd_pcm_hw_params_t *params,
+			     snd_pcm_hw_params_t *tparams,
+			     snd_pcm_uframes_t bufsize)
+{
+	snd_pcm_t *handle = lhandle->handle;
+	int err;
+	snd_pcm_uframes_t periodsize;
+	snd_pcm_uframes_t buffersize;
+	snd_pcm_uframes_t last_bufsize = 0;
+
+	if (lhandle->buffer_size_req > 0) {
+		bufsize = lhandle->buffer_size_req;
+		last_bufsize = bufsize;
+		goto __set_it;
+	}
+      __again:
+	if (lhandle->buffer_size_req > 0) {
+		logit(LOG_CRIT, "Unable to set buffer size %li for %s\n", (long)lhandle->buffer_size, lhandle->id);
+		return -EIO;
+	}
+	if (last_bufsize == bufsize)
+		bufsize += 4;
+	last_bufsize = bufsize;
+	if (bufsize > 10*1024*1024) {
+		logit(LOG_CRIT, "Buffer size too big\n");
+		return -EIO;
+	}
+      __set_it:
+	snd_pcm_hw_params_copy(params, tparams);
+	periodsize = bufsize * 8;
+	err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &periodsize);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to set buffer size %li for %s: %s\n", periodsize, lhandle->id, snd_strerror(err));
+		goto __again;
+	}
+	snd_pcm_hw_params_get_buffer_size(params, &periodsize);
+	if (verbose > 6)
+		snd_output_printf(lhandle->loopback->output, "%s: buffer_size=%li\n", lhandle->id, periodsize);
+	if (lhandle->period_size_req > 0)
+		periodsize = lhandle->period_size_req;
+	else
+		periodsize /= 8;
+	err = snd_pcm_hw_params_set_period_size_near(handle, params, &periodsize, 0);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to set period size %li for %s: %s\n", periodsize, lhandle->id, snd_strerror(err));
+		goto __again;
+	}
+	snd_pcm_hw_params_get_period_size(params, &periodsize, NULL);
+	if (verbose > 6)
+		snd_output_printf(lhandle->loopback->output, "%s: period_size=%li\n", lhandle->id, periodsize);
+	if (periodsize != bufsize)
+		bufsize = periodsize;
+	snd_pcm_hw_params_get_buffer_size(params, &buffersize);
+	if (periodsize * 2 > buffersize)
+		goto __again;
+	lhandle->period_size = periodsize;
+	lhandle->buffer_size = buffersize;
+	return 0;
+}
+
+static int setparams_set(struct loopback_handle *lhandle,
+			 snd_pcm_hw_params_t *params,
+			 snd_pcm_sw_params_t *swparams,
+			 snd_pcm_uframes_t bufsize)
+{
+	snd_pcm_t *handle = lhandle->handle;
+	int err;
+	snd_pcm_uframes_t val, period_size, buffer_size;
+
+	err = snd_pcm_hw_params(handle, params);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to set hw params for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_sw_params_current(handle, swparams);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to determine current swparams for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0x7fffffff);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to set start threshold mode for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	snd_pcm_hw_params_get_period_size(params, &period_size, NULL);
+	snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
+	if (lhandle->nblock) {
+		if (lhandle == lhandle->loopback->play) {
+			val = buffer_size - (2 * period_size - 4);
+		} else {
+			val = 4;
+		}
+		if (verbose > 6)
+			snd_output_printf(lhandle->loopback->output, "%s: avail_min1=%li\n", lhandle->id, val);
+	} else {
+		if (lhandle == lhandle->loopback->play) {
+			val = bufsize + bufsize / 2;
+			if (val < (period_size * 3) / 4)
+				val = (period_size * 3) / 4;
+			if (val > (buffer_size * 3) / 4)
+				val = (buffer_size * 3) / 4;
+			val = buffer_size - val;
+		} else {
+			val = bufsize / 2;
+			if (val < period_size / 2)
+				val = period_size / 2;
+			if (val > buffer_size / 4)
+				val = buffer_size / 4;
+		}
+		if (verbose > 6)
+			snd_output_printf(lhandle->loopback->output, "%s: avail_min2=%li\n", lhandle->id, val);
+	}
+	err = snd_pcm_sw_params_set_avail_min(handle, swparams, val);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to set avail min for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	snd_pcm_sw_params_get_avail_min(swparams, &lhandle->avail_min);
+	err = snd_pcm_sw_params(handle, swparams);
+	if (err < 0) {
+		logit(LOG_CRIT, "Unable to set sw params for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+static int setparams(struct loopback *loop, snd_pcm_uframes_t bufsize)
+{
+	int err;
+	snd_pcm_hw_params_t *pt_params, *ct_params;	/* templates with rate, format and channels */
+	snd_pcm_hw_params_t *p_params, *c_params;
+	snd_pcm_sw_params_t *p_swparams, *c_swparams;
+
+	snd_pcm_hw_params_alloca(&p_params);
+	snd_pcm_hw_params_alloca(&c_params);
+	snd_pcm_hw_params_alloca(&pt_params);
+	snd_pcm_hw_params_alloca(&ct_params);
+	snd_pcm_sw_params_alloca(&p_swparams);
+	snd_pcm_sw_params_alloca(&c_swparams);
+	if ((err = setparams_stream(loop->play, pt_params)) < 0) {
+		logit(LOG_CRIT, "Unable to set parameters for %s stream: %s\n", loop->play->id, snd_strerror(err));
+		return err;
+	}
+	if ((err = setparams_stream(loop->capt, ct_params)) < 0) {
+		logit(LOG_CRIT, "Unable to set parameters for %s stream: %s\n", loop->capt->id, snd_strerror(err));
+		return err;
+	}
+
+	if ((err = setparams_bufsize(loop->play, p_params, pt_params, bufsize / loop->play->pitch)) < 0) {
+		logit(LOG_CRIT, "Unable to set buffer parameters for %s stream: %s\n", loop->play->id, snd_strerror(err));
+		return err;
+	}
+	if ((err = setparams_bufsize(loop->capt, c_params, ct_params, bufsize / loop->capt->pitch)) < 0) {
+		logit(LOG_CRIT, "Unable to set buffer parameters for %s stream: %s\n", loop->capt->id, snd_strerror(err));
+		return err;
+	}
+
+	if ((err = setparams_set(loop->play, p_params, p_swparams, bufsize / loop->play->pitch)) < 0) {
+		logit(LOG_CRIT, "Unable to set sw parameters for %s stream: %s\n", loop->play->id, snd_strerror(err));
+		return err;
+	}
+	if ((err = setparams_set(loop->capt, c_params, c_swparams, bufsize / loop->capt->pitch)) < 0) {
+		logit(LOG_CRIT, "Unable to set sw parameters for %s stream: %s\n", loop->capt->id, snd_strerror(err));
+		return err;
+	}
+
+#if 0
+	if (!loop->linked)
+		if (snd_pcm_link(loop->capt->handle, loop->play->handle) >= 0)
+			loop->linked = 1;
+#endif
+	if ((err = snd_pcm_prepare(loop->play->handle)) < 0) {
+		logit(LOG_CRIT, "Prepare %s error: %s\n", loop->play->id, snd_strerror(err));
+		return err;
+	}
+	if (!loop->linked && (err = snd_pcm_prepare(loop->capt->handle)) < 0) {
+		logit(LOG_CRIT, "Prepare %s error: %s\n", loop->capt->id, snd_strerror(err));
+		return err;
+	}
+
+	if (verbose) {
+		snd_pcm_dump(loop->play->handle, loop->output);
+		snd_pcm_dump(loop->capt->handle, loop->output);
+	}
+	return 0;
+}
+
+static void showlatency(snd_output_t *out, size_t latency, unsigned int rate,
+			char *prefix)
+{
+	double d;
+	d = (double)latency / (double)rate;
+	snd_output_printf(out, "%s %li frames, %.3fus, %.6fms (%.4fHz)\n", prefix, (long)latency, d * 1000000, d * 1000, (double)1 / d);
+}
+
+static long timediff(snd_timestamp_t t1, snd_timestamp_t t2)
+{
+	signed long l;
+
+	t1.tv_sec -= t2.tv_sec;
+	if (t1.tv_usec < t2.tv_usec) {
+		l = ((t1.tv_usec + 1000000) - t2.tv_usec) % 1000000;
+		t1.tv_sec--;
+	} else {
+		l = t1.tv_usec - t2.tv_usec;
+	}
+	return (t1.tv_sec * 1000000) + l;
+}
+
+static int getcurtimestamp(snd_timestamp_t *ts)
+{
+	struct timeval tv;
+	gettimeofday(&tv, NULL);
+	ts->tv_sec = tv.tv_sec;
+	ts->tv_usec = tv.tv_usec;
+	return 0;
+}
+
+static void xrun_profile0(struct loopback *loop)
+{
+	snd_pcm_sframes_t pdelay, cdelay;
+
+	if (snd_pcm_delay(loop->play->handle, &pdelay) >= 0 &&
+	    snd_pcm_delay(loop->capt->handle, &cdelay) >= 0) {
+		getcurtimestamp(&loop->xrun_last_update);
+		loop->xrun_last_pdelay = pdelay;
+		loop->xrun_last_cdelay = cdelay;
+		loop->xrun_buf_pcount = loop->play->buf_count;
+		loop->xrun_buf_ccount = loop->capt->buf_count;
+#ifdef USE_SAMPLERATE
+		loop->xrun_out_frames = loop->src_out_frames;
+#endif
+	}
+}
+
+static inline void xrun_profile(struct loopback *loop)
+{
+	if (loop->xrun)
+		xrun_profile0(loop);
+}
+
+static void xrun_stats0(struct loopback *loop)
+{
+	snd_timestamp_t t;
+	double expected, last, wake, check, queued = -1, proc, missing = -1;
+	double maxbuf, pfilled, cfilled, cqueued = -1, avail_min;
+	double sincejob;
+
+	expected = ((double)loop->latency /
+				(double)loop->play->rate_req) * 1000;
+	getcurtimestamp(&t);
+	last = (double)timediff(t, loop->xrun_last_update) / 1000;
+	wake = (double)timediff(t, loop->xrun_last_wake) / 1000;
+	check = (double)timediff(t, loop->xrun_last_check) / 1000;
+	sincejob = (double)timediff(t, loop->tstamp_start) / 1000;
+	if (loop->xrun_last_pdelay != XRUN_PROFILE_UNKNOWN)
+		queued = ((double)loop->xrun_last_pdelay /
+				(double)loop->play->rate) * 1000;
+	if (loop->xrun_last_cdelay != XRUN_PROFILE_UNKNOWN)
+		cqueued = ((double)loop->xrun_last_cdelay /
+				(double)loop->capt->rate) * 1000;
+	maxbuf = ((double)loop->play->buffer_size /
+				(double)loop->play->rate) * 1000;
+	proc = (double)loop->xrun_max_proctime / 1000;
+	pfilled = ((double)(loop->xrun_buf_pcount + loop->xrun_out_frames) /
+				(double)loop->play->rate) * 1000;
+	cfilled = ((double)loop->xrun_buf_ccount /
+				(double)loop->capt->rate) * 1000;
+	avail_min = (((double)loop->play->buffer_size - 
+				(double)loop->play->avail_min ) / 
+				(double)loop->play->rate) * 1000;
+	avail_min = expected - avail_min;
+	if (queued >= 0)
+		missing = last - queued;
+	if (missing >= 0 && loop->xrun_max_missing < missing)
+		loop->xrun_max_missing = missing;
+	loop->xrun_max_proctime = 0;
+	getcurtimestamp(&t);
+	logit(LOG_INFO, "  last write before %.4fms, queued %.4fms/%.4fms -> missing %.4fms\n", last, queued, cqueued, missing);
+	logit(LOG_INFO, "  expected %.4fms, processing %.4fms, max missing %.4fms\n", expected, proc, loop->xrun_max_missing);
+	logit(LOG_INFO, "  last wake %.4fms, last check %.4fms, avail_min %.4fms\n", wake, check, avail_min);
+	logit(LOG_INFO, "  max buf %.4fms, pfilled %.4fms, cfilled %.4fms\n", maxbuf, pfilled, cfilled);
+	logit(LOG_INFO, "  job started before %.4fms\n", sincejob);
+}
+
+static inline void xrun_stats(struct loopback *loop)
+{
+	if (loop->xrun)
+		xrun_stats0(loop);
+}
+
+static inline snd_pcm_uframes_t buf_avail(struct loopback_handle *lhandle)
+{
+	return lhandle->buf_size - lhandle->buf_count;
+}
+
+static void buf_remove(struct loopback *loop, snd_pcm_uframes_t count)
+{
+	/* remove samples from the capture buffer */
+	if (count <= 0)
+		return;
+	if (loop->play->buf == loop->capt->buf) {
+		if (count < loop->capt->buf_count)
+			loop->capt->buf_count -= count;
+		else
+			loop->capt->buf_count = 0;
+	}
+}
+
+#if 0
+static void buf_add_copy(struct loopback *loop)
+{
+	struct loopback_handle *capt = loop->capt;
+	struct loopback_handle *play = loop->play;
+	snd_pcm_uframes_t count, count1, cpos, ppos;
+
+	count = capt->buf_count;
+	cpos = capt->buf_pos - count;
+	if (cpos > capt->buf_size)
+		cpos += capt->buf_size;
+	ppos = (play->buf_pos + play->buf_count) % play->buf_size;
+	while (count > 0) {
+		count1 = count;
+		if (count1 + cpos > capt->buf_size)
+			count1 = capt->buf_size - cpos;
+		if (count1 > buf_avail(play))
+			count1 = buf_avail(play);
+		if (count1 + ppos > play->buf_size)
+			count1 = play->buf_size - ppos;
+		if (count1 == 0)
+			break;
+		memcpy(play->buf + ppos * play->frame_size,
+		       capt->buf + cpos * capt->frame_size,
+		       count1 * capt->frame_size);
+		play->buf_count += count1;
+		capt->buf_count -= count1;
+		ppos += count1;
+		ppos %= play->buf_size;
+		cpos += count1;
+		cpos %= capt->buf_size;
+		count -= count1;
+	}
+}
+#endif
+
+#ifdef USE_SAMPLERATE
+static void buf_add_src(struct loopback *loop)
+{
+	struct loopback_handle *capt = loop->capt;
+	struct loopback_handle *play = loop->play;
+	float *old_data_out;
+	snd_pcm_uframes_t count, pos, count1, pos1;
+	count = capt->buf_count;
+	pos = 0;
+	pos1 = capt->buf_pos - count;
+	if (pos1 > capt->buf_size)
+		pos1 += capt->buf_size;
+	while (count > 0) {
+		count1 = count;
+		if (count1 + pos1 > capt->buf_size)
+			count1 = capt->buf_size - pos1;
+		if (capt->format == SND_PCM_FORMAT_S32)
+			src_int_to_float_array((int *)(capt->buf +
+						pos1 * capt->frame_size),
+					 loop->src_data.data_in +
+					   pos * capt->channels,
+					 count1 * capt->channels);
+		else
+			src_short_to_float_array((short *)(capt->buf +
+						pos1 * capt->frame_size),
+					 loop->src_data.data_in +
+					   pos * capt->channels,
+					 count1 * capt->channels);
+		count -= count1;
+		pos += count1;
+		pos1 += count1;
+		pos1 %= capt->buf_size;
+	}
+	loop->src_data.input_frames = pos;
+	loop->src_data.output_frames = play->buf_size -
+						loop->src_out_frames;
+	loop->src_data.end_of_input = 0;
+	old_data_out = loop->src_data.data_out;
+	loop->src_data.data_out = old_data_out + loop->src_out_frames;
+	src_process(loop->src_state, &loop->src_data);
+	loop->src_data.data_out = old_data_out;
+	capt->buf_count -= loop->src_data.input_frames_used;
+	count = loop->src_data.output_frames_gen +
+		loop->src_out_frames;
+	pos = 0;
+	pos1 = (play->buf_pos + play->buf_count) % play->buf_size;
+	while (count > 0) {
+		count1 = count;
+		if (count1 + pos1 > play->buf_size)
+			count1 = play->buf_size - pos1;
+		if (count1 > buf_avail(play))
+			count1 = buf_avail(play);
+		if (count1 == 0)
+			break;
+		if (capt->format == SND_PCM_FORMAT_S32)
+			src_float_to_int_array(loop->src_data.data_out +
+					   pos * play->channels,
+					 (int *)(play->buf +
+					   pos1 * play->frame_size),
+					 count1 * play->channels);
+		else
+			src_float_to_short_array(loop->src_data.data_out +
+					   pos * play->channels,
+					 (short *)(play->buf +
+					   pos1 * play->frame_size),
+					 count1 * play->channels);
+		play->buf_count += count1;
+		count -= count1;
+		pos += count1;
+		pos1 += count1;
+		pos1 %= play->buf_size;
+	}
+#if 0
+	printf("src: pos = %li, gen = %li, out = %li, count = %li\n",
+		(long)pos, (long)loop->src_data.output_frames_gen,
+		(long)loop->src_out_frames, play->buf_count);
+#endif
+	loop->src_out_frames = (loop->src_data.output_frames_gen +
+					loop->src_out_frames) - pos;
+	if (loop->src_out_frames > 0) {
+		memmove(loop->src_data.data_out,
+			loop->src_data.data_out + pos * play->channels,
+			loop->src_out_frames * play->channels * sizeof(float));
+	}
+}
+#else
+static void buf_add_src(struct loopback *loop)
+{
+}
+#endif
+
+static void buf_add(struct loopback *loop, snd_pcm_uframes_t count)
+{
+	/* copy samples from capture to playback buffer */
+	if (count <= 0)
+		return;
+	if (loop->play->buf == loop->capt->buf) {
+		loop->play->buf_count += count;
+	} else {
+		buf_add_src(loop);
+	}
+}
+
+static int xrun(struct loopback_handle *lhandle)
+{
+	int err;
+
+	if (lhandle == lhandle->loopback->play) {
+		logit(LOG_DEBUG, "underrun for %s\n", lhandle->id);
+		xrun_stats(lhandle->loopback);
+		if ((err = snd_pcm_prepare(lhandle->handle)) < 0)
+			return err;
+		lhandle->xrun_pending = 1;
+	} else {
+		logit(LOG_DEBUG, "overrun for %s\n", lhandle->id);
+		xrun_stats(lhandle->loopback);
+		if ((err = snd_pcm_prepare(lhandle->handle)) < 0)
+			return err;
+		lhandle->xrun_pending = 1;
+	}
+	return 0;
+}
+
+static int suspend(struct loopback_handle *lhandle)
+{
+	int err;
+
+	while ((err = snd_pcm_resume(lhandle->handle)) == -EAGAIN)
+		usleep(1);
+	if (err < 0)
+		return xrun(lhandle);
+	return 0;
+}
+
+static int readit(struct loopback_handle *lhandle)
+{
+	snd_pcm_sframes_t r, res = 0;
+	snd_pcm_sframes_t avail;
+	int err;
+
+	avail = snd_pcm_avail_update(lhandle->handle);
+	if (avail == -EPIPE) {
+		return xrun(lhandle);
+	} else if (avail == -ESTRPIPE) {
+		if ((err = suspend(lhandle)) < 0)
+			return err;
+	}
+	if (avail > buf_avail(lhandle)) {
+		lhandle->buf_over += avail - buf_avail(lhandle);
+		avail = buf_avail(lhandle);
+	} else if (avail == 0) {
+		if (snd_pcm_state(lhandle->handle) == SND_PCM_STATE_DRAINING) {
+			lhandle->loopback->reinit = 1;
+			return 0;
+		}
+	}
+	while (avail > 0) {
+		r = buf_avail(lhandle);
+		if (r + lhandle->buf_pos > lhandle->buf_size)
+			r = lhandle->buf_size - lhandle->buf_pos;
+		if (r > avail)
+			r = avail;
+		r = snd_pcm_readi(lhandle->handle,
+				  lhandle->buf +
+				  lhandle->buf_pos *
+				  lhandle->frame_size, r);
+		if (r == 0)
+			return res;
+		if (r < 0) {
+			if (r == -EPIPE) {
+				err = xrun(lhandle);
+				return res > 0 ? res : err;
+			} else if (r == -ESTRPIPE) {
+				if ((err = suspend(lhandle)) < 0)
+					return res > 0 ? res : err;
+				r = 0;
+			} else {
+				return res > 0 ? res : r;
+			}
+		}
+#ifdef FILE_CWRITE
+		if (lhandle->loopback->cfile)
+			fwrite(lhandle->buf + lhandle->buf_pos * lhandle->frame_size,
+			       r, lhandle->frame_size, lhandle->loopback->cfile);
+#endif
+		res += r;
+		if (lhandle->max < res)
+			lhandle->max = res;
+		lhandle->counter += r;
+		lhandle->buf_count += r;
+		lhandle->buf_pos += r;
+		lhandle->buf_pos %= lhandle->buf_size;
+		avail -= r;
+	}
+	return res;
+}
+
+static int writeit(struct loopback_handle *lhandle)
+{
+	snd_pcm_sframes_t avail;
+	snd_pcm_sframes_t r, res = 0;
+	int err;
+
+      __again:
+	avail = snd_pcm_avail_update(lhandle->handle);
+	if (avail == -EPIPE) {
+		if ((err = xrun(lhandle)) < 0)
+			return err;
+		return res;
+	} else if (avail == -ESTRPIPE) {
+		if ((err = suspend(lhandle)) < 0)
+			return err;
+		goto __again;
+	}
+	while (avail > 0 && lhandle->buf_count > 0) {
+		r = lhandle->buf_count;
+		if (r + lhandle->buf_pos > lhandle->buf_size)
+			r = lhandle->buf_size - lhandle->buf_pos;
+		if (r > avail)
+			r = avail;
+		r = snd_pcm_writei(lhandle->handle,
+				   lhandle->buf +
+				   lhandle->buf_pos *
+				   lhandle->frame_size, r);
+		if (r <= 0) {
+			if (r == -EPIPE) {
+				if ((err = xrun(lhandle)) < 0)
+					return err;
+				return res;
+			} else if (r == -ESTRPIPE) {
+			}
+			return res > 0 ? res : r;
+		}
+#ifdef FILE_PWRITE
+		if (lhandle->loopback->pfile)
+			fwrite(lhandle->buf + lhandle->buf_pos * lhandle->frame_size,
+			       r, lhandle->frame_size, lhandle->loopback->pfile);
+#endif
+		res += r;
+		lhandle->counter += r;
+		lhandle->buf_count -= r;
+		lhandle->buf_pos += r;
+		lhandle->buf_pos %= lhandle->buf_size;
+		xrun_profile(lhandle->loopback);
+		if (lhandle->loopback->stop_pending) {
+			lhandle->loopback->stop_count += r;
+			if (lhandle->loopback->stop_count * lhandle->pitch >
+			    lhandle->loopback->latency * 3) {
+				lhandle->loopback->stop_pending = 0;
+				lhandle->loopback->reinit = 1;
+				break;
+			}
+		}
+	}
+	return res;
+}
+
+static snd_pcm_sframes_t remove_samples(struct loopback *loop,
+					int capture_preferred,
+					snd_pcm_sframes_t count)
+{
+	struct loopback_handle *play = loop->play;
+	struct loopback_handle *capt = loop->capt;
+
+	if (loop->play->buf == loop->capt->buf) {
+		if (count > loop->play->buf_count)
+			count = loop->play->buf_count;
+		if (count > loop->capt->buf_count)
+			count = loop->capt->buf_count;
+		capt->buf_count -= count;
+		play->buf_pos += count;
+		play->buf_pos %= play->buf_size;
+		play->buf_count -= count;
+		return count;
+	}
+	if (capture_preferred) {
+		if (count > capt->buf_count)
+			count = capt->buf_count;
+		capt->buf_count -= count;
+	} else {
+		if (count > play->buf_count)
+			count = play->buf_count;
+		play->buf_count -= count;
+	}
+	return count;
+}
+
+static int xrun_sync(struct loopback *loop)
+{
+	struct loopback_handle *play = loop->play;
+	struct loopback_handle *capt = loop->capt;
+	snd_pcm_uframes_t fill = get_whole_latency(loop);
+	snd_pcm_sframes_t pdelay, cdelay, delay1, pdelay1, cdelay1, diff;
+	int err;
+
+      __again:
+	if (verbose > 5)
+		snd_output_printf(loop->output, "%s: xrun sync %i %i\n", loop->id, capt->xrun_pending, play->xrun_pending);
+	if (capt->xrun_pending) {
+	      __pagain:
+		capt->xrun_pending = 0;
+		if ((err = snd_pcm_prepare(capt->handle)) < 0) {
+			logit(LOG_CRIT, "%s prepare failed: %s\n", capt->id, snd_strerror(err));
+			return err;
+		}
+		if ((err = snd_pcm_start(capt->handle)) < 0) {
+			logit(LOG_CRIT, "%s start failed: %s\n", capt->id, snd_strerror(err));
+			return err;
+		}
+	} else {
+		diff = readit(capt);
+		buf_add(loop, diff);
+		if (capt->xrun_pending)
+			goto __pagain;
+	}
+	/* skip additional playback samples */
+	if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0) {
+		if (err == -EPIPE) {
+			capt->xrun_pending = 1;
+			goto __again;
+		}
+		if (err == -ESTRPIPE) {
+			err = suspend(capt);
+			if (err < 0)
+				return err;
+			goto __again;
+		}
+		logit(LOG_CRIT, "%s capture delay failed: %s\n", capt->id, snd_strerror(err));
+		return err;
+	}
+	if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0) {
+		if (err == -EPIPE) {
+			pdelay = 0;
+			play->xrun_pending = 1;
+		} else if (err == -ESTRPIPE) {
+			err = suspend(play);
+			if (err < 0)
+				return err;
+			goto __again;
+		} else {
+			logit(LOG_CRIT, "%s playback delay failed: %s\n", play->id, snd_strerror(err));
+			return err;
+		}
+	}
+	capt->counter = cdelay;
+	play->counter = pdelay;
+	if (play->buf != capt->buf)
+		cdelay += capt->buf_count;
+	pdelay += play->buf_count;
+#ifdef USE_SAMPLERATE
+	pdelay += loop->src_out_frames;
+#endif
+	cdelay1 = cdelay * capt->pitch;
+	pdelay1 = pdelay * play->pitch;
+	delay1 = cdelay1 + pdelay1;
+	capt->total_queued = 0;
+	play->total_queued = 0;
+	loop->total_queued_count = 0;
+	loop->pitch_diff = loop->pitch_diff_min = loop->pitch_diff_max = 0;
+	if (verbose > 6) {
+		snd_output_printf(loop->output,
+			"sync: cdelay=%li(%li), pdelay=%li(%li), fill=%li (delay=%li)"
+#ifdef USE_SAMPLERATE
+			", src_out=%li"
+#endif
+			"\n",
+			(long)cdelay, (long)cdelay1, (long)pdelay, (long)pdelay1,
+			(long)fill, (long)delay1
+#ifdef USE_SAMPLERATE
+			, (long)loop->src_out_frames
+#endif
+			);
+		snd_output_printf(loop->output,
+			"sync: cbufcount=%li, pbufcount=%li\n",
+			(long)capt->buf_count, (long)play->buf_count);
+	}
+	if (delay1 > fill && capt->counter > 0) {
+		if ((err = snd_pcm_drop(capt->handle)) < 0)
+			return err;
+		if ((err = snd_pcm_prepare(capt->handle)) < 0)
+			return err;
+		if ((err = snd_pcm_start(capt->handle)) < 0)
+			return err;
+		diff = remove_samples(loop, 1, (delay1 - fill) / capt->pitch);
+		if (verbose > 6)
+			snd_output_printf(loop->output,
+				"sync: capt stop removed %li samples\n", (long)diff);
+		goto __again;
+	}
+	if (delay1 > fill) {
+		diff = (delay1 - fill) / play->pitch;
+		if (diff > play->buf_count)
+			diff = play->buf_count;
+		if (verbose > 6)
+			snd_output_printf(loop->output,
+				"sync: removing %li playback samples, delay1=%li\n", (long)diff, (long)delay1);
+		diff = remove_samples(loop, 0, diff);
+		pdelay -= diff;
+		pdelay1 = pdelay * play->pitch;
+		delay1 = cdelay1 + pdelay1;
+		if (verbose > 6)
+			snd_output_printf(loop->output,
+				"sync: removed %li playback samples, delay1=%li\n", (long)diff, (long)delay1);
+	}
+	if (delay1 > fill) {
+		diff = (delay1 - fill) / capt->pitch;
+		if (diff > capt->buf_count)
+			diff = capt->buf_count;
+		if (verbose > 6)
+			snd_output_printf(loop->output,
+				"sync: removing %li captured samples, delay1=%li\n", (long)diff, (long)delay1);
+		diff -= remove_samples(loop, 1, diff);
+		cdelay -= diff;
+		cdelay1 = cdelay * capt->pitch;
+		delay1 = cdelay1 + pdelay1;		
+		if (verbose > 6)
+			snd_output_printf(loop->output,
+				"sync: removed %li captured samples, delay1=%li\n", (long)diff, (long)delay1);
+	}
+	if (play->xrun_pending) {
+		play->xrun_pending = 0;
+		diff = (fill - delay1) / play->pitch;
+		if (verbose > 6)
+			snd_output_printf(loop->output,
+				"sync: xrun_pending, silence filling %li / buf_count=%li\n", (long)diff, play->buf_count);
+		if (fill > delay1 && play->buf_count < diff) {
+			diff = diff - play->buf_count;
+			if (verbose > 6)
+				snd_output_printf(loop->output,
+					"sync: playback silence added %li samples\n", (long)diff);
+			play->buf_pos -= diff;
+			play->buf_pos %= play->buf_size;
+			if ((err = snd_pcm_format_set_silence(play->format, play->buf + play->buf_pos * play->channels, diff)) < 0)
+				return err;
+			play->buf_count += diff;
+		}
+		if ((err = snd_pcm_prepare(play->handle)) < 0) {
+			logit(LOG_CRIT, "%s prepare failed: %s\n", play->id, snd_strerror(err));
+
+			return err;
+		}
+		delay1 = writeit(play);
+		if (verbose > 6)
+			snd_output_printf(loop->output,
+				"sync: playback wrote %li samples\n", (long)delay1);
+		if (delay1 > diff) {
+			buf_remove(loop, delay1 - diff);
+			if (verbose > 6)
+				snd_output_printf(loop->output,
+					"sync: playback buf_remove %li samples\n", (long)(delay1 - diff));
+		}
+		if ((err = snd_pcm_start(play->handle)) < 0) {
+			logit(LOG_CRIT, "%s start failed: %s\n", play->id, snd_strerror(err));
+			return err;
+		}
+	}
+	if (verbose > 5) {
+		snd_output_printf(loop->output, "%s: xrun sync ok\n", loop->id);
+		if (verbose > 6) {
+			if (snd_pcm_delay(capt->handle, &cdelay) < 0)
+				cdelay = -1;
+			if (snd_pcm_delay(play->handle, &pdelay) < 0)
+				pdelay = -1;
+			if (play->buf != capt->buf)
+				cdelay += capt->buf_count;
+			pdelay += play->buf_count;
+#ifdef USE_SAMPLERATE
+			pdelay += loop->src_out_frames;
+#endif
+			cdelay1 = cdelay * capt->pitch;
+			pdelay1 = pdelay * play->pitch;
+			delay1 = cdelay1 + pdelay1;
+			snd_output_printf(loop->output, "%s: sync verify: %li\n", loop->id, delay1);
+		}
+	}
+	loop->xrun_max_proctime = 0;
+	return 0;
+}
+
+static int set_notify(struct loopback_handle *lhandle, int enable)
+{
+	int err;
+
+	if (lhandle->ctl_notify == NULL)
+		return 0;
+	snd_ctl_elem_value_set_boolean(lhandle->ctl_notify, 0, enable);
+	err = snd_ctl_elem_write(lhandle->ctl, lhandle->ctl_notify);
+	if (err < 0) {
+		logit(LOG_CRIT, "Cannot set PCM Notify element for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_notify);
+	if (err < 0) {
+		logit(LOG_CRIT, "Cannot get PCM Notify element for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+static int set_rate_shift(struct loopback_handle *lhandle, double pitch)
+{
+	int err;
+
+	if (lhandle->ctl_rate_shift == NULL)
+		return 0;
+	snd_ctl_elem_value_set_integer(lhandle->ctl_rate_shift, 0, pitch * 100000);
+	err = snd_ctl_elem_write(lhandle->ctl, lhandle->ctl_rate_shift);
+	if (err < 0) {
+		logit(LOG_CRIT, "Cannot set PCM Rate Shift element for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+void update_pitch(struct loopback *loop)
+{
+	double pitch = loop->pitch;
+
+#ifdef USE_SAMPLERATE
+	if (loop->sync == SYNC_TYPE_SAMPLERATE) {
+		loop->src_data.src_ratio = (double)1.0 / (pitch *
+				loop->play->pitch * loop->capt->pitch);
+		if (verbose > 2)
+			snd_output_printf(loop->output, "%s: Samplerate src_ratio update1: %.8f\n", loop->id, loop->src_data.src_ratio);
+	} else
+#endif
+	if (loop->sync == SYNC_TYPE_CAPTRATESHIFT) {
+		set_rate_shift(loop->capt, pitch);
+#ifdef USE_SAMPLERATE
+		if (loop->use_samplerate) {
+			loop->src_data.src_ratio = 
+				(double)1.0 /
+					(loop->play->pitch * loop->capt->pitch);
+			if (verbose > 2)
+				snd_output_printf(loop->output, "%s: Samplerate src_ratio update2: %.8f\n", loop->id, loop->src_data.src_ratio);
+		}
+#endif
+	}
+	else if (loop->sync == SYNC_TYPE_PLAYRATESHIFT) {
+		set_rate_shift(loop->play, pitch);
+#ifdef USE_SAMPLERATE
+		if (loop->use_samplerate) {
+			loop->src_data.src_ratio = 
+				(double)1.0 /
+					(loop->play->pitch * loop->capt->pitch);
+			if (verbose > 2)
+				snd_output_printf(loop->output, "%s: Samplerate src_ratio update3: %.8f\n", loop->id, loop->src_data.src_ratio);
+		}
+#endif
+	}
+	if (verbose)
+		snd_output_printf(loop->output, "New pitch for %s: %.8f (min/max samples = %li/%li)\n", loop->id, pitch, loop->pitch_diff_min, loop->pitch_diff_max);
+}
+
+static int get_active(struct loopback_handle *lhandle)
+{
+	int err;
+
+	if (lhandle->ctl_active == NULL)
+		return 0;
+	err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_active);
+	if (err < 0) {
+		logit(LOG_CRIT, "Cannot get PCM Slave Active element for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	return snd_ctl_elem_value_get_boolean(lhandle->ctl_active, 0);
+}
+
+static int get_format(struct loopback_handle *lhandle)
+{
+	int err;
+
+	if (lhandle->ctl_format == NULL)
+		return 0;
+	err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_format);
+	if (err < 0) {
+		logit(LOG_CRIT, "Cannot get PCM Format element for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	return snd_ctl_elem_value_get_integer(lhandle->ctl_format, 0);
+}
+
+static int get_rate(struct loopback_handle *lhandle)
+{
+	int err;
+
+	if (lhandle->ctl_rate == NULL)
+		return 0;
+	err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_rate);
+	if (err < 0) {
+		logit(LOG_CRIT, "Cannot get PCM Rate element for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	return snd_ctl_elem_value_get_integer(lhandle->ctl_rate, 0);
+}
+
+static int get_channels(struct loopback_handle *lhandle)
+{
+	int err;
+
+	if (lhandle->ctl_channels == NULL)
+		return 0;
+	err = snd_ctl_elem_read(lhandle->ctl, lhandle->ctl_channels);
+	if (err < 0) {
+		logit(LOG_CRIT, "Cannot get PCM Channels element for %s: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	return snd_ctl_elem_value_get_integer(lhandle->ctl_channels, 0);
+}
+
+static void openctl_elem(struct loopback_handle *lhandle,
+			 int device, int subdevice,
+			 const char *name,
+			 snd_ctl_elem_value_t **elem)
+{
+	int err;
+
+	if (snd_ctl_elem_value_malloc(elem) < 0) {
+		*elem = NULL;
+	} else {
+		snd_ctl_elem_value_set_interface(*elem,
+						 SND_CTL_ELEM_IFACE_PCM);
+		snd_ctl_elem_value_set_device(*elem, device);
+		snd_ctl_elem_value_set_subdevice(*elem, subdevice);
+		snd_ctl_elem_value_set_name(*elem, name);
+		err = snd_ctl_elem_read(lhandle->ctl, *elem);
+		if (err < 0) {
+			snd_ctl_elem_value_free(*elem);
+			*elem = NULL;
+		}
+	}
+}
+
+static int openctl(struct loopback_handle *lhandle, int device, int subdevice)
+{
+	int err;
+
+	lhandle->ctl_rate_shift = NULL;
+	if (lhandle->loopback->play == lhandle) {
+		if (lhandle->loopback->controls)
+			goto __events;
+		return 0;
+	}
+	openctl_elem(lhandle, device, subdevice, "PCM Notify",
+			&lhandle->ctl_notify);
+	openctl_elem(lhandle, device, subdevice, "PCM Rate Shift 100000",
+			&lhandle->ctl_rate_shift);
+	set_rate_shift(lhandle, 1);
+	openctl_elem(lhandle, device, subdevice, "PCM Slave Active",
+			&lhandle->ctl_active);
+	openctl_elem(lhandle, device, subdevice, "PCM Slave Format",
+			&lhandle->ctl_format);
+	openctl_elem(lhandle, device, subdevice, "PCM Slave Rate",
+			&lhandle->ctl_rate);
+	openctl_elem(lhandle, device, subdevice, "PCM Slave Channels",
+			&lhandle->ctl_channels);
+	if ((lhandle->ctl_active &&
+	     lhandle->ctl_format &&
+	     lhandle->ctl_rate &&
+	     lhandle->ctl_channels) ||
+	    lhandle->loopback->controls) {
+	      __events:
+		if ((err = snd_ctl_poll_descriptors_count(lhandle->ctl)) < 0)
+			lhandle->ctl_pollfd_count = 0;
+		else
+			lhandle->ctl_pollfd_count = err;
+		if (snd_ctl_subscribe_events(lhandle->ctl, 1) < 0)
+			lhandle->ctl_pollfd_count = 0;
+	}
+	return 0;
+}
+
+static int openit(struct loopback_handle *lhandle)
+{
+	snd_pcm_info_t *info;
+	int stream = lhandle == lhandle->loopback->play ?
+				SND_PCM_STREAM_PLAYBACK :
+				SND_PCM_STREAM_CAPTURE;
+	int err, card, device, subdevice;
+	pcm_open_lock();
+	err = snd_pcm_open(&lhandle->handle, lhandle->device, stream, SND_PCM_NONBLOCK);
+	pcm_open_unlock();
+	if (err < 0) {
+		logit(LOG_CRIT, "%s open error: %s\n", lhandle->id, snd_strerror(err));
+		return err;
+	}
+	if ((err = snd_pcm_info_malloc(&info)) < 0)
+		return err;
+	if ((err = snd_pcm_info(lhandle->handle, info)) < 0) {
+		snd_pcm_info_free(info);
+		return err;
+	}
+	card = snd_pcm_info_get_card(info);
+	device = snd_pcm_info_get_device(info);
+	subdevice = snd_pcm_info_get_subdevice(info);
+	snd_pcm_info_free(info);
+	lhandle->card_number = card;
+	lhandle->ctl = NULL;
+	if (card >= 0 || lhandle->ctldev) {
+		char name[16], *dev = lhandle->ctldev;
+		if (dev == NULL) {
+			sprintf(name, "hw:%i", card);
+			dev = name;
+		}
+		pcm_open_lock();
+		err = snd_ctl_open(&lhandle->ctl, dev, SND_CTL_NONBLOCK);
+		pcm_open_unlock();
+		if (err < 0) {
+			logit(LOG_CRIT, "%s [%s] ctl open error: %s\n", lhandle->id, dev, snd_strerror(err));
+			lhandle->ctl = NULL;
+		}
+		if (lhandle->ctl)
+			openctl(lhandle, device, subdevice);
+	}
+	return 0;
+}
+
+static int freeit(struct loopback_handle *lhandle)
+{
+	free(lhandle->buf);
+	lhandle->buf = NULL;
+	return 0;
+}
+
+static int closeit(struct loopback_handle *lhandle)
+{
+	int err = 0;
+
+	set_rate_shift(lhandle, 1);
+	if (lhandle->ctl_rate_shift)
+		snd_ctl_elem_value_free(lhandle->ctl_rate_shift);
+	lhandle->ctl_rate_shift = NULL;
+	if (lhandle->ctl)
+		err = snd_ctl_close(lhandle->ctl);
+	lhandle->ctl = NULL;
+	if (lhandle->handle)
+		err = snd_pcm_close(lhandle->handle);
+	lhandle->handle = NULL;
+	return err;
+}
+
+static int init_handle(struct loopback_handle *lhandle, int alloc)
+{
+	snd_pcm_uframes_t lat;
+	lhandle->frame_size = (snd_pcm_format_width(lhandle->format) / 8) *
+							   lhandle->channels;
+	lhandle->sync_point = lhandle->rate * 15;	/* every 15 seconds */
+	lat = lhandle->loopback->latency;
+	if (lhandle->buffer_size > lat)
+		lat = lhandle->buffer_size;
+	lhandle->buf_size = lat * 2;
+	if (alloc) {
+		lhandle->buf = calloc(1, lhandle->buf_size * lhandle->frame_size);
+		if (lhandle->buf == NULL)
+			return -ENOMEM;
+	}
+	return 0;
+}
+
+int pcmjob_init(struct loopback *loop)
+{
+	int err;
+	char id[128];
+
+#ifdef FILE_CWRITE
+	loop->cfile = fopen(FILE_CWRITE, "w+");
+#endif
+#ifdef FILE_PWRITE
+	loop->pfile = fopen(FILE_PWRITE, "w+");
+#endif
+	if ((err = openit(loop->play)) < 0)
+		goto __error;
+	if ((err = openit(loop->capt)) < 0)
+		goto __error;
+	snprintf(id, sizeof(id), "%s/%s", loop->play->id, loop->capt->id);
+	id[sizeof(id)-1] = '\0';
+	loop->id = strdup(id);
+	if (loop->sync == SYNC_TYPE_AUTO && loop->capt->ctl_rate_shift)
+		loop->sync = SYNC_TYPE_CAPTRATESHIFT;
+	if (loop->sync == SYNC_TYPE_AUTO && loop->play->ctl_rate_shift)
+		loop->sync = SYNC_TYPE_PLAYRATESHIFT;
+#ifdef USE_SAMPLERATE
+	if (loop->sync == SYNC_TYPE_AUTO && loop->src_enable)
+		loop->sync = SYNC_TYPE_SAMPLERATE;
+#endif
+	if (loop->sync == SYNC_TYPE_AUTO)
+		loop->sync = SYNC_TYPE_SIMPLE;
+	if (loop->slave == SLAVE_TYPE_AUTO &&
+	    loop->capt->ctl_notify &&
+	    loop->capt->ctl_active &&
+	    loop->capt->ctl_format &&
+	    loop->capt->ctl_rate &&
+	    loop->capt->ctl_channels)
+		loop->slave = SLAVE_TYPE_ON;
+	if (loop->slave == SLAVE_TYPE_ON) {
+		err = set_notify(loop->capt, 1);
+		if (err < 0)
+			goto __error;
+		if (loop->capt->ctl_notify == NULL ||
+		    snd_ctl_elem_value_get_boolean(loop->capt->ctl_notify, 0) == 0) {
+			logit(LOG_CRIT, "unable to enable slave mode for %s\n", loop->id);
+			err = -EINVAL;
+			goto __error;
+		}
+	}
+	err = control_init(loop);
+	if (err < 0)
+		goto __error;
+	return 0;
+      __error:
+	pcmjob_done(loop);
+	return err;
+}
+
+static void freeloop(struct loopback *loop)
+{
+#ifdef USE_SAMPLERATE
+	if (loop->use_samplerate) {
+		if (loop->src_state)
+			src_delete(loop->src_state);
+		loop->src_state = NULL;
+		free(loop->src_data.data_in);
+		loop->src_data.data_in = NULL;
+		free(loop->src_data.data_out);
+		loop->src_data.data_out = NULL;
+	}
+#endif
+	if (loop->play->buf == loop->capt->buf)
+		loop->play->buf = NULL;
+	freeit(loop->play);
+	freeit(loop->capt);
+}
+
+int pcmjob_done(struct loopback *loop)
+{
+	control_done(loop);
+	closeit(loop->play);
+	closeit(loop->capt);
+	freeloop(loop);
+	free(loop->id);
+	loop->id = NULL;
+#ifdef FILE_PWRITE
+	if (loop->pfile) {
+		fclose(loop->pfile);
+		loop->pfile = NULL;
+	}
+#endif
+#ifdef FILE_CWRITE
+	if (loop->cfile) {
+		fclose(loop->cfile);
+		loop->cfile = NULL;
+	}
+#endif
+	return 0;
+}
+
+static void lhandle_start(struct loopback_handle *lhandle)
+{
+	lhandle->buf_pos = 0;
+	lhandle->buf_count = 0;
+	lhandle->counter = 0;
+	lhandle->total_queued = 0;
+}
+
+int pcmjob_start(struct loopback *loop)
+{
+	snd_pcm_uframes_t count;
+	int err;
+
+	loop->pollfd_count = loop->play->ctl_pollfd_count +
+			     loop->capt->ctl_pollfd_count;
+	if ((err = snd_pcm_poll_descriptors_count(loop->play->handle)) < 0)
+		goto __error;
+	loop->play->pollfd_count = err;
+	loop->pollfd_count += err;
+	if ((err = snd_pcm_poll_descriptors_count(loop->capt->handle)) < 0)
+		goto __error;
+	loop->capt->pollfd_count = err;
+	loop->pollfd_count += err;
+	if (loop->slave == SLAVE_TYPE_ON) {
+		err = get_active(loop->capt);
+		if (err < 0)
+			goto __error;
+		if (err == 0)		/* stream is not active */
+			return 0;
+		err = get_format(loop->capt);
+		if (err < 0)
+			goto __error;
+		loop->play->format = loop->capt->format = err;
+		err = get_rate(loop->capt);
+		if (err < 0)
+			goto __error;
+		loop->play->rate_req = loop->capt->rate_req = err;
+		err = get_channels(loop->capt);
+		if (err < 0)
+			goto __error;
+		loop->play->channels = loop->capt->channels = err;
+	}
+	loop->reinit = 0;
+	loop->use_samplerate = 0;
+	if (loop->latency_req) {
+		loop->latency_reqtime = frames_to_time(loop->play->rate_req,
+						       loop->latency_req);
+		loop->latency_req = 0;
+	}
+	loop->latency = time_to_frames(loop->play->rate_req, loop->latency_reqtime);
+	if ((err = setparams(loop, loop->latency/2)) < 0)
+		goto __error;
+	if (verbose)
+		showlatency(loop->output, loop->latency, loop->play->rate_req, "Latency");
+	if (loop->play->access == loop->capt->access &&
+	    loop->play->format == loop->capt->format &&
+	    loop->play->rate == loop->capt->rate &&
+	    loop->play->channels == loop->play->channels &&
+	    loop->sync != SYNC_TYPE_SAMPLERATE) {
+		if (verbose > 1)
+			snd_output_printf(loop->output, "shared buffer!!!\n");
+		if ((err = init_handle(loop->play, 1)) < 0)
+			goto __error;
+		if ((err = init_handle(loop->capt, 0)) < 0)
+			goto __error;
+		if (loop->play->buf_size < loop->capt->buf_size) {
+			char *nbuf = realloc(loop->play->buf,
+					     loop->capt->buf_size *
+					       loop->capt->frame_size);
+			if (nbuf == NULL) {
+				err = -ENOMEM;
+				goto __error;
+			}
+			loop->play->buf = nbuf;
+			loop->play->buf_size = loop->capt->buf_size;
+		} else if (loop->capt->buf_size < loop->play->buf_size) {
+			char *nbuf = realloc(loop->capt->buf,
+					     loop->play->buf_size *
+					       loop->play->frame_size);
+			if (nbuf == NULL) {
+				err = -ENOMEM;
+				goto __error;
+			}
+			loop->capt->buf = nbuf;
+			loop->capt->buf_size = loop->play->buf_size;
+		}
+		loop->capt->buf = loop->play->buf;
+	} else {
+		if ((err = init_handle(loop->play, 1)) < 0)
+			goto __error;
+		if ((err = init_handle(loop->capt, 1)) < 0)
+			goto __error;
+		if (loop->play->rate_req != loop->play->rate)
+			loop->use_samplerate = 1;
+		if (loop->capt->rate_req != loop->capt->rate)
+			loop->use_samplerate = 1;
+	}
+#ifdef USE_SAMPLERATE
+	if (loop->sync == SYNC_TYPE_SAMPLERATE)
+		loop->use_samplerate = 1;
+	if (loop->use_samplerate && !loop->src_enable) {
+		logit(LOG_CRIT, "samplerate conversion required but disabled\n");
+		loop->use_samplerate = 0;
+		err = -EIO;
+		goto __error;		
+	}
+	if (loop->use_samplerate) {
+		if ((loop->capt->format != SND_PCM_FORMAT_S16 ||
+		    loop->play->format != SND_PCM_FORMAT_S16) &&
+		    (loop->capt->format != SND_PCM_FORMAT_S32 ||
+		     loop->play->format != SND_PCM_FORMAT_S32)) {
+			logit(LOG_CRIT, "samplerate conversion supports only %s or %s formats (play=%s, capt=%s)\n", snd_pcm_format_name(SND_PCM_FORMAT_S16), snd_pcm_format_name(SND_PCM_FORMAT_S32), snd_pcm_format_name(loop->play->format), snd_pcm_format_name(loop->capt->format));
+			loop->use_samplerate = 0;
+			err = -EIO;
+			goto __error;		
+		}
+		loop->src_state = src_new(loop->src_converter_type,
+					  loop->play->channels, &err);
+		loop->src_data.data_in = calloc(1, sizeof(float)*loop->capt->channels*loop->capt->buf_size);
+		if (loop->src_data.data_in == NULL) {
+			err = -ENOMEM;
+			goto __error;
+		}
+		loop->src_data.data_out =  calloc(1, sizeof(float)*loop->play->channels*loop->play->buf_size);
+		if (loop->src_data.data_out == NULL) {
+			err = -ENOMEM;
+			goto __error;
+		}
+		loop->src_data.src_ratio = (double)loop->play->rate /
+					   (double)loop->capt->rate;
+		loop->src_data.end_of_input = 0;
+		loop->src_out_frames = 0;
+	} else {
+		loop->src_state = NULL;
+	}
+#else
+	if (loop->sync == SYNC_TYPE_SAMPLERATE || loop->use_samplerate) {
+		logit(LOG_CRIT, "alsaloop is compiled without libsamplerate support\n");
+		err = -EIO;
+		goto __error;
+	}
+#endif
+	if (verbose) {
+		snd_output_printf(loop->output, "%s sync type: %s", loop->id, sync_types[loop->sync]);
+#ifdef USE_SAMPLERATE
+		if (loop->sync == SYNC_TYPE_SAMPLERATE)
+			snd_output_printf(loop->output, " (%s)", src_types[loop->src_converter_type]);
+#endif
+		snd_output_printf(loop->output, "\n");
+	}
+	lhandle_start(loop->play);
+	lhandle_start(loop->capt);
+	if ((err = snd_pcm_format_set_silence(loop->play->format,
+					      loop->play->buf,
+					      loop->play->buf_size * loop->play->channels)) < 0) {
+		logit(LOG_CRIT, "%s: silence error\n", loop->id);
+		goto __error;
+	}
+	if (verbose > 4)
+		snd_output_printf(loop->output, "%s: capt->buffer_size = %li, play->buffer_size = %li\n", loop->id, loop->capt->buf_size, loop->play->buf_size);
+	loop->pitch = 1.0;
+	update_pitch(loop);
+	loop->pitch_delta = 1.0 / ((double)loop->capt->rate * 4);
+	loop->total_queued_count = 0;
+	loop->pitch_diff = 0;
+	count = get_whole_latency(loop) / loop->play->pitch;
+	loop->play->buf_count = count;
+	if (loop->play->buf == loop->capt->buf)
+		loop->capt->buf_pos = count;
+	err = writeit(loop->play);
+	if (verbose > 4)
+		snd_output_printf(loop->output, "%s: silence queued %i samples\n", loop->id, err);
+	if (count > loop->play->buffer_size)
+		count = loop->play->buffer_size;
+	if (err != count) {
+		logit(LOG_CRIT, "%s: initial playback fill error (%i/%i/%i)\n", loop->id, err, (int)count, loop->play->buffer_size);
+		err = -EIO;
+		goto __error;
+	}
+	loop->running = 1;
+	loop->stop_pending = 0;
+	if (loop->xrun) {
+		getcurtimestamp(&loop->xrun_last_update);
+		loop->xrun_last_pdelay = XRUN_PROFILE_UNKNOWN;
+		loop->xrun_last_cdelay = XRUN_PROFILE_UNKNOWN;
+		loop->xrun_max_proctime = 0;
+	}
+	if ((err = snd_pcm_start(loop->capt->handle)) < 0) {
+		logit(LOG_CRIT, "pcm start %s error: %s\n", loop->capt->id, snd_strerror(err));
+		goto __error;
+	}
+	if (!loop->linked) {
+		if ((err = snd_pcm_start(loop->play->handle)) < 0) {
+			logit(LOG_CRIT, "pcm start %s error: %s\n", loop->play->id, snd_strerror(err));
+			goto __error;
+		}
+	}
+	return 0;
+      __error:
+	pcmjob_stop(loop);
+	return err;
+}
+
+int pcmjob_stop(struct loopback *loop)
+{
+	int err;
+
+	if (loop->running) {
+		if ((err = snd_pcm_drop(loop->capt->handle)) < 0)
+			logit(LOG_WARNING, "pcm drop %s error: %s\n", loop->capt->id, snd_strerror(err));
+		if ((err = snd_pcm_drop(loop->play->handle)) < 0)
+			logit(LOG_WARNING, "pcm drop %s error: %s\n", loop->play->id, snd_strerror(err));
+		if ((err = snd_pcm_hw_free(loop->capt->handle)) < 0)
+			logit(LOG_WARNING, "pcm hw_free %s error: %s\n", loop->capt->id, snd_strerror(err));
+		if ((err = snd_pcm_hw_free(loop->play->handle)) < 0)
+			logit(LOG_WARNING, "pcm hw_free %s error: %s\n", loop->play->id, snd_strerror(err));
+		loop->running = 0;
+	}
+	freeloop(loop);
+	return 0;
+}
+
+int pcmjob_pollfds_init(struct loopback *loop, struct pollfd *fds)
+{
+	int err, idx = 0;
+
+	if (loop->running) {
+		err = snd_pcm_poll_descriptors(loop->play->handle, fds + idx, loop->play->pollfd_count);
+		if (err < 0)
+			return err;
+		idx += loop->play->pollfd_count;
+		err = snd_pcm_poll_descriptors(loop->capt->handle, fds + idx, loop->capt->pollfd_count);
+		if (err < 0)
+			return err;
+		idx += loop->capt->pollfd_count;
+	}
+	if (loop->play->ctl_pollfd_count > 0 &&
+	    (loop->slave == SLAVE_TYPE_ON || loop->controls)) {
+		err = snd_ctl_poll_descriptors(loop->play->ctl, fds + idx, loop->play->ctl_pollfd_count);
+		if (err < 0)
+			return err;
+		idx += loop->play->ctl_pollfd_count;
+	}
+	if (loop->capt->ctl_pollfd_count > 0 &&
+	    (loop->slave == SLAVE_TYPE_ON || loop->controls)) {
+		err = snd_ctl_poll_descriptors(loop->capt->ctl, fds + idx, loop->capt->ctl_pollfd_count);
+		if (err < 0)
+			return err;
+		idx += loop->capt->ctl_pollfd_count;
+	}
+	loop->active_pollfd_count = idx;
+	return idx;
+}
+
+static snd_pcm_sframes_t get_queued_playback_samples(struct loopback *loop)
+{
+	snd_pcm_sframes_t delay;
+	int err;
+
+	if ((err = snd_pcm_delay(loop->play->handle, &delay)) < 0)
+		return 0;
+	loop->play->last_delay = delay;
+	delay += loop->play->buf_count;
+#ifdef USE_SAMPLERATE
+	delay += loop->src_out_frames;
+#endif
+	return delay;
+}
+
+static snd_pcm_sframes_t get_queued_capture_samples(struct loopback *loop)
+{
+	snd_pcm_sframes_t delay;
+	int err;
+
+	if ((err = snd_pcm_delay(loop->capt->handle, &delay)) < 0)
+		return 0;
+	loop->capt->last_delay = delay;
+	delay += loop->capt->buf_count;
+	return delay;
+}
+
+static int ctl_event_check(snd_ctl_elem_value_t *val, snd_ctl_event_t *ev)
+{
+	snd_ctl_elem_id_t *id1, *id2;
+	snd_ctl_elem_id_alloca(&id1);
+	snd_ctl_elem_id_alloca(&id2);
+	snd_ctl_elem_value_get_id(val, id1);
+	snd_ctl_event_elem_get_id(ev, id2);
+	if (snd_ctl_event_elem_get_mask(ev) == SND_CTL_EVENT_MASK_REMOVE)
+		return 0;
+	if ((snd_ctl_event_elem_get_mask(ev) & SND_CTL_EVENT_MASK_VALUE) == 0)
+		return 0;
+	return control_id_match(id1, id2);
+}
+
+static int handle_ctl_events(struct loopback_handle *lhandle,
+			     unsigned short events)
+{
+	struct loopback *loop = lhandle->loopback;
+	snd_ctl_event_t *ev;
+	int err, restart = 0;
+
+	snd_ctl_event_alloca(&ev);
+	while ((err = snd_ctl_read(lhandle->ctl, ev)) != 0 && err != -EAGAIN) {
+		if (err < 0)
+			break;
+		if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM)
+			continue;
+		if (lhandle == loop->play)
+			goto __ctl_check;
+		if (verbose > 6)
+			snd_output_printf(loop->output, "%s: ctl event!!!! %s\n", lhandle->id, snd_ctl_event_elem_get_name(ev));
+		if (ctl_event_check(lhandle->ctl_active, ev)) {
+			continue;
+		} else if (ctl_event_check(lhandle->ctl_format, ev)) {
+			err = get_format(lhandle);
+			if (lhandle->format != err)
+				restart = 1;
+			continue;
+		} else if (ctl_event_check(lhandle->ctl_rate, ev)) {
+			err = get_rate(lhandle);
+			if (lhandle->rate != err)
+				restart = 1;
+			continue;
+		} else if (ctl_event_check(lhandle->ctl_channels, ev)) {
+			err = get_channels(lhandle);
+			if (lhandle->channels != err)
+				restart = 1;
+			continue;
+		}
+	      __ctl_check:
+		control_event(lhandle, ev);
+	}
+	err = get_active(lhandle);
+	if (verbose > 7)
+		snd_output_printf(loop->output, "%s: ctl event active %i\n", lhandle->id, err);
+	if (!err) {
+		if (lhandle->loopback->running) {
+			loop->stop_pending = 1;
+			loop->stop_count = 0;
+		}
+	} else {
+		loop->stop_pending = 0;
+		if (loop->running == 0)
+			restart = 1;
+	}
+	if (restart) {
+		pcmjob_stop(loop);
+		err = pcmjob_start(loop);
+		if (err < 0)
+			return err;
+	}
+	return 1;
+}
+
+int pcmjob_pollfds_handle(struct loopback *loop, struct pollfd *fds)
+{
+	struct loopback_handle *play = loop->play;
+	struct loopback_handle *capt = loop->capt;
+	unsigned short prevents, crevents, events;
+	snd_pcm_uframes_t ccount, pcount;
+	int err, loopcount = 10, idx;
+
+	if (verbose > 11)
+		snd_output_printf(loop->output, "%s: pollfds handle\n", loop->id);
+	if (verbose > 13 || loop->xrun)
+		getcurtimestamp(&loop->tstamp_start);
+	if (verbose > 12) {
+		snd_pcm_sframes_t pdelay, cdelay;
+		if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0)
+			snd_output_printf(loop->output, "%s: delay error: %s / %li / %li\n", play->id, snd_strerror(err), play->buf_size, play->buf_count);
+		else
+			snd_output_printf(loop->output, "%s: delay %li / %li / %li\n", play->id, pdelay, play->buf_size, play->buf_count);
+		if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0)
+			snd_output_printf(loop->output, "%s: delay error: %s / %li / %li\n", capt->id, snd_strerror(err), capt->buf_size, capt->buf_count);
+		else
+			snd_output_printf(loop->output, "%s: delay %li / %li / %li\n", capt->id, cdelay, capt->buf_size, capt->buf_count);
+	}
+	idx = 0;
+	if (loop->running) {
+		err = snd_pcm_poll_descriptors_revents(play->handle, fds,
+						       play->pollfd_count,
+						       &prevents);
+		if (err < 0)
+			return err;
+		idx += play->pollfd_count;
+		err = snd_pcm_poll_descriptors_revents(capt->handle, fds + idx,
+						       capt->pollfd_count,
+						       &crevents);
+		if (err < 0)
+			return err;
+		idx += capt->pollfd_count;
+		if (loop->xrun) {
+			if (prevents || crevents) {
+				loop->xrun_last_wake = loop->xrun_last_wake0;
+				loop->xrun_last_wake0 = loop->tstamp_start;
+			}
+			loop->xrun_last_check = loop->xrun_last_check0;
+			loop->xrun_last_check0 = loop->tstamp_start;
+		}
+	} else {
+		prevents = crevents = 0;
+	}
+	if (play->ctl_pollfd_count > 0 &&
+	    (loop->slave == SLAVE_TYPE_ON || loop->controls)) {
+		err = snd_ctl_poll_descriptors_revents(play->ctl, fds + idx,
+						       play->ctl_pollfd_count,
+						       &events);
+		if (err < 0)
+			return err;
+		if (events) {
+			err = handle_ctl_events(play, events);
+			if (err == 1)
+				return 0;
+			if (err < 0)
+				return err;
+		}
+		idx += play->ctl_pollfd_count;
+	}
+	if (capt->ctl_pollfd_count > 0 &&
+	    (loop->slave == SLAVE_TYPE_ON || loop->controls)) {
+		err = snd_ctl_poll_descriptors_revents(capt->ctl, fds + idx,
+						       capt->ctl_pollfd_count,
+						       &events);
+		if (err < 0)
+			return err;
+		if (events) {
+			err = handle_ctl_events(capt, events);
+			if (err == 1)
+				return 0;
+			if (err < 0)
+				return err;
+		}
+		idx += capt->ctl_pollfd_count;
+	}
+	if (verbose > 9)
+		snd_output_printf(loop->output, "%s: prevents = 0x%x, crevents = 0x%x\n", loop->id, prevents, crevents);
+	if (!loop->running)
+		goto __pcm_end;
+	do {
+		ccount = readit(capt);
+		buf_add(loop, ccount);
+		if (capt->xrun_pending || loop->reinit)
+			break;
+		/* we read new samples, if we have a room in the playback
+		   buffer, feed them there */
+		pcount = writeit(play);
+		buf_remove(loop, pcount);
+		if (play->xrun_pending || loop->reinit)
+			break;
+		loopcount--;
+	} while ((ccount > 0 || pcount > 0) && loopcount > 0);
+	if (play->xrun_pending || capt->xrun_pending) {
+		if ((err = xrun_sync(loop)) < 0)
+			return err;
+	}
+	if (loop->reinit) {
+		err = pcmjob_stop(loop);
+		if (err < 0)
+			return err;
+		err = pcmjob_start(loop);
+		if (err < 0)
+			return err;
+	}
+	if (loop->sync != SYNC_TYPE_NONE &&
+	    play->counter >= play->sync_point &&
+	    capt->counter >= play->sync_point) {
+		snd_pcm_sframes_t diff, lat = get_whole_latency(loop);
+		diff = ((double)(((double)play->total_queued * play->pitch) +
+				 ((double)capt->total_queued * capt->pitch)) /
+			(double)loop->total_queued_count) - lat;
+		/* FIXME: this algorithm may be slightly better */
+		if (verbose > 3)
+			snd_output_printf(loop->output, "%s: sync diff %li old diff %li\n", loop->id, diff, loop->pitch_diff);
+		if (diff > 0) {
+			if (diff == loop->pitch_diff)
+				loop->pitch += loop->pitch_delta;
+			else if (diff > loop->pitch_diff)
+				loop->pitch += loop->pitch_delta*2;
+		} else if (diff < 0) {
+			if (diff == loop->pitch_diff)
+				loop->pitch -= loop->pitch_delta;
+			else if (diff < loop->pitch_diff)
+				loop->pitch -= loop->pitch_delta*2;
+		}
+		loop->pitch_diff = diff;
+		if (loop->pitch_diff_min > diff)
+			loop->pitch_diff_min = diff;
+		if (loop->pitch_diff_max < diff)
+			loop->pitch_diff_max = diff;
+		update_pitch(loop);
+		play->counter -= play->sync_point;
+		capt->counter -= play->sync_point;
+		play->total_queued = 0;
+		capt->total_queued = 0;
+		loop->total_queued_count = 0;
+	}
+	if (loop->sync != SYNC_TYPE_NONE) {
+		snd_pcm_sframes_t pqueued, cqueued;
+		pqueued = get_queued_playback_samples(loop);
+		cqueued = get_queued_capture_samples(loop);
+		if (verbose > 4)
+			snd_output_printf(loop->output, "%s: queued %li/%li samples\n", loop->id, pqueued, cqueued);
+		if (pqueued > 0)
+			play->total_queued += pqueued;
+		if (cqueued > 0)
+			capt->total_queued += cqueued;
+		if (pqueued > 0 || cqueued > 0)
+			loop->total_queued_count += 1;
+	}
+	if (verbose > 12) {
+		snd_pcm_sframes_t pdelay, cdelay;
+		if ((err = snd_pcm_delay(play->handle, &pdelay)) < 0)
+			snd_output_printf(loop->output, "%s: end delay error: %s / %li / %li\n", play->id, snd_strerror(err), play->buf_size, play->buf_count);
+		else
+			snd_output_printf(loop->output, "%s: end delay %li / %li / %li\n", play->id, pdelay, play->buf_size, play->buf_count);
+		if ((err = snd_pcm_delay(capt->handle, &cdelay)) < 0)
+			snd_output_printf(loop->output, "%s: end delay error: %s / %li / %li\n", capt->id, snd_strerror(err), capt->buf_size, capt->buf_count);
+		else
+			snd_output_printf(loop->output, "%s: end delay %li / %li / %li\n", capt->id, cdelay, capt->buf_size, capt->buf_count);
+	}
+      __pcm_end:
+	if (verbose > 13 || loop->xrun) {
+		long diff;
+		getcurtimestamp(&loop->tstamp_end);
+		diff = timediff(loop->tstamp_end, loop->tstamp_start);
+		if (verbose > 13)
+			snd_output_printf(loop->output, "%s: processing time %lius\n", loop->id, diff);
+		if (loop->xrun && loop->xrun_max_proctime < diff)
+			loop->xrun_max_proctime = diff;
+	}
+	return 0;
+}
+
+#define OUT(args...) \
+	snd_output_printf(loop->state, ##args)
+
+static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void show_handle(struct loopback_handle *lhandle, const char *id)
+{
+	struct loopback *loop = lhandle->loopback;
+
+	OUT("  %s: %s:\n", id, lhandle->id);
+	OUT("    device = '%s', ctldev '%s'\n", lhandle->device, lhandle->ctldev);
+	OUT("    card_number = %i\n", lhandle->card_number);
+	if (!loop->running)
+		return;
+	OUT("    access = %s, format = %s, rate = %u, channels = %u\n", snd_pcm_access_name(lhandle->access), snd_pcm_format_name(lhandle->format), lhandle->rate, lhandle->channels);
+	OUT("    buffer_size = %u, period_size = %u, avail_min = %li\n", lhandle->buffer_size, lhandle->period_size, lhandle->avail_min);
+	OUT("    xrun_pending = %i\n", lhandle->xrun_pending);
+	OUT("    buf_size = %li, buf_pos = %li, buf_count = %li, buf_over = %li\n", lhandle->buf_size, lhandle->buf_pos, lhandle->buf_count, lhandle->buf_over);
+	OUT("    pitch = %.8f\n", lhandle->pitch);
+}
+
+void pcmjob_state(struct loopback *loop)
+{
+	pthread_t self = pthread_self();
+	pthread_mutex_lock(&state_mutex);
+	OUT("State dump for thread %p job %i: %s:\n", (void *)self, loop->thread, loop->id);
+	OUT("  running = %i\n", loop->running);
+	OUT("  sync = %i\n", loop->sync);
+	OUT("  slave = %i\n", loop->slave);
+	if (!loop->running)
+		goto __skip;
+	OUT("  pollfd_count = %i\n", loop->pollfd_count);
+	OUT("  pitch = %.8f, delta = %.8f, diff = %li, min = %li, max = %li\n", loop->pitch, loop->pitch_delta, loop->pitch_diff, loop->pitch_diff_min, loop->pitch_diff_max);
+	OUT("  use_samplerate = %i\n", loop->use_samplerate);
+      __skip:
+	show_handle(loop->play, "playback");
+	show_handle(loop->capt, "capture");
+	pthread_mutex_unlock(&state_mutex);
+}
diff --git a/alsaloop/test.sh b/alsaloop/test.sh
new file mode 100755
index 0000000..fac72b9
--- /dev/null
+++ b/alsaloop/test.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+#DBG="gdb --args "
+#DBG="strace"
+#DBG="valgrind --leak-check=full"
+ARGS=
+CFGFILE="/tmp/alsaloop.test.cfg"
+
+test1() {
+  echo "TEST1"
+  $DBG ./alsaloop -C hw:1,0 -P plughw:0,0 \
+    --tlatency 50000 \
+    --mixer "name='Master Playback Volume'@name='Master Playback Volume'" \
+    --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \
+    --mixer "name='PCM Playback Volume'" \
+    --ossmixer "Master@VOLUME" \
+    --ossmixer "PCM@PCM" \
+    $ARGS
+}
+
+test2() {
+  echo "TEST2"
+cat > $CFGFILE <<EOF
+# first job
+-C hw:1,0,0 -P hw:0,0,0 --tlatency 50000 --thread 1 \
+    --mixer "name='Master Playback Volume'@name='Master Playback Volume'" \
+    --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \
+    --mixer "name='PCM Playback Volume'"
+# next line - second job
+-C hw:1,0,1 -P hw:0,1,0 --tlatency 50000 --thread 2
+EOF
+  $DBG ./alsaloop -d --config $CFGFILE $ARGS
+}
+
+test3() {
+  echo "TEST3"
+  LATENCY=180000
+cat > $CFGFILE <<EOF
+-C hw:1,0,0 -P plug:dmix:0 --tlatency $LATENCY --thread 0 \
+    --mixer "name='Master Playback Volume'@name='Master Playback Volume'" \
+    --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \
+    --mixer "name='PCM Playback Volume'" \
+    --ossmixer "name=Master@VOLUME"
+-C hw:1,0,1 -P plug:dmix:0 --tlatency $LATENCY --thread 1
+-C hw:1,0,2 -P plug:dmix:0 --tlatency $LATENCY --thread 2
+-C hw:1,0,3 -P plug:dmix:0 --tlatency $LATENCY --thread 3
+-C hw:1,0,4 -P plug:dmix:0 --tlatency $LATENCY --thread 4
+-C hw:1,0,5 -P plug:dmix:0 --tlatency $LATENCY --thread 5
+-C hw:1,0,6 -P plug:dmix:0 --tlatency $LATENCY --thread 6
+-C hw:1,0,7 -P plug:dmix:0 --tlatency $LATENCY --thread 7
+EOF
+  $DBG ./alsaloop --config $CFGFILE $ARGS
+}
+
+test4() {
+  echo "TEST4"
+  $DBG ./alsaloop -C hw:1,0 -P plughw:0,0 -a off -r 11025 \
+    --tlatency 50000 \
+    --mixer "name='Master Playback Volume'@name='Master Playback Volume'" \
+    --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \
+    --mixer "name='PCM Playback Volume'" \
+    $ARGS
+}
+
+test5() {
+  echo "TEST5"
+cat > $CFGFILE <<EOF
+-C hw:1,0,0 -P plughw:0,0 --tlatency 50000 --thread 1 \
+    --mixer "name='Master Playback Volume'@name='Master Playback Volume'" \
+    --mixer "name='Master Playback Switch'@name='Master Playback Switch'" \
+    --mixer "name='PCM Playback Volume'" \
+    --ossmixer "name=Master@VOLUME"
+-C hw:1,0,1 -P plughw:0,1 --tlatency 50000 --thread 2
+EOF
+  $DBG ./alsaloop --config $CFGFILE $ARGS
+}
+
+sigusr1() {
+	pid=$(ps ax | grep alsaloop | grep -v grep | colrm 7 255)
+	if test -n "$pid"; then
+		echo "Killing alsaloop $pid..."
+		kill -SIGUSR1 $pid
+	fi
+}
+
+case "$1" in
+test1) shift; ARGS="$@"; test1 ;;
+test2) shift; ARGS="$@"; test2 ;;
+test3) shift; ARGS="$@"; test3 ;;
+test4) shift; ARGS="$@"; test4 ;;
+test5) shift; ARGS="$@"; test5 ;;
+usr|sig*) sigusr1 ;;
+*) ARGS="$@"; test1 ;;
+esac
diff --git a/alsamixer/Makefile.am b/alsamixer/Makefile.am
index 1de47c6..8a82323 100644
--- a/alsamixer/Makefile.am
+++ b/alsamixer/Makefile.am
@@ -15,6 +15,7 @@
 		proc_files.c proc_files.h \
 		textbox.c textbox.h \
 		utils.c utils.h \
+		volume_mapping.c volume_mapping.h \
 		widget.c widget.h
 man_MANS = alsamixer.1
 EXTRA_DIST = alsamixer.1
diff --git a/alsamixer/alsamixer.1 b/alsamixer/alsamixer.1
index ba05aca..cd88404 100644
--- a/alsamixer/alsamixer.1
+++ b/alsamixer/alsamixer.1
@@ -171,7 +171,7 @@
 
 .SH AUTHOR
 .B alsamixer
-has been written by Tim Janik <timj@gtk.org> and
+has been written by Tim Janik and
 been further improved by Jaroslav Kysela <perex@perex.cz>
 and Clemens Ladisch <clemens@ladisch.de>.
 
diff --git a/alsamixer/cli.c b/alsamixer/cli.c
index ab6255f..3898196 100644
--- a/alsamixer/cli.c
+++ b/alsamixer/cli.c
@@ -1,6 +1,6 @@
 /*
  * alsamixer - curses mixer for the ALSA project
- * Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
+ * Copyright (c) 1998,1999 Tim Janik
  *                         Jaroslav Kysela <perex@perex.cz>
  * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
  *
diff --git a/alsamixer/colors.c b/alsamixer/colors.c
index fcceb16..b4b98e5 100644
--- a/alsamixer/colors.c
+++ b/alsamixer/colors.c
@@ -1,6 +1,6 @@
 /*
  * colors.c - color and attribute definitions
- * Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
+ * Copyright (c) 1998,1999 Tim Janik
  *                         Jaroslav Kysela <perex@perex.cz>
  * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
  *
diff --git a/alsamixer/mixer_controls.c b/alsamixer/mixer_controls.c
index 796df7b..cc98b64 100644
--- a/alsamixer/mixer_controls.c
+++ b/alsamixer/mixer_controls.c
@@ -1,6 +1,6 @@
 /*
  * mixer_controls.c - handles mixer controls and mapping from selems
- * Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
+ * Copyright (c) 1998,1999 Tim Janik
  *                         Jaroslav Kysela <perex@perex.cz>
  * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
  *
diff --git a/alsamixer/mixer_display.c b/alsamixer/mixer_display.c
index 20d6d6a..51a1546 100644
--- a/alsamixer/mixer_display.c
+++ b/alsamixer/mixer_display.c
@@ -17,10 +17,12 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define _C99_SOURCE /* lrint() */
 #include "aconfig.h"
 #include <stdlib.h>
 #include <string.h>
 #include <strings.h>
+#include <math.h>
 #include CURSESINC
 #include <alsa/asoundlib.h>
 #include "gettext_curses.h"
@@ -28,6 +30,7 @@
 #include "mem.h"
 #include "colors.h"
 #include "widget.h"
+#include "volume_mapping.h"
 #include "mixer_widget.h"
 #include "mixer_controls.h"
 #include "mixer_display.h"
@@ -390,24 +393,14 @@
 	display_string_in_field(y, x, s, width, ALIGN_CENTER);
 }
 
-static long clamp(long value, long min, long max)
-{
-	if (value < min)
-		return min;
-	if (value > max)
-		return max;
-	return value;
-}
-
 static void display_control(unsigned int control_index)
 {
 	struct control *control;
 	int col;
 	int i, c;
 	int left, frame_left;
-	int bar_height, value;
-	long volumes[2];
-	long min, max;
+	int bar_height;
+	double volumes[2];
 	int switches[2];
 	unsigned int index;
 	const char *s;
@@ -452,35 +445,22 @@
 		waddch(mixer_widget.window, ACS_LRCORNER);
 	}
 	if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
-		int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
+		double (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
 
 		if (control->flags & TYPE_PVOLUME)
-			get_vol_func = snd_mixer_selem_get_playback_volume;
+			get_vol_func = get_normalized_playback_volume;
 		else
-			get_vol_func = snd_mixer_selem_get_capture_volume;
-		err = get_vol_func(control->elem, control->volume_channels[0], &volumes[0]);
-		if (err >= 0 && (control->flags & HAS_VOLUME_1))
-			err = get_vol_func(control->elem, control->volume_channels[1], &volumes[1]);
+			get_vol_func = get_normalized_capture_volume;
+		volumes[0] = get_vol_func(control->elem, control->volume_channels[0]);
+		if (control->flags & HAS_VOLUME_1)
+			volumes[1] = get_vol_func(control->elem, control->volume_channels[1]);
 		else
 			volumes[1] = volumes[0];
-		if (err < 0)
-			return;
-		if (control->flags & TYPE_PVOLUME)
-			err = snd_mixer_selem_get_playback_volume_range(control->elem, &min, &max);
-		else
-			err = snd_mixer_selem_get_capture_volume_range(control->elem, &min, &max);
-		if (err < 0)
-			return;
-		if (min >= max)
-			max = min + 1;
-		volumes[0] = clamp(volumes[0], min, max);
-		volumes[1] = clamp(volumes[1], min, max);
 
 		if (control->flags & IS_ACTIVE)
 			wattrset(mixer_widget.window, 0);
 		for (c = 0; c < 2; c++) {
-			bar_height = ((volumes[c] - min) * volume_height +
-				      max - min - 1) / (max - min);
+			bar_height = lrint(volumes[c] * volume_height);
 			for (i = 0; i < volume_height; ++i) {
 				chtype ch;
 				if (i + 1 > bar_height)
@@ -505,19 +485,18 @@
 		}
 		if (control->flags & IS_ACTIVE)
 			wattrset(mixer_widget.window, attr_mixer_active);
-		value = ((volumes[0] - min) * 100 + (max - min) / 2) / (max - min);
 		if (!(control->flags & HAS_VOLUME_1)) {
-			sprintf(buf, "%d", value);
+			sprintf(buf, "%d", lrint(volumes[0] * 100));
 			display_string_in_field(values_y, frame_left - 2, buf, 8, ALIGN_CENTER);
 		} else {
-			mvwprintw(mixer_widget.window, values_y, frame_left - 2, "%3d", value);
+			mvwprintw(mixer_widget.window, values_y, frame_left - 2,
+				  "%3d", lrint(volumes[0] * 100));
 			if (control->flags & IS_ACTIVE)
 				wattrset(mixer_widget.window, attr_ctl_frame);
 			waddstr(mixer_widget.window, "<>");
 			if (control->flags & IS_ACTIVE)
 				wattrset(mixer_widget.window, attr_mixer_active);
-			value = ((volumes[1] - min) * 100 + (max - min) / 2) / (max - min);
-			wprintw(mixer_widget.window, "%-3d", value);
+			wprintw(mixer_widget.window, "%-3d", lrint(volumes[1] * 100));
 		}
 	}
 
diff --git a/alsamixer/mixer_widget.c b/alsamixer/mixer_widget.c
index 796ea1d..fb352d3 100644
--- a/alsamixer/mixer_widget.c
+++ b/alsamixer/mixer_widget.c
@@ -1,6 +1,6 @@
 /*
  * mixer_widget.c - mixer widget and keys handling
- * Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
+ * Copyright (c) 1998,1999 Tim Janik
  *                         Jaroslav Kysela <perex@perex.cz>
  * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
  *
@@ -33,6 +33,7 @@
 #include "textbox.h"
 #include "proc_files.h"
 #include "card_select.h"
+#include "volume_mapping.h"
 #include "mixer_controls.h"
 #include "mixer_display.h"
 #include "mixer_widget.h"
@@ -211,7 +212,7 @@
 		_("; '        Toggle left/right capture"),
 		"",
 		_("Authors:"),
-		_("  Tim Janik <timj@gtk.org>"),
+		_("  Tim Janik"),
 		_("  Jaroslav Kysela <perex@perex.cz>"),
 		_("  Clemens Ladisch <clemens@ladisch.de>"),
 	};
@@ -295,80 +296,57 @@
 
 static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
 {
-	int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
-	int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
-	long min, max;
-	int err;
+	int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
 
 	if (!(control->flags & HAS_VOLUME_1))
 		channels = LEFT;
-	if (control->flags & TYPE_PVOLUME) {
-		get_range_func = snd_mixer_selem_get_playback_volume_range;
-		set_func = snd_mixer_selem_set_playback_volume;
-	} else {
-		get_range_func = snd_mixer_selem_get_capture_volume_range;
-		set_func = snd_mixer_selem_set_capture_volume;
-	}
-	err = get_range_func(control->elem, &min, &max);
-	if (err < 0)
-		return;
+	if (control->flags & TYPE_PVOLUME)
+		set_func = set_normalized_playback_volume;
+	else
+		set_func = set_normalized_capture_volume;
 	if (channels & LEFT)
-		set_func(control->elem, control->volume_channels[0], min + (max - min) * value / 100);
+		set_func(control->elem, control->volume_channels[0], value / 100.0, 0);
 	if (channels & RIGHT)
-		set_func(control->elem, control->volume_channels[1], min + (max - min) * value / 100);
+		set_func(control->elem, control->volume_channels[1], value / 100.0, 0);
+}
+
+static double clamp_volume(double v)
+{
+	if (v < 0)
+		return 0;
+	if (v > 1)
+		return 1;
+	return v;
 }
 
 static void change_volume_relative(struct control *control, int delta, unsigned int channels)
 {
-	int (*get_range_func)(snd_mixer_elem_t *, long *, long *);
-	int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
-	int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long);
-	long min, max;
-	long left, right;
-	long value;
-	int err;
+	double (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
+	int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, double, int);
+	double left, right;
+	int dir;
 
 	if (!(control->flags & HAS_VOLUME_1))
 		channels = LEFT;
 	if (control->flags & TYPE_PVOLUME) {
-		get_range_func = snd_mixer_selem_get_playback_volume_range;
-		get_func = snd_mixer_selem_get_playback_volume;
-		set_func = snd_mixer_selem_set_playback_volume;
+		get_func = get_normalized_playback_volume;
+		set_func = set_normalized_playback_volume;
 	} else {
-		get_range_func = snd_mixer_selem_get_capture_volume_range;
-		get_func = snd_mixer_selem_get_capture_volume;
-		set_func = snd_mixer_selem_set_capture_volume;
+		get_func = get_normalized_capture_volume;
+		set_func = set_normalized_capture_volume;
 	}
-	err = get_range_func(control->elem, &min, &max);
-	if (err < 0)
-		return;
+	if (channels & LEFT)
+		left = get_func(control->elem, control->volume_channels[0]);
+	if (channels & RIGHT)
+		right = get_func(control->elem, control->volume_channels[1]);
+	dir = delta > 0 ? 1 : -1;
 	if (channels & LEFT) {
-		err = get_func(control->elem, control->volume_channels[0], &left);
-		if (err < 0)
-			return;
+		left = clamp_volume(left + delta / 100.0);
+		set_func(control->elem, control->volume_channels[0], left, dir);
 	}
 	if (channels & RIGHT) {
-		err = get_func(control->elem, control->volume_channels[1], &right);
-		if (err < 0)
-			return;
-	}
-	if (channels & LEFT) {
-		value = left + delta;
-		if (value < min)
-			value = min;
-		else if (value > max)
-			value = max;
-		if (value != left)
-			set_func(control->elem, control->volume_channels[0], value);
-	}
-	if (channels & RIGHT) {
-		value = right + delta;
-		if (value < min)
-			value = min;
-		else if (value > max)
-			value = max;
-		if (value != right)
-			set_func(control->elem, control->volume_channels[1], value);
+		right = clamp_volume(right + delta / 100.0);
+		set_func(control->elem, control->volume_channels[1], right, dir);
 	}
 }
 
@@ -458,34 +436,26 @@
 static void balance_volumes(void)
 {
 	struct control *control;
-	long left, right;
+	double left, right;
 	int err;
 
 	control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME);
 	if (!control || !(control->flags & HAS_VOLUME_1))
 		return;
 	if (control->flags & TYPE_PVOLUME) {
-		err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[0], &left);
-		if (err < 0)
-			return;
-		err = snd_mixer_selem_get_playback_volume(control->elem, control->volume_channels[1], &right);
-		if (err < 0)
-			return;
+		left = get_normalized_playback_volume(control->elem, control->volume_channels[0]);
+		right = get_normalized_playback_volume(control->elem, control->volume_channels[1]);
 	} else {
-		err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[0], &left);
-		if (err < 0)
-			return;
-		err = snd_mixer_selem_get_capture_volume(control->elem, control->volume_channels[1], &right);
-		if (err < 0)
-			return;
+		left = get_normalized_capture_volume(control->elem, control->volume_channels[0]);
+		right = get_normalized_capture_volume(control->elem, control->volume_channels[1]);
 	}
 	left = (left + right) / 2;
 	if (control->flags & TYPE_PVOLUME) {
-		snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[0], left);
-		snd_mixer_selem_set_playback_volume(control->elem, control->volume_channels[1], left);
+		set_normalized_playback_volume(control->elem, control->volume_channels[0], left, 0);
+		set_normalized_playback_volume(control->elem, control->volume_channels[1], left, 0);
 	} else {
-		snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[0], left);
-		snd_mixer_selem_set_capture_volume(control->elem, control->volume_channels[1], left);
+		set_normalized_capture_volume(control->elem, control->volume_channels[0], left, 0);
+		set_normalized_capture_volume(control->elem, control->volume_channels[1], left, 0);
 	}
 	display_controls();
 }
diff --git a/alsamixer/textbox.c b/alsamixer/textbox.c
index d743a14..a979d3c 100644
--- a/alsamixer/textbox.c
+++ b/alsamixer/textbox.c
@@ -1,6 +1,6 @@
 /*
  * textbox.c - show a text box for messages, files or help
- * Copyright (c) 1998,1999 Tim Janik <timj@gtk.org>
+ * Copyright (c) 1998,1999 Tim Janik
  *                         Jaroslav Kysela <perex@perex.cz>
  * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
  *
diff --git a/alsamixer/volume_mapping.c b/alsamixer/volume_mapping.c
new file mode 100644
index 0000000..9cacad8
--- /dev/null
+++ b/alsamixer/volume_mapping.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2010 Clemens Ladisch <clemens@ladisch.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * The functions in this file map the value ranges of ALSA mixer controls onto
+ * the interval 0..1.
+ *
+ * The mapping is designed so that the position in the interval is proportional
+ * to the volume as a human ear would perceive it (i.e., the position is the
+ * cubic root of the linear sample multiplication factor).  For controls with
+ * a small range (24 dB or less), the mapping is linear in the dB values so
+ * that each step has the same size visually.  Only for controls without dB
+ * information, a linear mapping of the hardware volume register values is used
+ * (this is the same algorithm as used in the old alsamixer).
+ *
+ * When setting the volume, 'dir' is the rounding direction:
+ * -1/0/1 = down/nearest/up.
+ */
+
+#define _ISOC99_SOURCE /* lrint() */
+#define _GNU_SOURCE /* exp10() */
+#include "aconfig.h"
+#include <math.h>
+#include <stdbool.h>
+#include "volume_mapping.h"
+
+#define MAX_LINEAR_DB_SCALE	24
+
+static inline bool use_linear_dB_scale(long dBmin, long dBmax)
+{
+	return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100;
+}
+
+static long lrint_dir(double x, int dir)
+{
+	if (dir > 0)
+		return lrint(ceil(x));
+	else if (dir < 0)
+		return lrint(floor(x));
+	else
+		return lrint(x);
+}
+
+enum ctl_dir { PLAYBACK, CAPTURE };
+
+static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = {
+	snd_mixer_selem_get_playback_dB_range,
+	snd_mixer_selem_get_capture_dB_range,
+};
+static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = {
+	snd_mixer_selem_get_playback_volume_range,
+	snd_mixer_selem_get_capture_volume_range,
+};
+static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
+	snd_mixer_selem_get_playback_dB,
+	snd_mixer_selem_get_capture_dB,
+};
+static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = {
+	snd_mixer_selem_get_playback_volume,
+	snd_mixer_selem_get_capture_volume,
+};
+static int (* const set_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long, int) = {
+	snd_mixer_selem_set_playback_dB,
+	snd_mixer_selem_set_capture_dB,
+};
+static int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = {
+	snd_mixer_selem_set_playback_volume,
+	snd_mixer_selem_set_capture_volume,
+};
+
+static double get_normalized_volume(snd_mixer_elem_t *elem,
+				    snd_mixer_selem_channel_id_t channel,
+				    enum ctl_dir ctl_dir)
+{
+	long min, max, value;
+	double normalized, min_norm;
+	int err;
+
+	err = get_dB_range[ctl_dir](elem, &min, &max);
+	if (err < 0 || min >= max) {
+		err = get_raw_range[ctl_dir](elem, &min, &max);
+		if (err < 0 || min == max)
+			return 0;
+
+		err = get_raw[ctl_dir](elem, channel, &value);
+		if (err < 0)
+			return 0;
+
+		return (value - min) / (double)(max - min);
+	}
+
+	err = get_dB[ctl_dir](elem, channel, &value);
+	if (err < 0)
+		return 0;
+
+	if (use_linear_dB_scale(min, max))
+		return (value - min) / (double)(max - min);
+
+	normalized = exp10((value - max) / 6000.0);
+	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
+		min_norm = exp10((min - max) / 6000.0);
+		normalized = (normalized - min_norm) / (1 - min_norm);
+	}
+
+	return normalized;
+}
+
+static int set_normalized_volume(snd_mixer_elem_t *elem,
+				 snd_mixer_selem_channel_id_t channel,
+				 double volume,
+				 int dir,
+				 enum ctl_dir ctl_dir)
+{
+	long min, max, value;
+	double min_norm;
+	int err;
+
+	err = get_dB_range[ctl_dir](elem, &min, &max);
+	if (err < 0 || min >= max) {
+		err = get_raw_range[ctl_dir](elem, &min, &max);
+		if (err < 0)
+			return err;
+
+		value = lrint_dir(volume * (max - min), dir) + min;
+		return set_raw[ctl_dir](elem, channel, value);
+	}
+
+	if (use_linear_dB_scale(min, max)) {
+		value = lrint_dir(volume * (max - min), dir) + min;
+		return set_dB[ctl_dir](elem, channel, value, dir);
+	}
+
+	if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
+		min_norm = exp10((min - max) / 6000.0);
+		volume = volume * (1 - min_norm) + min_norm;
+	}
+	value = lrint_dir(6000.0 * log10(volume), dir) + max;
+	return set_dB[ctl_dir](elem, channel, value, dir);
+}
+
+double get_normalized_playback_volume(snd_mixer_elem_t *elem,
+				      snd_mixer_selem_channel_id_t channel)
+{
+	return get_normalized_volume(elem, channel, PLAYBACK);
+}
+
+double get_normalized_capture_volume(snd_mixer_elem_t *elem,
+				     snd_mixer_selem_channel_id_t channel)
+{
+	return get_normalized_volume(elem, channel, CAPTURE);
+}
+
+int set_normalized_playback_volume(snd_mixer_elem_t *elem,
+				   snd_mixer_selem_channel_id_t channel,
+				   double volume,
+				   int dir)
+{
+	return set_normalized_volume(elem, channel, volume, dir, PLAYBACK);
+}
+
+int set_normalized_capture_volume(snd_mixer_elem_t *elem,
+				  snd_mixer_selem_channel_id_t channel,
+				  double volume,
+				  int dir)
+{
+	return set_normalized_volume(elem, channel, volume, dir, CAPTURE);
+}
diff --git a/alsamixer/volume_mapping.h b/alsamixer/volume_mapping.h
new file mode 100644
index 0000000..d4251d6
--- /dev/null
+++ b/alsamixer/volume_mapping.h
@@ -0,0 +1,19 @@
+#ifndef VOLUME_MAPPING_H_INCLUDED
+#define VOLUME_MAPPING_H_INCLUDED
+
+#include <alsa/asoundlib.h>
+
+double get_normalized_playback_volume(snd_mixer_elem_t *elem,
+				      snd_mixer_selem_channel_id_t channel);
+double get_normalized_capture_volume(snd_mixer_elem_t *elem,
+				     snd_mixer_selem_channel_id_t channel);
+int set_normalized_playback_volume(snd_mixer_elem_t *elem,
+				   snd_mixer_selem_channel_id_t channel,
+				   double volume,
+				   int dir);
+int set_normalized_capture_volume(snd_mixer_elem_t *elem,
+				  snd_mixer_selem_channel_id_t channel,
+				  double volume,
+				  int dir);
+
+#endif
diff --git a/amixer/amixer.c b/amixer/amixer.c
index c9ea572..a177288 100644
--- a/amixer/amixer.c
+++ b/amixer/amixer.c
@@ -1120,8 +1120,8 @@
 					}
 					str++;
 				}
-				*ptr = '\0';
 			}
+			*ptr = '\0';
 			snd_ctl_elem_id_set_name(id, buf);
 		} else if (!strncasecmp(str, "index=", 6)) {
 			str += 6;
diff --git a/configure.in b/configure.in
index a6d6d3d..f784363 100644
--- a/configure.in
+++ b/configure.in
@@ -26,6 +26,7 @@
 AC_PROG_CC
 dnl AC_PROG_CXX
 AC_PROG_INSTALL
+AC_PROG_MKDIR_P
 AC_PROG_LN_S
 AM_PATH_ALSA(1.0.16)
 
@@ -38,11 +39,14 @@
   [#include <alsa/asoundlib.h>])
 AC_CHECK_HEADERS([alsa/seq.h], [have_seq="yes"], [have_seq="no"],
   [#include <alsa/asoundlib.h>])
+AC_CHECK_HEADERS([samplerate.h], [have_samplerate="yes"], [have_samplerate="no"],
+  [#include <samplerate.h>])
 
 AM_CONDITIONAL(HAVE_PCM, test "$have_pcm" = "yes")
 AM_CONDITIONAL(HAVE_MIXER, test "$have_mixer" = "yes")
 AM_CONDITIONAL(HAVE_RAWMIDI, test "$have_rawmidi" = "yes")
 AM_CONDITIONAL(HAVE_SEQ, test "$have_seq" = "yes")
+AM_CONDITIONAL(HAVE_SAMPLERATE, test "$have_samplerate" = "yes")
 
 dnl Check for librt
 LIBRT=""
@@ -87,17 +91,31 @@
      esac],[alsaconf=true])
 AM_CONDITIONAL(ALSACONF, test x$alsaconf = xtrue)
 
+dnl Disable alsaloop
+AC_ARG_ENABLE(alsaloop,
+     [  --disable-alsaloop      Disable alsaloop packaging],
+     [case "${enableval}" in
+       yes) alsaloop=true ;;
+       no)  alsaloop=false ;;
+       *) AC_MSG_ERROR(bad value ${enableval} for --enable-alsaloop) ;;
+     esac],[alsaloop=true])
+AM_CONDITIONAL(ALSALOOP, test x$alsaloop = xtrue)
+
 xmlto=""
-if test x"$alsaconf" = xtrue; then
-   AC_ARG_ENABLE(xmlto,
-     AS_HELP_STRING([--disable-xmlto], [Disable man page creation via xmlto]),
-     xmlto="$enableval", xmlto="yes")
-   if test "$xmlto" = "yes"; then
-      AC_CHECK_PROG([xmlto], [xmlto], [yes])
-   fi
+AC_ARG_ENABLE(xmlto,
+ AS_HELP_STRING([--disable-xmlto], [Disable man page creation via xmlto]),
+ xmlto="$enableval", xmlto="yes")
+if test "$xmlto" = "yes"; then
+  AC_CHECK_PROG([xmlto], [xmlto], [yes])
 fi
 AM_CONDITIONAL(USE_XMLTO, test x"$xmlto" = xyes)
 
+AC_ARG_WITH(
+        [udev-rules-dir],
+        AS_HELP_STRING([--with-udev-rules-dir],[Directory where to install udev rules to (defaults to /lib/udev/rules.d)]),
+        [udevrulesdir=$withval], [udevrulesdir="/lib/udev/rules.d"])
+AC_SUBST(udevrulesdir)
+
 dnl Checks for header files.
 AC_HEADER_STDC
 if test x$alsamixer = xtrue; then
@@ -117,7 +135,7 @@
       CURSES_CFLAGS=`ncursesw5-config --cflags`
       curseslib="ncursesw"
     else
-      AC_CHECK_LIB(ncursesw, initscr, 
+      AC_CHECK_LIB(ncursesw, initscr,
                  [ CURSESINC='<ncurses.h>'; CURSESLIB='-lncursesw'; curseslib="ncursesw"])
     fi
     if test -n "$CURSESINC"; then
@@ -134,12 +152,12 @@
       CURSES_CFLAGS=`ncurses5-config --cflags`
       curseslib="ncurses"
     else
-      AC_CHECK_LIB(ncurses, initscr, 
+      AC_CHECK_LIB(ncurses, initscr,
                  [ CURSESINC='<ncurses.h>'; CURSESLIB='-lncurses'; curseslib="ncurses"])
     fi
   fi
   if test "$curseslib" = "curses" -o "$curseslib" = "auto"; then
-    AC_CHECK_LIB(curses, initscr, 
+    AC_CHECK_LIB(curses, initscr,
                  [ CURSESINC='<curses.h>'; CURSESLIB='-lcurses'; curseslib="curses"])
   fi
   if test -z "$CURSESINC"; then
@@ -265,6 +283,22 @@
 
 AC_SUBST(LIBRT)
 
+dnl Check for systemd
+PKG_PROG_PKG_CONFIG
+AC_ARG_WITH([systemdsystemunitdir],
+        AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]),
+        [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
+if test "x$with_systemdsystemunitdir" != xno; then
+        AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
+fi
+AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ])
+
+AC_ARG_WITH([asound-state-dir],
+        AS_HELP_STRING([--with-asound-state-dir=DIR], [Directory to place asound.state file in]),
+        [ASOUND_STATE_DIR="$withval"],
+        [ASOUND_STATE_DIR="/var/lib/alsa"])
+AC_SUBST(ASOUND_STATE_DIR)
+
 AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/Makefile \
 	  alsamixer/Makefile amidi/Makefile amixer/Makefile \
 	  m4/Makefile po/Makefile.in \
@@ -274,4 +308,5 @@
 	  aplay/Makefile include/Makefile iecset/Makefile utils/Makefile \
 	  utils/alsa-utils.spec seq/Makefile seq/aconnect/Makefile \
 	  seq/aplaymidi/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \
-	  speaker-test/Makefile speaker-test/samples/Makefile)
+	  speaker-test/Makefile speaker-test/samples/Makefile \
+	  alsaloop/Makefile)
diff --git a/po/LINGUAS b/po/LINGUAS
index 081e105..2027386 100644
--- a/po/LINGUAS
+++ b/po/LINGUAS
@@ -1 +1 @@
-ja de
+de fr ja
diff --git a/po/de.po b/po/de.po
index 7cae06e..0c47c65 100644
--- a/po/de.po
+++ b/po/de.po
@@ -5,10 +5,10 @@
 #
 msgid ""
 msgstr ""
-"Project-Id-Version: alsa-utils 1.0.20\n"
+"Project-Id-Version: alsa-utils 1.0.23\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-05-24 19:56+0200\n"
-"PO-Revision-Date: 2009-05-24 12:34+0200\n"
+"POT-Creation-Date: 2010-11-08 21:32+0100\n"
+"PO-Revision-Date: 2010-11-09 21:12+0100\n"
 "Last-Translator: Clemens Ladisch <clemens@ladisch.de>\n"
 "Language-Team: German\n"
 "MIME-Version: 1.0\n"
@@ -179,47 +179,47 @@
 msgstr "Dieses Gerät hat keine Regler."
 
 #. TRANSLATORS: playback on; one character
-#: alsamixer/mixer_display.c:512 alsamixer/mixer_display.c:517
+#: alsamixer/mixer_display.c:537 alsamixer/mixer_display.c:542
 msgid "O"
 msgstr "O"
 
 #. TRANSLATORS: playback muted; one character
-#: alsamixer/mixer_display.c:514 alsamixer/mixer_display.c:518
+#: alsamixer/mixer_display.c:539 alsamixer/mixer_display.c:543
 msgid "M"
 msgstr "M"
 
 #. TRANSLATORS: "left"; no more than two characters
-#: alsamixer/mixer_display.c:532
+#: alsamixer/mixer_display.c:557
 msgid "L"
 msgstr "L"
 
 #. TRANSLATORS: "right"; no more than two characters
-#: alsamixer/mixer_display.c:536
+#: alsamixer/mixer_display.c:561
 msgid "R"
 msgstr "R"
 
 #. TRANSLATORS: no more than eight characters
-#: alsamixer/mixer_display.c:538
+#: alsamixer/mixer_display.c:563
 msgid "CAPTURE"
 msgstr "AUFNAHME"
 
-#: alsamixer/mixer_display.c:588
+#: alsamixer/mixer_display.c:613
 msgid "Front"
 msgstr "Vorne"
 
-#: alsamixer/mixer_display.c:591
+#: alsamixer/mixer_display.c:616
 msgid "Rear"
 msgstr "Hinten"
 
-#: alsamixer/mixer_display.c:594 speaker-test/speaker-test.c:106
+#: alsamixer/mixer_display.c:619 speaker-test/speaker-test.c:108
 msgid "Center"
 msgstr "Mitte"
 
-#: alsamixer/mixer_display.c:597
+#: alsamixer/mixer_display.c:622
 msgid "Woofer"
 msgstr "Bass"
 
-#: alsamixer/mixer_display.c:600
+#: alsamixer/mixer_display.c:625
 msgid "Side"
 msgstr "Seiten"
 
@@ -336,8 +336,8 @@
 msgstr "Autoren:"
 
 #: alsamixer/mixer_widget.c:214
-msgid "  Tim Janik <timj@gtk.org>"
-msgstr "  Tim Janik <tim@gtk.org>"
+msgid "  Tim Janik"
+msgstr "  Tim Janik"
 
 #: alsamixer/mixer_widget.c:215
 msgid "  Jaroslav Kysela <perex@perex.cz>"
@@ -364,23 +364,23 @@
 msgid "Cannot open file \"%s\"."
 msgstr "Fehler beim Öffnen der Datei \"%s\"."
 
-#: aplay/aplay.c:139
+#: aplay/aplay.c:152
 msgid "raw data"
 msgstr "Rohdaten"
 
-#: aplay/aplay.c:140
+#: aplay/aplay.c:153
 msgid "VOC"
 msgstr "VOC"
 
-#: aplay/aplay.c:142
+#: aplay/aplay.c:155
 msgid "WAVE"
 msgstr "WAVE"
 
-#: aplay/aplay.c:143
+#: aplay/aplay.c:156
 msgid "Sparc Audio"
 msgstr "Sparc-Audio"
 
-#: aplay/aplay.c:164
+#: aplay/aplay.c:177
 #, c-format
 msgid ""
 "Usage: %s [OPTION]... [FILE]...\n"
@@ -419,6 +419,11 @@
 "                        expression for validation is: coef * (buffer_size / "
 "2)\n"
 "    --test-nowait       do not wait for ring buffer - eats whole CPU\n"
+"    --max-file-time=#   start another output file when the old file has "
+"recorded\n"
+"                        for this many seconds\n"
+"    --process-id-file   write the process ID here\n"
+"    --use-strftime      apply the strftime facility to the output file name\n"
 msgstr ""
 "Verwendung: %s [Option]... [Datei]...\n"
 "\n"
@@ -456,13 +461,16 @@
 "2)\n"
 "    --test-nowait       kein Warten auf Ringpuffer; beansprucht volle CPU-"
 "Leistung\n"
+"    --max-file-time=#   erzeuge mehrere Dateien; Wechsel nach # Sekunden\n"
+"    --process-id-file=# schreibe Prozess-ID in diese Datei\n"
+"    --use-strftime      formatiere Dateiname mit strftime; %%v=Dateinummer\n"
 
-#: aplay/aplay.c:199 speaker-test/speaker-test.c:740
+#: aplay/aplay.c:216 speaker-test/speaker-test.c:819
 #, c-format
 msgid "Recognized sample formats are:"
 msgstr "Unterstützte Sample-Formate:"
 
-#: aplay/aplay.c:205
+#: aplay/aplay.c:222
 #, c-format
 msgid ""
 "\n"
@@ -471,115 +479,120 @@
 "\n"
 "Nicht alle davon sind auf jeder Hardware verfügbar.\n"
 
-#: aplay/aplay.c:206
+#: aplay/aplay.c:223
 #, c-format
 msgid "The availabled format shortcuts are:\n"
 msgstr "Unterstütze Format-Abkürzungen:\n"
 
-#: aplay/aplay.c:207
+#: aplay/aplay.c:224
 #, c-format
 msgid "-f cd (16 bit little endian, 44100, stereo)\n"
 msgstr "-f cd (16 Bits, Little Endian, 44100 Hz, stereo)\n"
 
-#: aplay/aplay.c:208
+#: aplay/aplay.c:225
 #, c-format
 msgid "-f cdr (16 bit big endian, 44100, stereo)\n"
 msgstr "-f cdr (16 Bits, Big Endian, 44100 Hz, stereo)\n"
 
-#: aplay/aplay.c:209
+#: aplay/aplay.c:226
 #, c-format
 msgid "-f dat (16 bit little endian, 48000, stereo)\n"
 msgstr "-f dat (16 Bits, Little Endian, 48000 Hz, stereo)\n"
 
-#: aplay/aplay.c:223
+#: aplay/aplay.c:240
 msgid "no soundcards found..."
 msgstr "keine Soundkarten gefunden ..."
 
-#: aplay/aplay.c:226
+#: aplay/aplay.c:243
 #, c-format
 msgid "**** List of %s Hardware Devices ****\n"
 msgstr "**** Liste der Hardware-Geräte (%s) ****\n"
 
-#: aplay/aplay.c:255
+#: aplay/aplay.c:272
 #, c-format
 msgid "card %i: %s [%s], device %i: %s [%s]\n"
 msgstr "Karte %i: %s [%s], Gerät %i: %s [%s]\n"
 
-#: aplay/aplay.c:261
+#: aplay/aplay.c:278
 #, c-format
 msgid "  Subdevices: %i/%i\n"
 msgstr "  Sub-Geräte: %i/%i\n"
 
-#: aplay/aplay.c:268
+#: aplay/aplay.c:285
 #, c-format
 msgid "  Subdevice #%i: %s\n"
 msgstr "  Sub-Gerät #%i: %s\n"
 
-#: aplay/aplay.c:332
+#: aplay/aplay.c:362
 #, c-format
 msgid "Aborted by signal %s...\n"
 msgstr "Abbruch durch Signal %s ...\n"
 
-#: aplay/aplay.c:430
+#: aplay/aplay.c:473
 msgid "command should be named either arecord or aplay"
 msgstr "Befehl sollte arecord oder aplay sein"
 
-#: aplay/aplay.c:469
+#: aplay/aplay.c:512
 #, c-format
 msgid "unrecognized file format %s"
 msgstr "unbekanntes Dateiformat %s"
 
-#: aplay/aplay.c:476
+#: aplay/aplay.c:519
 #, c-format
 msgid "value %i for channels is invalid"
 msgstr "Kanalanzahl %i ist ungültig"
 
-#: aplay/aplay.c:495
+#: aplay/aplay.c:538
 #, c-format
 msgid "wrong extended format '%s'"
 msgstr "erweitertes Format '%s' ist ungültig"
 
-#: aplay/aplay.c:506
+#: aplay/aplay.c:549
 #, c-format
 msgid "bad speed value %i"
 msgstr "ungültige Rate %i"
 
-#: aplay/aplay.c:592
+#: aplay/aplay.c:644
 #, c-format
 msgid "Try `%s --help' for more information.\n"
 msgstr "Siehe `%s --help' für mehr Informationen.\n"
 
-#: aplay/aplay.c:608
+#: aplay/aplay.c:660
 #, c-format
 msgid "audio open error: %s"
 msgstr "Fehler beim Öffnen des Gerätes: %s"
 
-#: aplay/aplay.c:613
+#: aplay/aplay.c:665
 #, c-format
 msgid "info error: %s"
 msgstr "Fehler beim Lesen der Geräteinformationen: %s"
 
-#: aplay/aplay.c:620
+#: aplay/aplay.c:672
 #, c-format
 msgid "nonblock setting error: %s"
 msgstr "Fehler beim Setzen des nonblock-Modus: %s"
 
-#: aplay/aplay.c:630 aplay/aplay.c:737 aplay/aplay.c:1092
+#: aplay/aplay.c:682 aplay/aplay.c:806 aplay/aplay.c:1174
 msgid "not enough memory"
 msgstr "nicht genug Speicher"
 
-#: aplay/aplay.c:727
+#: aplay/aplay.c:706
+#, c-format
+msgid "Cannot create process ID file %s: %s"
+msgstr "Fehler beim Schreiben der Prozess-ID-Datei %s: %s"
+
+#: aplay/aplay.c:796
 #, c-format
 msgid "read error (called from line %i)"
 msgstr "Lesefehler (aufgerufen von Zeile %i)"
 
-#: aplay/aplay.c:785
+#: aplay/aplay.c:854
 #, c-format
 msgid "unknown length of 'fmt ' chunk (read %u, should be %u at least)"
 msgstr ""
 "unbekannte Länge des 'fmt '-Blocks (gelesen: %u, sollte mindestens %u sein)"
 
-#: aplay/aplay.c:795
+#: aplay/aplay.c:864
 #, c-format
 msgid ""
 "unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)"
@@ -587,198 +600,230 @@
 "unbekannte Länge des erweiterten 'fmt '-Blocks (gelesen: %u, sollte "
 "mindestens %u sein)"
 
-#: aplay/aplay.c:800
+#: aplay/aplay.c:869
 msgid "wrong format tag in extensible 'fmt ' chunk"
 msgstr "ungültiger Format-Wert im erweiterten 'fmt '-Block"
 
-#: aplay/aplay.c:807
+#: aplay/aplay.c:876
 #, c-format
 msgid "can't play WAVE-file format 0x%04x which is not PCM or FLOAT encoded"
 msgstr ""
 "kann WAVE-Datei-Format 0x%04x nicht abspielen; ist weder PCM noch FLOAT"
 
-#: aplay/aplay.c:811
+#: aplay/aplay.c:880
 #, c-format
 msgid "can't play WAVE-files with %d tracks"
 msgstr "kann WAVE-Datei mit %d Kanälen nicht abspielen"
 
-#: aplay/aplay.c:819 aplay/aplay.c:919
+#: aplay/aplay.c:888 aplay/aplay.c:988
 #, c-format
 msgid "Warning: format is changed to U8\n"
 msgstr "Warnung: benutztes Format ist U8\n"
 
-#: aplay/aplay.c:825
+#: aplay/aplay.c:894
 #, c-format
 msgid "Warning: format is changed to S16_LE\n"
 msgstr "Warnung: benutztes Format ist S16_LE\n"
 
-#: aplay/aplay.c:833
+#: aplay/aplay.c:902
 #, c-format
 msgid "Warning: format is changed to S24_3LE\n"
 msgstr "Warnung: benutztes Format ist S24_3LE\n"
 
-#: aplay/aplay.c:839
+#: aplay/aplay.c:908
 #, c-format
 msgid "Warning: format is changed to S24_LE\n"
 msgstr "Warnung: benutztes Format ist S24_LE\n"
 
-#: aplay/aplay.c:843
+#: aplay/aplay.c:912
 #, c-format
 msgid ""
 " can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)"
 msgstr ""
 "kann WAVE-Datei mit %d-Bit-Samples in %d Bytes (%d Kanäle) nicht abspielen"
 
-#: aplay/aplay.c:855
+#: aplay/aplay.c:924
 #, c-format
 msgid " can't play WAVE-files with sample %d bits wide"
 msgstr "kann WAVE-Datei mit %d-Bit-Samples nicht abspielen"
 
-#: aplay/aplay.c:913
+#: aplay/aplay.c:982
 #, c-format
 msgid "Warning: format is changed to MU_LAW\n"
 msgstr "Warnung: benutztes Format ist MU_LAW\n"
 
-#: aplay/aplay.c:925
+#: aplay/aplay.c:994
 #, c-format
 msgid "Warning: format is changed to S16_BE\n"
 msgstr "Warnung: benutztes Format ist S16_BE\n"
 
-#: aplay/aplay.c:938 aplay/aplay.c:1768 aplay/aplay.c:1775 aplay/aplay.c:2297
-#: aplay/aplay.c:2309
+#: aplay/aplay.c:1007 aplay/aplay.c:1925 aplay/aplay.c:1932 aplay/aplay.c:2455
+#: aplay/aplay.c:2467
 msgid "read error"
 msgstr "Lesefehler"
 
-#: aplay/aplay.c:957
+#: aplay/aplay.c:1037
 msgid "Broken configuration for this PCM: no configurations available"
 msgstr ""
 "ungültige Konfiguration für dieses Gerät: keine unterstützte Konfiguration"
 
-#: aplay/aplay.c:974
+#: aplay/aplay.c:1054
 msgid "Access type not available"
 msgstr "Zugriffs-Modus nicht unterstützt"
 
-#: aplay/aplay.c:979
+#: aplay/aplay.c:1059
 msgid "Sample format non available"
 msgstr "Sample-Format nicht unterstützt"
 
-#: aplay/aplay.c:984
+#: aplay/aplay.c:1065
 msgid "Channels count non available"
 msgstr "Kanalanzahl nicht unterstützt"
 
-#: aplay/aplay.c:999
+#: aplay/aplay.c:1080
 #, c-format
 msgid "Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"
 msgstr ""
 "Warnung: Rate ist nicht exakt (angefordert: %i Hz, unterstützt: %i Hz)\n"
 
-#: aplay/aplay.c:1005
+#: aplay/aplay.c:1086
 #, c-format
 msgid "         please, try the plug plugin %s\n"
 msgstr "         probieren Sie bitte das plug-Plugin: %s\n"
 
-#: aplay/aplay.c:1041
+#: aplay/aplay.c:1123
 msgid "Unable to install hw params:"
 msgstr "Fehler beim Setzen der Hardware-Parameter:"
 
-#: aplay/aplay.c:1048
+#: aplay/aplay.c:1130
 #, c-format
 msgid "Can't use period equal to buffer size (%lu == %lu)"
 msgstr "Periode gleich der Puffer-Größe wird nicht unterstützt (%lu == %lu)"
 
-#: aplay/aplay.c:1079
+#: aplay/aplay.c:1161
 msgid "unable to install sw params:"
 msgstr "Fehler beim Setzen der Software-Parameter:"
 
-#: aplay/aplay.c:1154
+#: aplay/aplay.c:1192
+#, c-format
+msgid "snd_pcm_mmap_begin problem: %s"
+msgstr "Fehler bei snd_pcm_mmap_begin: %s"
+
+#: aplay/aplay.c:1215
+#, c-format
+msgid "stdin O_NONBLOCK flag setup failed\n"
+msgstr "Fehler beim Setzen von O_NONBLOCK in stdin\n"
+
+#: aplay/aplay.c:1237
+#, c-format
+msgid "\rPAUSE command ignored (no hw support)\n"
+msgstr "\rPause-Kommando ignoriert (keine HW-Unterstützung)\n"
+
+#: aplay/aplay.c:1242
+#, c-format
+msgid "pause push error: %s"
+msgstr "Fehler beim Pausieren: %s"
+
+#: aplay/aplay.c:1251
+#, c-format
+msgid "pause release error: %s"
+msgstr "Fehler beim Beenden der Pause: %s"
+
+#: aplay/aplay.c:1265
+#, c-format
+msgid ""
+"\r=== PAUSE ===                                                            "
+msgstr ""
+"\r=== PAUSE ===                                                            "
+
+#: aplay/aplay.c:1307
 #, c-format
 msgid "status error: %s"
 msgstr "Status-Fehler: %s"
 
-#: aplay/aplay.c:1164 aplay/aplay.c:1175
+#: aplay/aplay.c:1317 aplay/aplay.c:1328
 #, c-format
 msgid "%s!!! (at least %.3f ms long)\n"
 msgstr "%s!!! (mindestens %.3f ms)\n"
 
-#: aplay/aplay.c:1165 aplay/aplay.c:1168 aplay/aplay.c:1176
+#: aplay/aplay.c:1318 aplay/aplay.c:1321 aplay/aplay.c:1329
 msgid "underrun"
 msgstr "Unterlauf"
 
-#: aplay/aplay.c:1165 aplay/aplay.c:1176
+#: aplay/aplay.c:1318 aplay/aplay.c:1329
 msgid "overrun"
 msgstr "Überlauf"
 
-#: aplay/aplay.c:1180
+#: aplay/aplay.c:1333
 #, c-format
 msgid "Status:\n"
 msgstr "Status:\n"
 
-#: aplay/aplay.c:1184
+#: aplay/aplay.c:1337
 #, c-format
 msgid "xrun: prepare error: %s"
 msgstr "Unter-/Überlauf: Fehler beim Re-Initialisieren des Gerätes: %s"
 
-#: aplay/aplay.c:1190
+#: aplay/aplay.c:1343
 #, c-format
 msgid "Status(DRAINING):\n"
 msgstr "Status (DRAINING):\n"
 
-#: aplay/aplay.c:1194
+#: aplay/aplay.c:1347
 #, c-format
 msgid "capture stream format change? attempting recover...\n"
 msgstr "Format-Wechsel der Aufnahme-Daten? Versuche Wiederherstellung ...\n"
 
-#: aplay/aplay.c:1196
+#: aplay/aplay.c:1349
 #, c-format
 msgid "xrun(DRAINING): prepare error: %s"
 msgstr "XRUN (DRAINING): Fehler beim Re-Initialisieren des Gerätes: %s"
 
-#: aplay/aplay.c:1203
+#: aplay/aplay.c:1356
 #, c-format
 msgid "Status(R/W):\n"
 msgstr "Status (R/W):\n"
 
-#: aplay/aplay.c:1206
+#: aplay/aplay.c:1359
 #, c-format
 msgid "read/write error, state = %s"
 msgstr "Lese-/Schreibfehler, Status = %s"
 
-#: aplay/aplay.c:1216
+#: aplay/aplay.c:1369
 #, c-format
 msgid "Suspended. Trying resume. "
 msgstr "Ruhezustand. Versuche, aufzuwecken. "
 
-#: aplay/aplay.c:1221
+#: aplay/aplay.c:1374
 #, c-format
 msgid "Failed. Restarting stream. "
 msgstr "Fehlgeschlagen. Re-Initialisierung. "
 
-#: aplay/aplay.c:1223
+#: aplay/aplay.c:1376
 #, c-format
 msgid "suspend: prepare error: %s"
 msgstr "Ruhezustand: Fehler beim Re-Initialisieren: %s"
 
-#: aplay/aplay.c:1228
+#: aplay/aplay.c:1381
 #, c-format
 msgid "Done.\n"
 msgstr "Fertig.\n"
 
-#: aplay/aplay.c:1250
+#: aplay/aplay.c:1403
 #, c-format
 msgid " !clip  "
 msgstr " !clip  "
 
-#: aplay/aplay.c:1397
+#: aplay/aplay.c:1550
 #, c-format
 msgid "Unsupported bit size %d.\n"
 msgstr "%d-Bit-Samples werden nicht unterstützt.\n"
 
-#: aplay/aplay.c:1431
+#: aplay/aplay.c:1584
 #, c-format
 msgid "Max peak (%li samples): 0x%08x "
 msgstr "Höchstwert (%li Samples): 0x%08x "
 
-#: aplay/aplay.c:1465
+#: aplay/aplay.c:1618
 #, c-format
 msgid ""
 "Suspicious buffer position (%li total): avail = %li, delay = %li, buffer = %"
@@ -787,108 +832,108 @@
 "verdächtige Puffer-Position (total %li): avail = %li, delay = %li, buffer = %"
 "li\n"
 
-#: aplay/aplay.c:1528
+#: aplay/aplay.c:1682
 #, c-format
 msgid "write error: %s"
 msgstr "Schreibfehler: %s"
 
-#: aplay/aplay.c:1574
+#: aplay/aplay.c:1729
 #, c-format
 msgid "writev error: %s"
 msgstr "Vektor-Schreib-Fehler: %s"
 
-#: aplay/aplay.c:1617
+#: aplay/aplay.c:1773
 #, c-format
 msgid "read error: %s"
 msgstr "Lesefehler: %s"
 
-#: aplay/aplay.c:1660
+#: aplay/aplay.c:1817
 #, c-format
 msgid "readv error: %s"
 msgstr "Vektor-Lese-Fehler: %s"
 
-#: aplay/aplay.c:1708
+#: aplay/aplay.c:1865
 msgid "can't allocate buffer for silence"
 msgstr "nicht genug Speicher für Stille-Block"
 
-#: aplay/aplay.c:1717 aplay/aplay.c:1943 aplay/aplay.c:1948 aplay/aplay.c:1995
-#: aplay/aplay.c:2004 aplay/aplay.c:2011 aplay/aplay.c:2021 aplay/aplay.c:2027
-#: aplay/aplay.c:2099 aplay/aplay.c:2129 aplay/aplay.c:2143
+#: aplay/aplay.c:1874 aplay/aplay.c:2100 aplay/aplay.c:2105 aplay/aplay.c:2152
+#: aplay/aplay.c:2161 aplay/aplay.c:2168 aplay/aplay.c:2178 aplay/aplay.c:2184
+#: aplay/aplay.c:2256 aplay/aplay.c:2286 aplay/aplay.c:2300
 msgid "write error"
 msgstr "Schreibfehler"
 
-#: aplay/aplay.c:1730
+#: aplay/aplay.c:1887
 #, c-format
 msgid "voc_pcm_flush - silence error"
 msgstr "voc_pcm_flush - Fehler in set_silence"
 
-#: aplay/aplay.c:1733
+#: aplay/aplay.c:1890
 msgid "voc_pcm_flush error"
 msgstr "Schreibfehler"
 
-#: aplay/aplay.c:1759
+#: aplay/aplay.c:1916
 msgid "malloc error"
 msgstr "nicht genug Speicher"
 
-#: aplay/aplay.c:1763
+#: aplay/aplay.c:1920
 #, c-format
 msgid "Playing Creative Labs Channel file '%s'...\n"
 msgstr "Spiele Creative Labs Channel-Datei '%s'...\n"
 
-#: aplay/aplay.c:1831 aplay/aplay.c:1923
+#: aplay/aplay.c:1988 aplay/aplay.c:2080
 msgid "can't play packed .voc files"
 msgstr "kann komprimierte .voc-Dateien nicht abspielen"
 
-#: aplay/aplay.c:1883
+#: aplay/aplay.c:2040
 #, c-format
 msgid "can't play loops; %s isn't seekable\n"
 msgstr ""
 "kann Schleife nicht abspielen; Dateiposition in %s ist nicht änderbar\n"
 
-#: aplay/aplay.c:1932
+#: aplay/aplay.c:2089
 #, c-format
 msgid "unknown blocktype %d. terminate."
 msgstr "Unbekannter Block-Typ %d. Abbruch."
 
-#: aplay/aplay.c:2063
+#: aplay/aplay.c:2220
 #, c-format
 msgid "Wave doesn't support %s format..."
 msgstr "Format %s wird in WAVE nicht unterstützt ..."
 
-#: aplay/aplay.c:2123
+#: aplay/aplay.c:2280
 #, c-format
 msgid "Sparc Audio doesn't support %s format..."
 msgstr "Format %s wird in Sparc-Audio nicht unterstützt ..."
 
-#: aplay/aplay.c:2204
+#: aplay/aplay.c:2361
 msgid "Playing"
 msgstr "Wiedergabe:"
 
-#: aplay/aplay.c:2204
+#: aplay/aplay.c:2361
 msgid "Recording"
 msgstr "Aufnahme:"
 
-#: aplay/aplay.c:2208
+#: aplay/aplay.c:2365
 #, c-format
 msgid "Rate %d Hz, "
 msgstr "Rate: %d Hz, "
 
-#: aplay/aplay.c:2210
+#: aplay/aplay.c:2367
 #, c-format
 msgid "Mono"
 msgstr "mono"
 
-#: aplay/aplay.c:2212
+#: aplay/aplay.c:2369
 #, c-format
 msgid "Stereo"
 msgstr "stereo"
 
-#: aplay/aplay.c:2214
+#: aplay/aplay.c:2371
 #, c-format
 msgid "Channels %i"
 msgstr "%i Kanäle"
 
-#: aplay/aplay.c:2573 aplay/aplay.c:2626
+#: aplay/aplay.c:2882 aplay/aplay.c:2935
 #, c-format
 msgid "You need to specify %d files"
 msgstr "Es werden %d Dateien benötigt."
@@ -999,47 +1044,47 @@
 msgid "kernel"
 msgstr "Kernel"
 
-#: seq/aconnect/aconnect.c:326
+#: seq/aconnect/aconnect.c:307
 #, c-format
 msgid "can't open sequencer\n"
 msgstr "Fehler beim Öffnen des Sequenzers\n"
 
-#: seq/aconnect/aconnect.c:354
+#: seq/aconnect/aconnect.c:335
 #, c-format
 msgid "can't get client id\n"
 msgstr "Fehler beim Lesen der Client-ID\n"
 
-#: seq/aconnect/aconnect.c:361
+#: seq/aconnect/aconnect.c:342
 #, c-format
 msgid "can't set client info\n"
 msgstr "Fehler beim Setzen des Client-Namens\n"
 
-#: seq/aconnect/aconnect.c:368
+#: seq/aconnect/aconnect.c:349
 #, c-format
 msgid "invalid sender address %s\n"
 msgstr "ungültige Sender-Adresse %s\n"
 
-#: seq/aconnect/aconnect.c:373 seq/aseqnet/aseqnet.c:290
+#: seq/aconnect/aconnect.c:354 seq/aseqnet/aseqnet.c:290
 #, c-format
 msgid "invalid destination address %s\n"
 msgstr "ungültige Ziel-Adresse %s\n"
 
-#: seq/aconnect/aconnect.c:387
+#: seq/aconnect/aconnect.c:368
 #, c-format
 msgid "No subscription is found\n"
 msgstr "keine Verbindung gefunden\n"
 
-#: seq/aconnect/aconnect.c:392
+#: seq/aconnect/aconnect.c:373
 #, c-format
 msgid "Disconnection failed (%s)\n"
 msgstr "Verbindungs-Trennung fehlgeschlagen (%s)\n"
 
-#: seq/aconnect/aconnect.c:398
+#: seq/aconnect/aconnect.c:379
 #, c-format
 msgid "Connection is already subscribed\n"
 msgstr "Verbindung ist bereits vorhanden\n"
 
-#: seq/aconnect/aconnect.c:403
+#: seq/aconnect/aconnect.c:384
 #, c-format
 msgid "Connection failed (%s)\n"
 msgstr "Verbindung fehlgeschlagen (%s)\n"
@@ -1169,262 +1214,262 @@
 msgid "disconnected\n"
 msgstr "Verbindung getrennt\n"
 
-#: speaker-test/speaker-test.c:102
+#: speaker-test/speaker-test.c:104
 msgid "Front Left"
 msgstr "Vorne links"
 
-#: speaker-test/speaker-test.c:103
+#: speaker-test/speaker-test.c:105
 msgid "Front Right"
 msgstr "Vorne rechts"
 
-#: speaker-test/speaker-test.c:104
+#: speaker-test/speaker-test.c:106
 msgid "Rear Left"
 msgstr "Hinten links"
 
-#: speaker-test/speaker-test.c:105
+#: speaker-test/speaker-test.c:107
 msgid "Rear Right"
 msgstr "Hinten rechts"
 
-#: speaker-test/speaker-test.c:107
+#: speaker-test/speaker-test.c:109
 msgid "LFE"
 msgstr "Bass"
 
-#: speaker-test/speaker-test.c:108
+#: speaker-test/speaker-test.c:110
 msgid "Side Left"
 msgstr "Seitlich links"
 
-#: speaker-test/speaker-test.c:109
+#: speaker-test/speaker-test.c:111
 msgid "Side Right"
 msgstr "Seitlich rechts"
 
-#: speaker-test/speaker-test.c:110
+#: speaker-test/speaker-test.c:112
 msgid "Channel 9"
 msgstr "Kanal 9"
 
-#: speaker-test/speaker-test.c:111
+#: speaker-test/speaker-test.c:113
 msgid "Channel 10"
 msgstr "Kanal 10"
 
-#: speaker-test/speaker-test.c:112
+#: speaker-test/speaker-test.c:114
 msgid "Channel 11"
 msgstr "Kanal 11"
 
-#: speaker-test/speaker-test.c:113
+#: speaker-test/speaker-test.c:115
 msgid "Channel 12"
 msgstr "Kanal 12"
 
-#: speaker-test/speaker-test.c:114
+#: speaker-test/speaker-test.c:116
 msgid "Channel 13"
 msgstr "Kanal 13"
 
-#: speaker-test/speaker-test.c:115
+#: speaker-test/speaker-test.c:117
 msgid "Channel 14"
 msgstr "Kanal 14"
 
-#: speaker-test/speaker-test.c:116
+#: speaker-test/speaker-test.c:118
 msgid "Channel 15"
 msgstr "Kanal 15"
 
-#: speaker-test/speaker-test.c:117
+#: speaker-test/speaker-test.c:119
 msgid "Channel 16"
 msgstr "Kanal 16"
 
-#: speaker-test/speaker-test.c:307
+#: speaker-test/speaker-test.c:383
 #, c-format
 msgid "Broken configuration for playback: no configurations available: %s\n"
 msgstr "Ungültige Konfiguration: keine unterstützte Konfiguration: %s\n"
 
-#: speaker-test/speaker-test.c:314
+#: speaker-test/speaker-test.c:390
 #, c-format
 msgid "Access type not available for playback: %s\n"
 msgstr "Zugriffsmodus nicht unterstützt: %s\n"
 
-#: speaker-test/speaker-test.c:321
+#: speaker-test/speaker-test.c:397
 #, c-format
 msgid "Sample format not available for playback: %s\n"
 msgstr "Sample-Format nicht unterstützt: %s\n"
 
-#: speaker-test/speaker-test.c:328
+#: speaker-test/speaker-test.c:404
 #, c-format
 msgid "Channels count (%i) not available for playbacks: %s\n"
 msgstr "Kanal-Anzahl %i nicht unterstützt: %s\n"
 
-#: speaker-test/speaker-test.c:336
+#: speaker-test/speaker-test.c:412
 #, c-format
 msgid "Rate %iHz not available for playback: %s\n"
 msgstr "Rate %i Hz nicht unterstützt: %s\n"
 
-#: speaker-test/speaker-test.c:341
+#: speaker-test/speaker-test.c:417
 #, c-format
 msgid "Rate doesn't match (requested %iHz, get %iHz, err %d)\n"
 msgstr ""
 "Rate ist nicht exakt (angefordert: %i Hz, unterstützt: %i Hz, Fehlercode %"
 "d)\n"
 
-#: speaker-test/speaker-test.c:345
+#: speaker-test/speaker-test.c:421
 #, c-format
 msgid "Rate set to %iHz (requested %iHz)\n"
 msgstr "Rate ist %i Hz (angefordert: %i Hz)\n"
 
-#: speaker-test/speaker-test.c:351
+#: speaker-test/speaker-test.c:427
 #, c-format
 msgid "Buffer size range from %lu to %lu\n"
 msgstr "Puffergröße von %lu bis %lu\n"
 
-#: speaker-test/speaker-test.c:352
+#: speaker-test/speaker-test.c:428
 #, c-format
 msgid "Period size range from %lu to %lu\n"
 msgstr "Periodengröße von %lu bis %lu\n"
 
-#: speaker-test/speaker-test.c:354
+#: speaker-test/speaker-test.c:430
 #, c-format
 msgid "Requested period time %u us\n"
 msgstr "Angeforderte Periodenzeit %u µs\n"
 
-#: speaker-test/speaker-test.c:357
+#: speaker-test/speaker-test.c:433
 #, c-format
 msgid "Unable to set period time %u us for playback: %s\n"
 msgstr "Fehler beim Setzen der Periodenzeit %u µs: %s\n"
 
-#: speaker-test/speaker-test.c:363
+#: speaker-test/speaker-test.c:439
 #, c-format
 msgid "Requested buffer time %u us\n"
 msgstr "Angeforderte Pufferlänge %u µs\n"
 
-#: speaker-test/speaker-test.c:366
+#: speaker-test/speaker-test.c:442
 #, c-format
 msgid "Unable to set buffer time %u us for playback: %s\n"
 msgstr "Fehler beim Setzen der Pufferlänge %u µs: %s\n"
 
-#: speaker-test/speaker-test.c:375
+#: speaker-test/speaker-test.c:451
 #, c-format
 msgid "Using max buffer size %lu\n"
 msgstr "Verwende maximale Puffergröße %lu\n"
 
-#: speaker-test/speaker-test.c:378
+#: speaker-test/speaker-test.c:454
 #, c-format
 msgid "Unable to set buffer size %lu for playback: %s\n"
 msgstr "Fehler beim Setzen der Puffergröße %lu: %s\n"
 
-#: speaker-test/speaker-test.c:384
+#: speaker-test/speaker-test.c:460
 #, c-format
 msgid "Periods = %u\n"
 msgstr "Perioden = %u\n"
 
-#: speaker-test/speaker-test.c:387
+#: speaker-test/speaker-test.c:463
 #, c-format
 msgid "Unable to set nperiods %u for playback: %s\n"
 msgstr "Fehler beim Setzen der Periodenanzahl %u: %s\n"
 
-#: speaker-test/speaker-test.c:396
+#: speaker-test/speaker-test.c:472
 #, c-format
 msgid "Unable to set hw params for playback: %s\n"
 msgstr "Fehler beim Setzen der Hardware-Parameter: %s\n"
 
-#: speaker-test/speaker-test.c:402
+#: speaker-test/speaker-test.c:478
 #, c-format
 msgid "was set period_size = %lu\n"
 msgstr "gesetzt: period_size = %lu\n"
 
-#: speaker-test/speaker-test.c:403
+#: speaker-test/speaker-test.c:479
 #, c-format
 msgid "was set buffer_size = %lu\n"
 msgstr "gesetzt: buffer_size = %lu\n"
 
-#: speaker-test/speaker-test.c:405
+#: speaker-test/speaker-test.c:481
 #, c-format
 msgid "buffer to small, could not use\n"
 msgstr "Puffer zu klein, kann nicht benutzt werden\n"
 
-#: speaker-test/speaker-test.c:418
+#: speaker-test/speaker-test.c:494
 #, c-format
 msgid "Unable to determine current swparams for playback: %s\n"
 msgstr "Fehler beim Lesen der Software-Parameter: %s\n"
 
-#: speaker-test/speaker-test.c:425
+#: speaker-test/speaker-test.c:501
 #, c-format
 msgid "Unable to set start threshold mode for playback: %s\n"
 msgstr "Fehler beim Setzen des Start-Schwellenwertes: %s\n"
 
-#: speaker-test/speaker-test.c:432
+#: speaker-test/speaker-test.c:508
 #, c-format
 msgid "Unable to set avail min for playback: %s\n"
 msgstr "Fehler beim Setzen des Mindest-verfügbar-Wertes: %s\n"
 
-#: speaker-test/speaker-test.c:439
+#: speaker-test/speaker-test.c:515
 #, c-format
 msgid "Unable to set sw params for playback: %s\n"
 msgstr "Fehler beim Setzen der Software-Parameter: %s\n"
 
-#: speaker-test/speaker-test.c:454
+#: speaker-test/speaker-test.c:530
 #, c-format
 msgid "Can't recovery from underrun, prepare failed: %s\n"
 msgstr ""
 "Fehler bei Unterlauf-Behandlung, Re-Initialisierung fehlgeschlagen: %s\n"
 
-#: speaker-test/speaker-test.c:465
+#: speaker-test/speaker-test.c:541
 #, c-format
 msgid "Can't recovery from suspend, prepare failed: %s\n"
 msgstr ""
 "Fehler beim Aufwachen aus dem Ruhezustand, Re-Initialisierung "
 "fehlgeschlagen: %s\n"
 
-#: speaker-test/speaker-test.c:529 speaker-test/speaker-test.c:926
+#: speaker-test/speaker-test.c:605 speaker-test/speaker-test.c:1025
 #, c-format
 msgid "No enough memory\n"
 msgstr "Nicht genug Speicher\n"
 
-#: speaker-test/speaker-test.c:534
+#: speaker-test/speaker-test.c:610
 #, c-format
 msgid "Cannot open WAV file %s\n"
 msgstr "Kann WAV-Datei %s nicht öffnen\n"
 
-#: speaker-test/speaker-test.c:538 speaker-test/speaker-test.c:567
+#: speaker-test/speaker-test.c:614 speaker-test/speaker-test.c:643
 #, c-format
 msgid "Invalid WAV file %s\n"
 msgstr "Ungültige WAV-Datei %s\n"
 
-#: speaker-test/speaker-test.c:543
+#: speaker-test/speaker-test.c:619
 #, c-format
 msgid "Not a WAV file: %s\n"
 msgstr "Keine WAV-Datei: %s\n"
 
-#: speaker-test/speaker-test.c:547
+#: speaker-test/speaker-test.c:623
 #, c-format
 msgid "Unsupported WAV format %d for %s\n"
 msgstr "Nicht unterstütztes WAV-Format %d in %s\n"
 
-#: speaker-test/speaker-test.c:552
+#: speaker-test/speaker-test.c:628
 #, c-format
 msgid "%s is not a mono stream (%d channels)\n"
 msgstr "%s ist keine Mono-Datei (%d Kanäle)\n"
 
-#: speaker-test/speaker-test.c:557
+#: speaker-test/speaker-test.c:633
 #, c-format
 msgid "Sample rate doesn't match (%d) for %s\n"
 msgstr "Sample-Rate (%d) stimmt nicht überein in %s\n"
 
-#: speaker-test/speaker-test.c:562
+#: speaker-test/speaker-test.c:638
 #, c-format
 msgid "Unsupported sample format bits %d for %s\n"
 msgstr "Nicht unterstütztes Sample-Format mit %d Bits in %s\n"
 
-#: speaker-test/speaker-test.c:612
+#: speaker-test/speaker-test.c:688
 #, c-format
 msgid "Undefined channel %d\n"
 msgstr "Kanal %d nicht definiert\n"
 
-#: speaker-test/speaker-test.c:663
+#: speaker-test/speaker-test.c:739
 #, c-format
 msgid "Write error: %d,%s\n"
 msgstr "Schreibfehler: %d, %s\n"
 
-#: speaker-test/speaker-test.c:665
+#: speaker-test/speaker-test.c:741
 #, c-format
 msgid "xrun_recovery failed: %d,%s\n"
 msgstr "xrun_recovery fehlgeschlagen: %d, %s\n"
 
-#: speaker-test/speaker-test.c:723
+#: speaker-test/speaker-test.c:803
 #, c-format
 msgid ""
 "Usage: speaker-test [OPTION]... \n"
@@ -1461,72 +1506,72 @@
 "-W,--wavdir     benutze Verzeichnis mit darin enthaltenen WAV-Dateien\n"
 "\n"
 
-#: speaker-test/speaker-test.c:835
+#: speaker-test/speaker-test.c:921
 #, c-format
 msgid "Invalid number of periods %d\n"
 msgstr "Ungültige Periodenanzahl %d\n"
 
-#: speaker-test/speaker-test.c:849 speaker-test/speaker-test.c:853
+#: speaker-test/speaker-test.c:937 speaker-test/speaker-test.c:941
 #, c-format
 msgid "Invalid test type %s\n"
 msgstr "Ungültiger Test-Typ %s\n"
 
-#: speaker-test/speaker-test.c:865
+#: speaker-test/speaker-test.c:953
 #, c-format
 msgid "Invalid parameter for -s option.\n"
 msgstr "Ungültiger Wert für Option -s\n"
 
-#: speaker-test/speaker-test.c:876
+#: speaker-test/speaker-test.c:967
 #, c-format
 msgid "Unknown option '%c'\n"
 msgstr "Unbekannte Options '%c'\n"
 
-#: speaker-test/speaker-test.c:890
+#: speaker-test/speaker-test.c:981
 #, c-format
 msgid "Playback device is %s\n"
 msgstr "Wiedergabe-Gerät ist %s\n"
 
-#: speaker-test/speaker-test.c:891
+#: speaker-test/speaker-test.c:982
 #, c-format
 msgid "Stream parameters are %iHz, %s, %i channels\n"
 msgstr "Stream-Parameter sind %i Hz, %s, %i Kanäle\n"
 
-#: speaker-test/speaker-test.c:894
+#: speaker-test/speaker-test.c:985
 #, c-format
 msgid "Using 16 octaves of pink noise\n"
 msgstr "Verwende 16 Oktaven rosa Rauschen\n"
 
-#: speaker-test/speaker-test.c:897
+#: speaker-test/speaker-test.c:988
 #, c-format
 msgid "Sine wave rate is %.4fHz\n"
 msgstr "Sinuswelle mit Frequenz %.4f Hz\n"
 
-#: speaker-test/speaker-test.c:900
+#: speaker-test/speaker-test.c:991
 #, c-format
 msgid "WAV file(s)\n"
 msgstr "WAV-Datei(en)\n"
 
-#: speaker-test/speaker-test.c:906
+#: speaker-test/speaker-test.c:997
 #, c-format
 msgid "Playback open error: %d,%s\n"
 msgstr "Fehler beim Öffnen des Gerätes: %d, %s\n"
 
-#: speaker-test/speaker-test.c:911
+#: speaker-test/speaker-test.c:1002
 #, c-format
 msgid "Setting of hwparams failed: %s\n"
 msgstr "Fehler beim Setzen der Hardware-Parameter: %s\n"
 
-#: speaker-test/speaker-test.c:916
+#: speaker-test/speaker-test.c:1007
 #, c-format
 msgid "Setting of swparams failed: %s\n"
 msgstr "Fehler beim Setzen der Software-Parameter: %s\n"
 
-#: speaker-test/speaker-test.c:957 speaker-test/speaker-test.c:979
+#: speaker-test/speaker-test.c:1056 speaker-test/speaker-test.c:1078
 #, c-format
 msgid "Transfer failed: %s\n"
 msgstr "Schreibfehler: %s\n"
 
-#: speaker-test/speaker-test.c:967
+#: speaker-test/speaker-test.c:1066
 #, c-format
 msgid "Time per period = %lf\n"
 msgstr "Zeit pro Periode = %lf\n"
diff --git a/po/fr.po b/po/fr.po
new file mode 100644
index 0000000..a6028a4
--- /dev/null
+++ b/po/fr.po
@@ -0,0 +1,1566 @@
+# French translation for alsa-utils
+# Copyright (C) 2011 The ALSA Team
+# This file is distributed under the same license as the alsa-utils package.
+# Christoph J. Thompson <cjsthompson@gmail.com>, 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: alsa-utils 1.0.23\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-01-07 06:23+0100\n"
+"PO-Revision-Date: 2011-01-07 06:23+0100\n"
+"Last-Translator: Christoph J. Thompson <cjsthompson@gmail.com>\n"
+"Language-Team: French <fr@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../alsamixer/card_select.c:126 ../alsamixer/device_name.c:126
+msgid "Sound Card"
+msgstr "Carte Son"
+
+#: ../alsamixer/card_select.c:181
+msgid "(default)"
+msgstr "(par défaut)"
+
+#: ../alsamixer/card_select.c:191
+msgid "cannot enumerate sound cards"
+msgstr "les cartes son n'ont pas pu être énumérées"
+
+#: ../alsamixer/card_select.c:215
+msgid "enter device name..."
+msgstr "entrez le nom du périphérique..."
+
+#: ../alsamixer/cli.c:40
+msgid "Usage: alsamixer [options]"
+msgstr "Utilisation: alsamixer [options]"
+
+#: ../alsamixer/cli.c:41
+msgid ""
+"Useful options:\n"
+"  -h, --help              this help\n"
+"  -c, --card=NUMBER       sound card number or id\n"
+"  -D, --device=NAME       mixer device name\n"
+"  -V, --view=MODE         starting view mode: playback/capture/all"
+msgstr ""
+"Options utiles:\n"
+"  -h, --help           cette aide\n"
+"  -c, --card=NUMÉRO    numéro ou identifiant de la carte son\n"
+"  -D, --device=NOM     nom du périphérique de mixage\n"
+"  -V, --view=MODE      mode de visualisation par défaut: lecture/capture/tous"
+
+#: ../alsamixer/cli.c:46
+msgid ""
+"Debugging options:\n"
+"  -g, --no-color          toggle using of colors\n"
+"  -a, --abstraction=NAME  mixer abstraction level: none/basic"
+msgstr ""
+"Options de débogage:\n"
+"  -g, --no-color          supprimer la couleur\n"
+"  -a, --abstraction=NOM   niveau d'abstraction du mixeur: aucun/basique"
+
+#: ../alsamixer/cli.c:77
+#, c-format
+msgid "invalid card index: %s\n"
+msgstr "index de carte invalide: %s\n"
+
+#: ../alsamixer/cli.c:103
+#, c-format
+msgid "unknown abstraction level: %s\n"
+msgstr "niveau d'abstraction inconnu: %s\n"
+
+#: ../alsamixer/cli.c:108
+#, c-format
+msgid "unknown option: %c\n"
+msgstr "option inconnue: %c\n"
+
+#: ../alsamixer/cli.c:110
+msgid "try `alsamixer --help' for more information\n"
+msgstr "essayez `alsamixer --help' pour plus d'information\n"
+
+#: ../alsamixer/device_name.c:177
+msgid "Device name:"
+msgstr "Nom du périphérique:"
+
+#: ../alsamixer/die.c:37
+#, c-format
+msgid "%s: %s\n"
+msgstr "%s: %s\n"
+
+#: ../alsamixer/mixer_display.c:95
+msgid "Card:"
+msgstr "Carte:"
+
+#: ../alsamixer/mixer_display.c:96
+msgid "Chip:"
+msgstr "Puce:"
+
+#: ../alsamixer/mixer_display.c:97
+msgid "View:"
+msgstr "Vue:"
+
+#: ../alsamixer/mixer_display.c:98
+msgid "Item:"
+msgstr "Contrôle:"
+
+#: ../alsamixer/mixer_display.c:101
+msgid "F1:  Help"
+msgstr "F1:  Aide"
+
+#: ../alsamixer/mixer_display.c:102
+msgid "F2:  System information"
+msgstr "F2:  Informations Système"
+
+#: ../alsamixer/mixer_display.c:103
+msgid "F6:  Select sound card"
+msgstr "F6:  Choisir la carte son"
+
+#: ../alsamixer/mixer_display.c:104
+msgid "Esc: Exit"
+msgstr "Esc: Quitter"
+
+#: ../alsamixer/mixer_display.c:171
+msgid "(unplugged)"
+msgstr "(non branché)"
+
+#: ../alsamixer/mixer_display.c:189
+msgid "Playback"
+msgstr "Lecture"
+
+#: ../alsamixer/mixer_display.c:190
+msgid "Capture"
+msgstr "Capture"
+
+#: ../alsamixer/mixer_display.c:191
+msgid "All"
+msgstr "Tout"
+
+#: ../alsamixer/mixer_display.c:231
+msgid "mute"
+msgstr "muet"
+
+#: ../alsamixer/mixer_display.c:272 ../alsamixer/mixer_display.c:282
+msgid "dB gain:"
+msgstr "gain dB:"
+
+#: ../alsamixer/mixer_display.c:282
+#, c-format
+msgid " [%s %s, %s]"
+msgstr " [%s %s, %s]"
+
+#: ../alsamixer/mixer_display.c:291 ../alsamixer/mixer_display.c:297
+#: ../alsamixer/mixer_display.c:303 ../alsamixer/mixer_display.c:309
+msgid "Off"
+msgstr "Fermé"
+
+#: ../alsamixer/mixer_display.c:297 ../alsamixer/mixer_display.c:309
+msgid "On"
+msgstr "Ouvert"
+
+#: ../alsamixer/mixer_display.c:360
+msgid "The sound device was unplugged."
+msgstr "Le périphérique a été débranché"
+
+#: ../alsamixer/mixer_display.c:361
+msgid "Press F6 to select another sound card."
+msgstr "Appuyez sur F6 pour choisir une autre carte son."
+
+#: ../alsamixer/mixer_display.c:376
+msgid "This sound device does not have any playback controls."
+msgstr "Ce périphérique n'a pas de commandes de lecture."
+
+#: ../alsamixer/mixer_display.c:378
+msgid "This sound device does not have any capture controls."
+msgstr "Ce périphérique n'a pas de commandes de capture."
+
+#: ../alsamixer/mixer_display.c:380
+msgid "This sound device does not have any controls."
+msgstr "Ce périphérique n'a pas de commandes."
+
+#. TRANSLATORS: playback on; one character
+#: ../alsamixer/mixer_display.c:537 ../alsamixer/mixer_display.c:542
+msgid "O"
+msgstr "O"
+
+#. TRANSLATORS: playback muted; one character
+#: ../alsamixer/mixer_display.c:539 ../alsamixer/mixer_display.c:543
+msgid "M"
+msgstr "M"
+
+#. TRANSLATORS: "left"; no more than two characters
+#: ../alsamixer/mixer_display.c:557
+msgid "L"
+msgstr "G"
+
+#. TRANSLATORS: "right"; no more than two characters
+#: ../alsamixer/mixer_display.c:561
+msgid "R"
+msgstr "D"
+
+#. TRANSLATORS: no more than eight characters
+#: ../alsamixer/mixer_display.c:563
+msgid "CAPTURE"
+msgstr "CAPTURE"
+
+#: ../alsamixer/mixer_display.c:613
+msgid "Front"
+msgstr "Avant"
+
+#: ../alsamixer/mixer_display.c:616
+msgid "Rear"
+msgstr "Arrière"
+
+#. 4
+#: ../alsamixer/mixer_display.c:619 ../speaker-test/speaker-test.c:107
+msgid "Center"
+msgstr "Centre"
+
+#: ../alsamixer/mixer_display.c:622
+msgid "Woofer"
+msgstr "Basses"
+
+#: ../alsamixer/mixer_display.c:625
+msgid "Side"
+msgstr "Côté"
+
+#: ../alsamixer/mixer_widget.c:83 ../alsamixer/mixer_widget.c:88
+msgid "cannot open mixer"
+msgstr "le mixeur ne peut pas être ouvert"
+
+#: ../alsamixer/mixer_widget.c:94 ../alsamixer/mixer_widget.c:171
+msgid "cannot load mixer controls"
+msgstr "les commandes de mixage ne peuvent pas être chargés"
+
+#: ../alsamixer/mixer_widget.c:161
+#, c-format
+msgid "Cannot open mixer device '%s'."
+msgstr "Le périphérique de mixage '%s' ne peut pas être ouvert."
+
+#: ../alsamixer/mixer_widget.c:182
+msgid "Esc     Exit"
+msgstr "Esc     Quitter"
+
+#: ../alsamixer/mixer_widget.c:183
+msgid "F1 ? H  Help"
+msgstr "F1 ? H  Aide"
+
+#: ../alsamixer/mixer_widget.c:184
+msgid "F2 /    System information"
+msgstr "F2 /    Informations système"
+
+#: ../alsamixer/mixer_widget.c:185
+msgid "F3      Show playback controls"
+msgstr "F3      Afficher les commandes de lecture"
+
+#: ../alsamixer/mixer_widget.c:186
+msgid "F4      Show capture controls"
+msgstr "F4      Afficher les commandes de capture"
+
+#: ../alsamixer/mixer_widget.c:187
+msgid "F5      Show all controls"
+msgstr "F5      Afficher toutes les commandes"
+
+#: ../alsamixer/mixer_widget.c:188
+msgid "Tab     Toggle view mode (F3/F4/F5)"
+msgstr "Tab     Choisir le mode de visualisation (F3/F4/F5)"
+
+#: ../alsamixer/mixer_widget.c:189
+msgid "F6 S    Select sound card"
+msgstr "F6 S    Choisir la carte son"
+
+#: ../alsamixer/mixer_widget.c:190
+msgid "L       Redraw screen"
+msgstr "L       Actualiser l'écran"
+
+#: ../alsamixer/mixer_widget.c:192
+msgid "Left    Move to the previous control"
+msgstr "Gauche  Aller à la commande précédente"
+
+#: ../alsamixer/mixer_widget.c:193
+msgid "Right   Move to the next control"
+msgstr "Droite  Aller à la commande suivante"
+
+#: ../alsamixer/mixer_widget.c:195
+msgid "Up/Down    Change volume"
+msgstr "Haut/Bas   Ajuster le volume"
+
+#: ../alsamixer/mixer_widget.c:196
+msgid "+ -        Change volume"
+msgstr "+ -        Ajuster le volume"
+
+#: ../alsamixer/mixer_widget.c:197
+msgid "Page Up/Dn Change volume in big steps"
+msgstr "Page Préc./Suiv. Ajuster le volume en grandes intervalles"
+
+#: ../alsamixer/mixer_widget.c:198
+msgid "End        Set volume to 0%"
+msgstr "Fin        Couper le volume"
+
+#: ../alsamixer/mixer_widget.c:199
+msgid "0-9        Set volume to 0%-90%"
+msgstr "0-9        Ajuster le volume entre 0 et 90%"
+
+#: ../alsamixer/mixer_widget.c:200
+msgid "Q W E      Increase left/both/right volumes"
+msgstr "Q W E      Augmenter les volumes de gauche/centre/droite"
+
+#. TRANSLATORS: or Y instead of Z
+#: ../alsamixer/mixer_widget.c:202
+msgid "Z X C      Decrease left/both/right volumes"
+msgstr "Z X C      Baisser les volumes de gauche/centre/droite"
+
+#: ../alsamixer/mixer_widget.c:203
+msgid "B          Balance left and right volumes"
+msgstr "B          Égaliser les volumes de gauche et droite"
+
+#: ../alsamixer/mixer_widget.c:205
+msgid "M          Toggle mute"
+msgstr "M          (Dés)activer le mode muet"
+
+#. TRANSLATORS: or , .
+#: ../alsamixer/mixer_widget.c:207
+msgid "< >        Toggle left/right mute"
+msgstr "< >        (Dés)activer le mode muet à gauche et à droite"
+
+#: ../alsamixer/mixer_widget.c:209
+msgid "Space      Toggle capture"
+msgstr "Espace     (Dés)activer la capture"
+
+#. TRANSLATORS: or Insert Delete
+#: ../alsamixer/mixer_widget.c:211
+msgid "; '        Toggle left/right capture"
+msgstr ";          (Dés)activer la capture à gauche et à droite"
+
+#: ../alsamixer/mixer_widget.c:213
+msgid "Authors:"
+msgstr "Auteurs:"
+
+#: ../alsamixer/mixer_widget.c:214
+msgid "  Tim Janik <timj@gtk.org>"
+msgstr ""
+
+#: ../alsamixer/mixer_widget.c:215
+msgid "  Jaroslav Kysela <perex@perex.cz>"
+msgstr ""
+
+#: ../alsamixer/mixer_widget.c:216
+msgid "  Clemens Ladisch <clemens@ladisch.de>"
+msgstr ""
+
+#: ../alsamixer/mixer_widget.c:218
+msgid "Help"
+msgstr "Aide"
+
+#: ../alsamixer/proc_files.c:103
+msgid "Select File"
+msgstr "Choisir un Fichier"
+
+#: ../alsamixer/textbox.c:52 ../alsamixer/textbox.c:66
+msgid "Error"
+msgstr "Erreur"
+
+#: ../alsamixer/textbox.c:80
+#, c-format
+msgid "Cannot open file \"%s\"."
+msgstr "Le fichier \"%s\" n'a pas pu être ouvert."
+
+#: ../aplay/aplay.c:147
+msgid "raw data"
+msgstr "données brutes"
+
+#: ../aplay/aplay.c:148
+msgid "VOC"
+msgstr ""
+
+#. FIXME: can WAV handle exactly 2GB or less than it?
+#: ../aplay/aplay.c:150
+msgid "WAVE"
+msgstr ""
+
+#: ../aplay/aplay.c:151
+msgid "Sparc Audio"
+msgstr ""
+
+#: ../aplay/aplay.c:172
+#, c-format
+msgid ""
+"Usage: %s [OPTION]... [FILE]...\n"
+"\n"
+"-h, --help              help\n"
+"    --version           print current version\n"
+"-l, --list-devices      list all soundcards and digital audio devices\n"
+"-L, --list-pcms         list device names\n"
+"-D, --device=NAME       select PCM by name\n"
+"-q, --quiet             quiet mode\n"
+"-t, --file-type TYPE    file type (voc, wav, raw or au)\n"
+"-c, --channels=#        channels\n"
+"-f, --format=FORMAT     sample format (case insensitive)\n"
+"-r, --rate=#            sample rate\n"
+"-d, --duration=#        interrupt after # seconds\n"
+"-M, --mmap              mmap stream\n"
+"-N, --nonblock          nonblocking mode\n"
+"-F, --period-time=#     distance between interrupts is # microseconds\n"
+"-B, --buffer-time=#     buffer duration is # microseconds\n"
+"    --period-size=#     distance between interrupts is # frames\n"
+"    --buffer-size=#     buffer duration is # frames\n"
+"-A, --avail-min=#       min available space for wakeup is # microseconds\n"
+"-R, --start-delay=#     delay for automatic PCM start is # microseconds \n"
+"                        (relative to buffer size if <= 0)\n"
+"-T, --stop-delay=#      delay for automatic PCM stop is # microseconds from "
+"xrun\n"
+"-v, --verbose           show PCM structure and setup (accumulative)\n"
+"-V, --vumeter=TYPE      enable VU meter (TYPE: mono or stereo)\n"
+"-I, --separate-channels one file for each channel\n"
+"    --disable-resample  disable automatic rate resample\n"
+"    --disable-channels  disable automatic channel conversions\n"
+"    --disable-format    disable automatic format conversions\n"
+"    --disable-softvol   disable software volume control (softvol)\n"
+"    --test-position     test ring buffer position\n"
+"    --test-coef=#\t test coeficient for ring buffer position (default 8)\n"
+"                        expression for validation is: coef * (buffer_size / "
+"2)\n"
+"    --test-nowait       do not wait for ring buffer - eats whole CPU\n"
+"    --max-file-time=#   start another output file when the old file has "
+"recorded\n"
+"                        for this many seconds\n"
+"    --process-id-file   write the process ID here\n"
+"    --use-strftime      apply the strftime facility to the output file name\n"
+msgstr ""
+"Utilisation: %s [OPTION]... [FICHIER]...\n"
+"\n"
+"-h, --help              aide\n"
+"    --version           afficher la version du programme\n"
+"-l, --list-devices      afficher la listes des périphériques\n"
+"-L, --list-pcms         afficher la liste des noms de périphériques\n"
+"-D, --device=NOM        choisir le périphérique PCM par son nom\n"
+"-Q, --quiet             mode silencieux\n"
+"-t, --file-type TYPE    type de fichier (voc, wav, raw ou au)\n"
+"-c, --channels=#        canaux\n"
+"-f, --format=FORMAT     format d'échantillonage (insensible à la casse)\n"
+"-r, --rate=#            fréquence d'échantillonage\n"
+"-d, --duration=#        interrompre après # secondes\n"
+"-M, --mmap              charger le flux en mémoire avec mmap\n"
+"-N, --nonblock          mode non-bloquant\n"
+"-F, --period-time=#     # microsecondes entre chaque interruption\n"
+"-B, --buffer-time=#     la durée du tampon est de # microsecondes\n"
+"    --period-size=#     # trames entre chaque interruption\n"
+"    --buffer-size=#     la durée du tampon est de # trames\n"
+"-A, --avail-min=#       # microsecondes min. disponibles entre chaque réveil\n"
+"-R, --start-delay=#     # microsecondes de délai avant le démarrage\n"
+"                        automatique du périphérique PCM (relatif à la taille\n"
+"                        du tampon si <= 0)\n"
+"-T, --stop-delay=#      # microsecondes de délai depuis xrun avant l'arrêt\n"
+"                        automatique du périphérique PCM \n"
+"-v, --verbose           afficher la structure et la configuration du\n"
+"                        périphérique PCM (accumulatif)\n"
+"-V, --vumeter=TYPE      activer le VU-mètre (TYPE: mono ou stereo)\n"
+"-I, --separate-channels un fichier par canal\n"
+"    --disable-resample  désactiver le rééchantillonage de fréquence\n"
+"                        automatique\n"
+"    --disable-channels  désactiver les conversions automatiques de canal\n"
+"    --disable-format    désactiver les conversions automatiques de format\n"
+"    --disable-softvol   désactiver la commande du volume logicielle\n"
+"    --test-position     tester la position du tampon circulaire\n"
+"    --test-coef=#       test coefficient pour la position du tampon\n"
+"                        circulaire (par défaut 8). L'expression pour la\n"
+"                        validation est : coef * (taille_tampon / 2)\n"
+"    --test-nowait       ne pas attendre le tampon circulaire - prend toutes\n"
+"                        les resources du processeur\n"
+"    --max-file-time=#   commencer un autre fichier de capture quand l'ancien\n"
+"                        fichier a enregistré pendant # secondes\n"
+"    --process-id-file   écrire l'identifiant du processus ici\n"
+"    --use-strftime      utiliser strftime pour le nom du fichier de capture\n"
+
+#: ../aplay/aplay.c:211 ../speaker-test/speaker-test.c:750
+#, c-format
+msgid "Recognized sample formats are:"
+msgstr "Les formats d'échantillonage connus sont:"
+
+#: ../aplay/aplay.c:217
+#, c-format
+msgid ""
+"\n"
+"Some of these may not be available on selected hardware\n"
+msgstr ""
+"\n"
+"Certains de ceux-ci ne sont peut être pas disponibles avec ce matériel\n"
+
+#: ../aplay/aplay.c:218
+#, c-format
+msgid "The availabled format shortcuts are:\n"
+msgstr "Les raccourcis format disponibles sont:\n"
+
+#: ../aplay/aplay.c:219
+#, c-format
+msgid "-f cd (16 bit little endian, 44100, stereo)\n"
+msgstr "-f cd (16 bit petit boutiste, 44100, stéréo)\n"
+
+#: ../aplay/aplay.c:220
+#, c-format
+msgid "-f cdr (16 bit big endian, 44100, stereo)\n"
+msgstr "-f cdr (16 bit gros boutiste, 44100, stéréo)\n"
+
+#: ../aplay/aplay.c:221
+#, c-format
+msgid "-f dat (16 bit little endian, 48000, stereo)\n"
+msgstr "-f dat (16 bit petit boutiste, 48000, stéréo)\n"
+
+#: ../aplay/aplay.c:235
+msgid "no soundcards found..."
+msgstr "aucune carte son n'a été trouvée..."
+
+#: ../aplay/aplay.c:238
+#, c-format
+msgid "**** List of %s Hardware Devices ****\n"
+msgstr "**** Liste des Périphériques Matériels %s ****\n"
+
+#: ../aplay/aplay.c:267
+#, c-format
+msgid "card %i: %s [%s], device %i: %s [%s]\n"
+msgstr "carte %i: %s [%s], périphérique %i: %s [%s]\n"
+
+#: ../aplay/aplay.c:273
+#, c-format
+msgid "  Subdevices: %i/%i\n"
+msgstr "  Sous-périphériques: %i/%i\n"
+
+#: ../aplay/aplay.c:280
+#, c-format
+msgid "  Subdevice #%i: %s\n"
+msgstr "  Sous-périphérique #%i: %s\n"
+
+#: ../aplay/aplay.c:356
+#, c-format
+msgid "Aborted by signal %s...\n"
+msgstr "Interrompu par le signal %s...\n"
+
+#: ../aplay/aplay.c:467
+msgid "command should be named either arecord or aplay"
+msgstr "la commande doit être nommée arecord ou aplay"
+
+#: ../aplay/aplay.c:506
+#, c-format
+msgid "unrecognized file format %s"
+msgstr "format de fichier inconnu %s"
+
+#: ../aplay/aplay.c:513
+#, c-format
+msgid "value %i for channels is invalid"
+msgstr "la valeur %i pour les canaux est incorrecte"
+
+#: ../aplay/aplay.c:532
+#, c-format
+msgid "wrong extended format '%s'"
+msgstr "format étendu '%s' erronné"
+
+#: ../aplay/aplay.c:543
+#, c-format
+msgid "bad speed value %i"
+msgstr "mauvaise valeur de vitesse %i"
+
+#: ../aplay/aplay.c:638
+#, c-format
+msgid "Try `%s --help' for more information.\n"
+msgstr "Essayez `%s --help' pour plus d'informations.\n"
+
+#: ../aplay/aplay.c:654
+#, c-format
+msgid "audio open error: %s"
+msgstr "erreur à l'ouverture audio: %s"
+
+#: ../aplay/aplay.c:659
+#, c-format
+msgid "info error: %s"
+msgstr "erreur info: %s"
+
+#: ../aplay/aplay.c:666
+#, c-format
+msgid "nonblock setting error: %s"
+msgstr "paramètre de non-blocage erronné: %s"
+
+#: ../aplay/aplay.c:676 ../aplay/aplay.c:800 ../aplay/aplay.c:1167
+msgid "not enough memory"
+msgstr "pas assez de mémoire"
+
+#: ../aplay/aplay.c:700
+#, c-format
+msgid "Cannot create process ID file %s: %s"
+msgstr "Le fichier de numéro de processus %s n'a pas pu être créé: %s"
+
+#: ../aplay/aplay.c:790
+#, c-format
+msgid "read error (called from line %i)"
+msgstr "erreur de lecture (appelé à la ligne %i)"
+
+#: ../aplay/aplay.c:848
+#, c-format
+msgid "unknown length of 'fmt ' chunk (read %u, should be %u at least)"
+msgstr "longueur du morceau 'fmt ' inconnue (lu %u, devrait être au moins %u)"
+
+#: ../aplay/aplay.c:858
+#, c-format
+msgid ""
+"unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)"
+msgstr ""
+"longueur du morceau 'fmt ' extensible inconnue (lu %u, devrait être au moins %u)"
+
+#: ../aplay/aplay.c:863
+msgid "wrong format tag in extensible 'fmt ' chunk"
+msgstr "libellé de format erronné dans le morceau extensible 'fmt '"
+
+#: ../aplay/aplay.c:870
+#, c-format
+msgid "can't play WAVE-file format 0x%04x which is not PCM or FLOAT encoded"
+msgstr "Les formats de ficher WAVE 0x%04x qui ne sont pas encodés en PCM ou"
+       "FLOAT ne sont pas supportés"
+
+#: ../aplay/aplay.c:874
+#, c-format
+msgid "can't play WAVE-files with %d tracks"
+msgstr "Les fichiers WAVE avec %d pistes ne sont pas supportés"
+
+#: ../aplay/aplay.c:882 ../aplay/aplay.c:982
+#, c-format
+msgid "Warning: format is changed to U8\n"
+msgstr "Avertissement: le format est changé en U8\n"
+
+#: ../aplay/aplay.c:888
+#, c-format
+msgid "Warning: format is changed to S16_LE\n"
+msgstr "Avertissement: le format est changé en S16_LE\n"
+
+#: ../aplay/aplay.c:896
+#, c-format
+msgid "Warning: format is changed to S24_3LE\n"
+msgstr "Avertissement: le format est changé en S24_3LE\n"
+
+#: ../aplay/aplay.c:902
+#, c-format
+msgid "Warning: format is changed to S24_LE\n"
+msgstr "Avertissement: le format est changé en S24_LE\n"
+
+#: ../aplay/aplay.c:906
+#, c-format
+msgid ""
+" can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)"
+msgstr ""
+" les fichiers WAVE échantillonés %d bits par %d octets en largeur (%d canaux)"
+"ne sont pas supportés"
+
+#: ../aplay/aplay.c:918
+#, c-format
+msgid " can't play WAVE-files with sample %d bits wide"
+msgstr " les fichiers WAVES échantillonés %d bits en largeur ne sont pas"
+"supportés"
+
+#: ../aplay/aplay.c:976
+#, c-format
+msgid "Warning: format is changed to MU_LAW\n"
+msgstr "Avertissement: le format est changé en MU_LAW\n"
+
+#: ../aplay/aplay.c:988
+#, c-format
+msgid "Warning: format is changed to S16_BE\n"
+msgstr "Avertissement: le format est changé en S16_BE\n"
+
+#: ../aplay/aplay.c:1001 ../aplay/aplay.c:1843 ../aplay/aplay.c:1850
+#: ../aplay/aplay.c:2372 ../aplay/aplay.c:2384
+msgid "read error"
+msgstr "erreur de lecture"
+
+#: ../aplay/aplay.c:1031
+msgid "Broken configuration for this PCM: no configurations available"
+msgstr "Configuration erronée pour ce périphérique PCM: pas de configuration"
+
+#: ../aplay/aplay.c:1048
+msgid "Access type not available"
+msgstr "Ce type d'accès n'est pas disponible"
+
+#: ../aplay/aplay.c:1053
+msgid "Sample format non available"
+msgstr "Ce format d'échantillonage n'est pas disponible"
+
+#: ../aplay/aplay.c:1059
+msgid "Channels count non available"
+msgstr "Nombre de canaux non disponible"
+
+#: ../aplay/aplay.c:1074
+#, c-format
+msgid "Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"
+msgstr "Avertissement: la fréquence n'est pas précise (demandé = %iHz,\n"
+       "obtenu = %iHz)\n"
+
+#: ../aplay/aplay.c:1080
+#, c-format
+msgid "         please, try the plug plugin %s\n"
+msgstr "         veuillez essayez le greffon de branchement %s\n"
+
+#: ../aplay/aplay.c:1116
+msgid "Unable to install hw params:"
+msgstr "Les paramètres matériel n'ont pas pu être installés:"
+
+#: ../aplay/aplay.c:1123
+#, c-format
+msgid "Can't use period equal to buffer size (%lu == %lu)"
+msgstr "Une période égale à la taille du tampon (%lu == %lu) ne peut être"
+       "utilisée"
+
+#: ../aplay/aplay.c:1154
+msgid "unable to install sw params:"
+msgstr "Les paramètres logiciel n'ont pas pu être installés:"
+
+#: ../aplay/aplay.c:1229
+#, c-format
+msgid "status error: %s"
+msgstr "erreur d'état: %s"
+
+#: ../aplay/aplay.c:1239 ../aplay/aplay.c:1250
+#, c-format
+msgid "%s!!! (at least %.3f ms long)\n"
+msgstr "%s!!! (au moins longue de %.3f ms)\n"
+
+#: ../aplay/aplay.c:1240 ../aplay/aplay.c:1243 ../aplay/aplay.c:1251
+msgid "underrun"
+msgstr "perte"
+
+#: ../aplay/aplay.c:1240 ../aplay/aplay.c:1251
+msgid "overrun"
+msgstr "dépassement"
+
+#: ../aplay/aplay.c:1255
+#, c-format
+msgid "Status:\n"
+msgstr "État:\n"
+
+#: ../aplay/aplay.c:1259
+#, c-format
+msgid "xrun: prepare error: %s"
+msgstr ""
+
+#: ../aplay/aplay.c:1265
+#, c-format
+msgid "Status(DRAINING):\n"
+msgstr ""
+
+#: ../aplay/aplay.c:1269
+#, c-format
+msgid "capture stream format change? attempting recover...\n"
+msgstr "changement de format de flux de capture? tentative de récupération...\n"
+
+#: ../aplay/aplay.c:1271
+#, c-format
+msgid "xrun(DRAINING): prepare error: %s"
+msgstr ""
+
+#: ../aplay/aplay.c:1278
+#, c-format
+msgid "Status(R/W):\n"
+msgstr ""
+
+#: ../aplay/aplay.c:1281
+#, c-format
+msgid "read/write error, state = %s"
+msgstr "erreur de lecture/écriture, état = %s"
+
+#: ../aplay/aplay.c:1291
+#, c-format
+msgid "Suspended. Trying resume. "
+msgstr "Suspendu. Tentative de reprise. "
+
+#: ../aplay/aplay.c:1296
+#, c-format
+msgid "Failed. Restarting stream. "
+msgstr "Échec. Redémarrage du flux."
+
+#: ../aplay/aplay.c:1298
+#, c-format
+msgid "suspend: prepare error: %s"
+msgstr "suspension: prepare error: %s"
+
+#: ../aplay/aplay.c:1303
+#, c-format
+msgid "Done.\n"
+msgstr "Terminé.\n"
+
+#: ../aplay/aplay.c:1325
+#, c-format
+msgid " !clip  "
+msgstr ""
+
+#: ../aplay/aplay.c:1472
+#, c-format
+msgid "Unsupported bit size %d.\n"
+msgstr "%d bit(s) non supporté(s).\n"
+
+#: ../aplay/aplay.c:1506
+#, c-format
+msgid "Max peak (%li samples): 0x%08x "
+msgstr "Pic max. (%li échantillons): 0x%08x "
+
+#: ../aplay/aplay.c:1540
+#, c-format
+msgid ""
+"Suspicious buffer position (%li total): avail = %li, delay = %li, buffer = %"
+"li\n"
+msgstr ""
+"Position de tampon suspicieuse (total %li): dispo = %li, délai = %li, tampon ="
+" %li\n"
+
+#: ../aplay/aplay.c:1603
+#, c-format
+msgid "write error: %s"
+msgstr "erreur en écriture: %s"
+
+#: ../aplay/aplay.c:1649
+#, c-format
+msgid "writev error: %s"
+msgstr "erreur de writev: %s"
+
+#: ../aplay/aplay.c:1692
+#, c-format
+msgid "read error: %s"
+msgstr "erreur en lecture: %s"
+
+#: ../aplay/aplay.c:1735
+#, c-format
+msgid "readv error: %s"
+msgstr "erreur de readv: %s"
+
+#: ../aplay/aplay.c:1783
+msgid "can't allocate buffer for silence"
+msgstr "Un tampon pour silence n'a pas pu être alloué"
+
+#. to stderr
+#: ../aplay/aplay.c:1792 ../aplay/aplay.c:2018 ../aplay/aplay.c:2023
+#: ../aplay/aplay.c:2070 ../aplay/aplay.c:2079 ../aplay/aplay.c:2086
+#: ../aplay/aplay.c:2096 ../aplay/aplay.c:2102 ../aplay/aplay.c:2174
+#: ../aplay/aplay.c:2204 ../aplay/aplay.c:2218
+msgid "write error"
+msgstr "erreur en écriture"
+
+#: ../aplay/aplay.c:1805
+#, c-format
+msgid "voc_pcm_flush - silence error"
+msgstr "voc_pcm_flush - erreur de silence"
+
+#: ../aplay/aplay.c:1808
+msgid "voc_pcm_flush error"
+msgstr "erreur voc_pcm_flush"
+
+#: ../aplay/aplay.c:1834
+msgid "malloc error"
+msgstr "erreur malloc"
+
+#: ../aplay/aplay.c:1838
+#, c-format
+msgid "Playing Creative Labs Channel file '%s'...\n"
+msgstr "Lecture du ficher de canaux Creative Labs '%s'...\n"
+
+#. /dev/dsp can't it
+#: ../aplay/aplay.c:1906 ../aplay/aplay.c:1998
+msgid "can't play packed .voc files"
+msgstr "les fichiers .voc de type 'packed' ne sont pas supportés"
+
+#: ../aplay/aplay.c:1958
+#, c-format
+msgid "can't play loops; %s isn't seekable\n"
+msgstr "%s n'est pas navigable; les motifs n'ont pas pu être lus\n"
+
+#: ../aplay/aplay.c:2007
+#, c-format
+msgid "unknown blocktype %d. terminate."
+msgstr "type de bloc %d inconnu. fin."
+
+#: ../aplay/aplay.c:2138
+#, c-format
+msgid "Wave doesn't support %s format..."
+msgstr "Le format wave %s n'est pas supporté..."
+
+#: ../aplay/aplay.c:2198
+#, c-format
+msgid "Sparc Audio doesn't support %s format..."
+msgstr "Le format %s Sparc Audio n'est pas supporté..."
+
+#: ../aplay/aplay.c:2279
+msgid "Playing"
+msgstr "Lecture"
+
+#: ../aplay/aplay.c:2279
+msgid "Recording"
+msgstr "Capture"
+
+#: ../aplay/aplay.c:2283
+#, c-format
+msgid "Rate %d Hz, "
+msgstr "Fréquence %d Hz, "
+
+#: ../aplay/aplay.c:2285
+#, c-format
+msgid "Mono"
+msgstr ""
+
+#: ../aplay/aplay.c:2287
+#, c-format
+msgid "Stereo"
+msgstr "Stéréo"
+
+#: ../aplay/aplay.c:2289
+#, c-format
+msgid "Channels %i"
+msgstr "%i Canaux"
+
+#: ../aplay/aplay.c:2798 ../aplay/aplay.c:2851
+#, c-format
+msgid "You need to specify %d files"
+msgstr "Vous devez spécifier %d fichiers"
+
+#: ../seq/aconnect/aconnect.c:49
+#, c-format
+msgid "aconnect - ALSA sequencer connection manager\n"
+msgstr "aconnect - gestionnaire de connection séquenceur ALSA\n"
+
+#: ../seq/aconnect/aconnect.c:50
+#, c-format
+msgid "Copyright (C) 1999-2000 Takashi Iwai\n"
+msgstr ""
+
+#: ../seq/aconnect/aconnect.c:51
+#, c-format
+msgid "Usage:\n"
+msgstr "Utilisation:\n"
+
+#: ../seq/aconnect/aconnect.c:52
+#, c-format
+msgid " * Connection/disconnection between two ports\n"
+msgstr " * Connexion/déconnexion entre deux ports\n"
+
+#: ../seq/aconnect/aconnect.c:53
+#, c-format
+msgid "   aconnect [-options] sender receiver\n"
+msgstr "   aconnect [-options] envoyeur destinataire\n"
+
+#: ../seq/aconnect/aconnect.c:54
+#, c-format
+msgid "     sender, receiver = client:port pair\n"
+msgstr "     envoyeur, destinataire = couple client:port\n"
+
+#: ../seq/aconnect/aconnect.c:55
+#, c-format
+msgid "     -d,--disconnect     disconnect\n"
+msgstr "     -d,--disconnect     déconnecter\n"
+
+#: ../seq/aconnect/aconnect.c:56
+#, c-format
+msgid "     -e,--exclusive      exclusive connection\n"
+msgstr "     -e,--exclusive      connexion exclusive\n"
+
+#: ../seq/aconnect/aconnect.c:57
+#, c-format
+msgid "     -r,--real #         convert real-time-stamp on queue\n"
+msgstr ""
+
+#: ../seq/aconnect/aconnect.c:58
+#, c-format
+msgid "     -t,--tick #         convert tick-time-stamp on queue\n"
+msgstr ""
+
+#: ../seq/aconnect/aconnect.c:59
+#, c-format
+msgid " * List connected ports (no subscription action)\n"
+msgstr " * Afficher la liste des ports connectés (pas de souscription)\n"
+
+#: ../seq/aconnect/aconnect.c:60
+#, c-format
+msgid "   aconnect -i|-o [-options]\n"
+msgstr "   aconnect -i|-o [-options]\n"
+
+#: ../seq/aconnect/aconnect.c:61
+#, c-format
+msgid "     -i,--input          list input (readable) ports\n"
+msgstr "     -i,--input          afficher la liste des ports d'entrée\n"
+
+#: ../seq/aconnect/aconnect.c:62
+#, c-format
+msgid "     -o,--output         list output (writable) ports\n"
+msgstr "     -o,--output         afficher la liste des ports de sortie\n"
+
+#: ../seq/aconnect/aconnect.c:63
+#, c-format
+msgid "     -l,--list           list current connections of each port\n"
+msgstr "     -l,--list           afficher la liste des connexions en cours\n"
+       "                         sur chaque port\n"
+
+#: ../seq/aconnect/aconnect.c:64
+#, c-format
+msgid " * Remove all exported connections\n"
+msgstr " * Retirer toutes les connexions exportées\n"
+
+#: ../seq/aconnect/aconnect.c:65
+#, c-format
+msgid "     -x, --removeall\n"
+msgstr "     -x, --removeall\n"
+
+#: ../seq/aconnect/aconnect.c:132
+msgid "Connecting To"
+msgstr "Connexion À"
+
+#: ../seq/aconnect/aconnect.c:133
+msgid "Connected From"
+msgstr "Connecté Depuis"
+
+#: ../seq/aconnect/aconnect.c:169
+#, c-format
+msgid "client %d: '%s' [type=%s]\n"
+msgstr "client %d: '%s' [type=%s]\n"
+
+#: ../seq/aconnect/aconnect.c:173
+msgid "user"
+msgstr "utilisateur"
+
+#: ../seq/aconnect/aconnect.c:173
+msgid "kernel"
+msgstr "noyau"
+
+#: ../seq/aconnect/aconnect.c:307
+#, c-format
+msgid "can't open sequencer\n"
+msgstr "le séquenceur n'a pas pu être ouvert\n"
+
+#: ../seq/aconnect/aconnect.c:335
+#, c-format
+msgid "can't get client id\n"
+msgstr "l'identifiant du client n'as pas pu être obtenu\n"
+
+#: ../seq/aconnect/aconnect.c:342
+#, c-format
+msgid "can't set client info\n"
+msgstr "les infos du client n'ont pas pu être définies\n"
+
+#: ../seq/aconnect/aconnect.c:349
+#, c-format
+msgid "invalid sender address %s\n"
+msgstr "l'addresse envoyeur %s est incorrecte\n"
+
+#: ../seq/aconnect/aconnect.c:354 ../seq/aseqnet/aseqnet.c:290
+#, c-format
+msgid "invalid destination address %s\n"
+msgstr "l'addresse destinataire %s est incorrecte\n"
+
+#: ../seq/aconnect/aconnect.c:368
+#, c-format
+msgid "No subscription is found\n"
+msgstr "Aucune souscription trouvée\n"
+
+#: ../seq/aconnect/aconnect.c:373
+#, c-format
+msgid "Disconnection failed (%s)\n"
+msgstr "La déconnexion a échoué (%s)\n"
+
+#: ../seq/aconnect/aconnect.c:379
+#, c-format
+msgid "Connection is already subscribed\n"
+msgstr "La connexion a déjà été souscrite\n"
+
+#: ../seq/aconnect/aconnect.c:384
+#, c-format
+msgid "Connection failed (%s)\n"
+msgstr "Échec de connexion (%s)\n"
+
+#: ../seq/aseqnet/aseqnet.c:164
+#, c-format
+msgid "aseqnet - network client/server on ALSA sequencer\n"
+msgstr "aseqnet - client/serveur réseau sur le séquenceur ALSA\n"
+
+#: ../seq/aseqnet/aseqnet.c:165
+#, c-format
+msgid "  Copyright (C) 1999 Takashi Iwai\n"
+msgstr ""
+
+#: ../seq/aseqnet/aseqnet.c:166
+#, c-format
+msgid "usage:\n"
+msgstr "utilisation:\n"
+
+#: ../seq/aseqnet/aseqnet.c:167
+#, c-format
+msgid "  server mode: aseqnet [-options]\n"
+msgstr "  mode serveur: aseqnet [-options]\n"
+
+#: ../seq/aseqnet/aseqnet.c:168
+#, c-format
+msgid "  client mode: aseqnet [-options] server_host\n"
+msgstr "  mode client: aseqnet [-options] hôte_serveur\n"
+
+#: ../seq/aseqnet/aseqnet.c:169
+#, c-format
+msgid "options:\n"
+msgstr "options:\n"
+
+#: ../seq/aseqnet/aseqnet.c:170
+#, c-format
+msgid "  -p,--port # : sepcify TCP port (digit or service name)\n"
+msgstr "  -p,--port # : spécifier le port TCP (numéro ou nom de service)\n"
+
+#: ../seq/aseqnet/aseqnet.c:171
+#, c-format
+msgid "  -s,--source addr : read from given addr (client:port)\n"
+msgstr "  -s,--source addr : lire à partir de l'addr. donnée (client:port)\n"
+
+#: ../seq/aseqnet/aseqnet.c:172
+#, c-format
+msgid "  -d,--dest addr : write to given addr (client:port)\n"
+msgstr "  -d,--dest addr : écrire à l'addr. donnée (client:port)\n"
+
+#: ../seq/aseqnet/aseqnet.c:173
+#, c-format
+msgid "  -v, --verbose : print verbose messages\n"
+msgstr "  -v, --verbose : affichage locace de messages\n"
+
+#: ../seq/aseqnet/aseqnet.c:174
+#, c-format
+msgid "  -i, --info : print certain received events\n"
+msgstr "  -i, --info : afficher certains évènements reçus\n"
+
+#: ../seq/aseqnet/aseqnet.c:188
+#, c-format
+msgid "can't malloc\n"
+msgstr "échec allocation mémoire (malloc)\n"
+
+#: ../seq/aseqnet/aseqnet.c:213
+#, c-format
+msgid "closing files..\n"
+msgstr "fermeture des fichiers...\n"
+
+#: ../seq/aseqnet/aseqnet.c:272
+#, c-format
+msgid "sequencer opened: %d:%d\n"
+msgstr "séquenceur ouvert: %d:%d\n"
+
+#: ../seq/aseqnet/aseqnet.c:279
+#, c-format
+msgid "invalid source address %s\n"
+msgstr "addresse source %s incorrecte\n"
+
+#: ../seq/aseqnet/aseqnet.c:309
+#, c-format
+msgid "service '%s' is not found in /etc/services\n"
+msgstr "le service '%s' n'as pas été trouvé dans /etc/services\n"
+
+#: ../seq/aseqnet/aseqnet.c:377
+#, c-format
+msgid "too many connections!\n"
+msgstr "trop de connexions!\n"
+
+#: ../seq/aseqnet/aseqnet.c:388
+#, c-format
+msgid "accepted[%d]\n"
+msgstr "accepté[%d]\n"
+
+#: ../seq/aseqnet/aseqnet.c:411
+#, c-format
+msgid "can't get address %s\n"
+msgstr "l'addresse %s na pas pu être obtenue\n"
+
+#: ../seq/aseqnet/aseqnet.c:422
+#, c-format
+msgid "ok.. connected\n"
+msgstr "ok.. connecté\n"
+
+#: ../seq/aseqnet/aseqnet.c:518
+#, c-format
+msgid "Channel %2d: Control event : %5d\n"
+msgstr "Canal %2d: Évènement de contrôle: %5d\n"
+
+#: ../seq/aseqnet/aseqnet.c:522
+#, c-format
+msgid "Channel %2d: Pitchbender   : %5d\n"
+msgstr "Canal %2d: Pitchbender          : %5d\n"
+
+#: ../seq/aseqnet/aseqnet.c:526
+#, c-format
+msgid "Channel %2d: Note On event : %5d\n"
+msgstr "Canal %2d: évènement Note On    : %5d\n"
+
+#: ../seq/aseqnet/aseqnet.c:530
+#, c-format
+msgid "Channel %2d: Note Off event: %5d\n"
+msgstr "Canal %2d: évènement Note Off   : %5d\n"
+
+#: ../seq/aseqnet/aseqnet.c:585
+#, c-format
+msgid "disconnected\n"
+msgstr "déconnecté\n"
+
+#. 0
+#: ../speaker-test/speaker-test.c:103
+msgid "Front Left"
+msgstr "Avant Gauche"
+
+#. 1
+#: ../speaker-test/speaker-test.c:104
+msgid "Front Right"
+msgstr "Avant Droit"
+
+#. 2
+#: ../speaker-test/speaker-test.c:105
+msgid "Rear Left"
+msgstr "Arrière Gauche"
+
+#. 3
+#: ../speaker-test/speaker-test.c:106
+msgid "Rear Right"
+msgstr "Arrière Droit"
+
+#. 5
+#: ../speaker-test/speaker-test.c:108
+msgid "LFE"
+msgstr ""
+
+#. 6
+#: ../speaker-test/speaker-test.c:109
+msgid "Side Left"
+msgstr "Côté Gauche"
+
+#. 7
+#: ../speaker-test/speaker-test.c:110
+msgid "Side Right"
+msgstr "Côté Droit"
+
+#. 8
+#: ../speaker-test/speaker-test.c:111
+msgid "Channel 9"
+msgstr "Canal 9"
+
+#. 9
+#: ../speaker-test/speaker-test.c:112
+msgid "Channel 10"
+msgstr "Canal 10"
+
+#. 10
+#: ../speaker-test/speaker-test.c:113
+msgid "Channel 11"
+msgstr "Canal 11"
+
+#. 11
+#: ../speaker-test/speaker-test.c:114
+msgid "Channel 12"
+msgstr "Canal 12"
+
+#. 12
+#: ../speaker-test/speaker-test.c:115
+msgid "Channel 13"
+msgstr "Canal 13"
+
+#. 13
+#: ../speaker-test/speaker-test.c:116
+msgid "Channel 14"
+msgstr "Canal 14"
+
+#. 14
+#: ../speaker-test/speaker-test.c:117
+msgid "Channel 15"
+msgstr "Canal 15"
+
+#. 15
+#: ../speaker-test/speaker-test.c:118
+msgid "Channel 16"
+msgstr "Canal 16"
+
+#: ../speaker-test/speaker-test.c:317
+#, c-format
+msgid "Broken configuration for playback: no configurations available: %s\n"
+msgstr "Configuration erronée pour la lecture: pas de configuration: %s\n"
+
+#: ../speaker-test/speaker-test.c:324
+#, c-format
+msgid "Access type not available for playback: %s\n"
+msgstr "Le type d'accès est indisponible à la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:331
+#, c-format
+msgid "Sample format not available for playback: %s\n"
+msgstr "Le format d'échantillonage est indisponible à la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:338
+#, c-format
+msgid "Channels count (%i) not available for playbacks: %s\n"
+msgstr "Le nombre (%i) de canaux est indisponible à la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:346
+#, c-format
+msgid "Rate %iHz not available for playback: %s\n"
+msgstr "La fréquence %iHz est indisponible à la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:351
+#, c-format
+msgid "Rate doesn't match (requested %iHz, get %iHz, err %d)\n"
+msgstr "La fréquence ne correspond pas (demandée: %iHz, obtenue %iHz, err %d)\n"
+
+#: ../speaker-test/speaker-test.c:355
+#, c-format
+msgid "Rate set to %iHz (requested %iHz)\n"
+msgstr "La fréquence est %iHz (demandée %iHz)\n"
+
+#: ../speaker-test/speaker-test.c:361
+#, c-format
+msgid "Buffer size range from %lu to %lu\n"
+msgstr "L'intervalle de la taille du tampon est de %lu à %lu\n"
+
+#: ../speaker-test/speaker-test.c:362
+#, c-format
+msgid "Period size range from %lu to %lu\n"
+msgstr "L'intervalle de la taille de la période est de %lu à %lu\n"
+
+#: ../speaker-test/speaker-test.c:364
+#, c-format
+msgid "Requested period time %u us\n"
+msgstr "Temps de période demandé %u µs\n"
+
+#: ../speaker-test/speaker-test.c:367
+#, c-format
+msgid "Unable to set period time %u us for playback: %s\n"
+msgstr "Le temps de période %u µs n'a pas pu être défini pour la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:373
+#, c-format
+msgid "Requested buffer time %u us\n"
+msgstr "Temps tampon demandé %u µs\n"
+
+#: ../speaker-test/speaker-test.c:376
+#, c-format
+msgid "Unable to set buffer time %u us for playback: %s\n"
+msgstr "Le temps tampon de %u µs n'a pas pu être définie pour la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:385
+#, c-format
+msgid "Using max buffer size %lu\n"
+msgstr "Taille max. de tampon %lu utilisée\n"
+
+#: ../speaker-test/speaker-test.c:388
+#, c-format
+msgid "Unable to set buffer size %lu for playback: %s\n"
+msgstr "La taile de tampon %lu n'a pas pu être définie pour la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:394
+#, c-format
+msgid "Periods = %u\n"
+msgstr "Périodes = %u\n"
+
+#: ../speaker-test/speaker-test.c:397
+#, c-format
+msgid "Unable to set nperiods %u for playback: %s\n"
+msgstr "nperiods %u n'a pas pu être défini pour la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:406
+#, c-format
+msgid "Unable to set hw params for playback: %s\n"
+msgstr "Les paramètres matériel n'ont pas pu être définis pour la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:412
+#, c-format
+msgid "was set period_size = %lu\n"
+msgstr ""
+
+#: ../speaker-test/speaker-test.c:413
+#, c-format
+msgid "was set buffer_size = %lu\n"
+msgstr ""
+
+#: ../speaker-test/speaker-test.c:415
+#, c-format
+msgid "buffer to small, could not use\n"
+msgstr "tampon trop petit, il n'a pas pu être utilisé\n"
+
+#: ../speaker-test/speaker-test.c:428
+#, c-format
+msgid "Unable to determine current swparams for playback: %s\n"
+msgstr "Les paramètres logiciels actuels n'ont pas pu être déterminés pour la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:435
+#, c-format
+msgid "Unable to set start threshold mode for playback: %s\n"
+msgstr ""
+
+#: ../speaker-test/speaker-test.c:442
+#, c-format
+msgid "Unable to set avail min for playback: %s\n"
+msgstr ""
+
+#: ../speaker-test/speaker-test.c:449
+#, c-format
+msgid "Unable to set sw params for playback: %s\n"
+msgstr "Les paramètres logiciel n'ont pas pu être définis pour la lecture: %s\n"
+
+#: ../speaker-test/speaker-test.c:464
+#, c-format
+msgid "Can't recovery from underrun, prepare failed: %s\n"
+msgstr ""
+
+#: ../speaker-test/speaker-test.c:475
+#, c-format
+msgid "Can't recovery from suspend, prepare failed: %s\n"
+msgstr ""
+
+#: ../speaker-test/speaker-test.c:539 ../speaker-test/speaker-test.c:954
+#, c-format
+msgid "No enough memory\n"
+msgstr "Pas assez de mémoire\n"
+
+#: ../speaker-test/speaker-test.c:544
+#, c-format
+msgid "Cannot open WAV file %s\n"
+msgstr "Le fichier WAV %s n'a pas pu être ouvert\n"
+
+#: ../speaker-test/speaker-test.c:548 ../speaker-test/speaker-test.c:577
+#, c-format
+msgid "Invalid WAV file %s\n"
+msgstr "Fichier WAV incorrect %s\n"
+
+#: ../speaker-test/speaker-test.c:553
+#, c-format
+msgid "Not a WAV file: %s\n"
+msgstr "Le fichier %s n'est pas au format WAV\n"
+
+#: ../speaker-test/speaker-test.c:557
+#, c-format
+msgid "Unsupported WAV format %d for %s\n"
+msgstr "Format WAV %d non supporté pour %s\n"
+
+#: ../speaker-test/speaker-test.c:562
+#, c-format
+msgid "%s is not a mono stream (%d channels)\n"
+msgstr "%s n'est pas un flux en mono (%d canaux)\n"
+
+#: ../speaker-test/speaker-test.c:567
+#, c-format
+msgid "Sample rate doesn't match (%d) for %s\n"
+msgstr "La fréquence d'échantillonage (%d) ne correspond pas à %s\n"
+
+#: ../speaker-test/speaker-test.c:572
+#, c-format
+msgid "Unsupported sample format bits %d for %s\n"
+msgstr "Le format d'échantillonage %d bits n'est pas supporté pour %s\n"
+
+#: ../speaker-test/speaker-test.c:622
+#, c-format
+msgid "Undefined channel %d\n"
+msgstr "Canal non-défini %d\n"
+
+#: ../speaker-test/speaker-test.c:673
+#, c-format
+msgid "Write error: %d,%s\n"
+msgstr "Erreur en écriture: %d,%s\n"
+
+#: ../speaker-test/speaker-test.c:675
+#, c-format
+msgid "xrun_recovery failed: %d,%s\n"
+msgstr ""
+
+#: ../speaker-test/speaker-test.c:734
+#, c-format
+msgid ""
+"Usage: speaker-test [OPTION]... \n"
+"-h,--help\thelp\n"
+"-D,--device\tplayback device\n"
+"-r,--rate\tstream rate in Hz\n"
+"-c,--channels\tcount of channels in stream\n"
+"-f,--frequency\tsine wave frequency in Hz\n"
+"-F,--format\tsample format\n"
+"-b,--buffer\tring buffer size in us\n"
+"-p,--period\tperiod size in us\n"
+"-P,--nperiods\tnumber of periods\n"
+"-t,--test\tpink=use pink noise, sine=use sine wave, wav=WAV file\n"
+"-l,--nloops\tspecify number of loops to test, 0 = infinite\n"
+"-s,--speaker\tsingle speaker test. Values 1=Left, 2=right, etc\n"
+"-w,--wavfile\tUse the given WAV file as a test sound\n"
+"-W,--wavdir\tSpecify the directory containing WAV files\n"
+"\n"
+msgstr ""
+"Utilisation: speaker-test [OPTION]...\n"
+"-h,--help\taide\n"
+"-D,--device\tpériphérique de lecture\n"
+"-r,--rate\tfréquence du flux en Hz\n"
+"-c,--channels\tnombre de canaux du flux\n"
+"-f,--frequency\tfréquence courbe sinus en Hz\n"
+"-F,--format\tformat d'échantillonage\n"
+"-b,--buffer\ttaille en µs du tampon circulaire\n"
+"-p,--period\ttaille de la période en µs\n"
+"-P,--nperiods\tnombre de périodes\n"
+"-t,--test\tpink=pink noise, sine=courbe sinus, wav=fichier WAV\n"
+"-l,--nloops\tspécifier le nombre de motifs à tester, 0 = infini\n"
+"-s,--speaker\ttest sur une seule enceinte.\n"
+"\t\tValeurs 1=Gauche, 2=Droite, et c.\n"
+"-w,--wavfile\tUtiliser le fichier WAV spécifié pour le test\n"
+"-w,--wavdir\tSpécifier le répertoire contenant les fichiers WAV\n"
+"\n"
+
+#: ../speaker-test/speaker-test.c:852
+#, c-format
+msgid "Invalid number of periods %d\n"
+msgstr "Nombre de périodes %d incorrect\n"
+
+#: ../speaker-test/speaker-test.c:866 ../speaker-test/speaker-test.c:870
+#, c-format
+msgid "Invalid test type %s\n"
+msgstr "Type de test %s incorrect\n"
+
+#: ../speaker-test/speaker-test.c:882
+#, c-format
+msgid "Invalid parameter for -s option.\n"
+msgstr "Paramètre incorrect pour l'option -s.\n"
+
+#: ../speaker-test/speaker-test.c:896
+#, c-format
+msgid "Unknown option '%c'\n"
+msgstr "Option '%c' inconnue\n"
+
+#. fixed format
+#: ../speaker-test/speaker-test.c:910
+#, c-format
+msgid "Playback device is %s\n"
+msgstr "Le périphérique de lecture est %s\n"
+
+#: ../speaker-test/speaker-test.c:911
+#, c-format
+msgid "Stream parameters are %iHz, %s, %i channels\n"
+msgstr "Les paramètres du flux sont %iHz, %s, %i canaux\n"
+
+#: ../speaker-test/speaker-test.c:914
+#, c-format
+msgid "Using 16 octaves of pink noise\n"
+msgstr "Utilisation de 16 octaves de 'pink noise'\n"
+
+#: ../speaker-test/speaker-test.c:917
+#, c-format
+msgid "Sine wave rate is %.4fHz\n"
+msgstr "La fréquence de la courbe sinus est %.4fHz\n"
+
+#: ../speaker-test/speaker-test.c:920
+#, c-format
+msgid "WAV file(s)\n"
+msgstr "fichier(s) WAV\n"
+
+#: ../speaker-test/speaker-test.c:926
+#, c-format
+msgid "Playback open error: %d,%s\n"
+msgstr "Erreur d'ouverture à la lecture: %d,%s\n"
+
+#: ../speaker-test/speaker-test.c:931
+#, c-format
+msgid "Setting of hwparams failed: %s\n"
+msgstr "Échec de la configuration des paramètres matériel: %s\n"
+
+#: ../speaker-test/speaker-test.c:936
+#, c-format
+msgid "Setting of swparams failed: %s\n"
+msgstr "Échec de la configuration des paramètres logiciel: %s\n"
+
+#: ../speaker-test/speaker-test.c:985 ../speaker-test/speaker-test.c:1007
+#, c-format
+msgid "Transfer failed: %s\n"
+msgstr "Échec du transfer: %s\n"
+
+#: ../speaker-test/speaker-test.c:995
+#, c-format
+msgid "Time per period = %lf\n"
+msgstr "Temps par période = %lf\n"
diff --git a/speaker-test/speaker-test.c b/speaker-test/speaker-test.c
index 458a8d7..3029110 100644
--- a/speaker-test/speaker-test.c
+++ b/speaker-test/speaker-test.c
@@ -993,9 +993,9 @@
 
   }
 
-  while ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+  if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
     printf(_("Playback open error: %d,%s\n"), err,snd_strerror(err));
-    sleep(1);
+    exit(EXIT_FAILURE);
   }
 
   if ((err = set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {