Project import generated by Copybara.

GitOrigin-RevId: 77bcd1a7b643dd9ab7c4a29a0e77e96b09566213
diff --git a/COPYING b/COPYING
index c5611e4..623b625 100644
--- a/COPYING
+++ b/COPYING
@@ -55,7 +55,7 @@
 
   The precise terms and conditions for copying, distribution and
 modification follow.
-
+
 		    GNU GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
@@ -110,7 +110,7 @@
     License.  (Exception: if the Program itself is interactive but
     does not normally print such an announcement, your work based on
     the Program is not required to print an announcement.)
-
+
 These requirements apply to the modified work as a whole.  If
 identifiable sections of that work are not derived from the Program,
 and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@
 access to copy the source code from the same place counts as
 distribution of the source code, even though third parties are not
 compelled to copy the source along with the object code.
-
+
   4. You may not copy, modify, sublicense, or distribute the Program
 except as expressly provided under this License.  Any attempt
 otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@
 
 This section is intended to make thoroughly clear what is believed to
 be a consequence of the rest of this License.
-
+
   8. If the distribution and/or use of the Program is restricted in
 certain countries either by patents or by copyrighted interfaces, the
 original copyright holder who places the Program under this License
@@ -278,7 +278,7 @@
 POSSIBILITY OF SUCH DAMAGES.
 
 		     END OF TERMS AND CONDITIONS
-
+
 	    How to Apply These Terms to Your New Programs
 
   If you develop a new program, and you want it to be of the greatest
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..06849ec
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,43 @@
+2005-08-13  gettextize  <bug-gnu-gettext@gnu.org>
+
+	* Makefile.am (EXTRA_DIST): Add config.rpath, mkinstalldirs.
+	* configure.in (AM_GNU_GETTEXT_VERSION): Bump to 0.14.4.
+
+0.0.7 -> 0.2.0
+
+* removed error message from alsamixer
+* fixes in alsactl program
+* version is now compatible with driver
+
+0.0.6 -> 0.0.7
+
+* added alsactl control program
+* updated alsamixer by Carl van Schaik <carl@dreamcoat.che.uct.ac.za>
+  - stereo record source select
+  - left/right route support
+
+0.0.5 -> 0.0.6
+
+* updates for new alsa-lib
+* fixed compilation problems with config.h header file
+* updated output 'aplay -l'
+
+0.0.3 -> 0.0.4
+
+* added check for alsa-lib package
+* added spec file for RPM
+
+0.0.2 -> 0.0.3
+
+* included great ncurses alsamixer from Tim Janik <timj@gtk.org>
+* enhanced Mixer from Andy Lo A Foe <arloafoe@cs.vu.nl> renamed to amixer
+
+0.0.1 -> 0.0.2
+
+* fixed small bug in aplay in command line parsing
+* fixed fragment size allocation for low rate files in aplay
+* a little bit corrected output from Mixer
+
+0.0.1
+
+* initial release (aplay, broken Mixer)
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..202c491
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,46 @@
+
+			ALSA utils installation
+			=======================
+
+Requirements
+------------
+
+alsamixer requires the ncurses library.  Some systems need to install
+the develop package additionally to link with this library.
+
+alsaconf requires dialog or whiptail program to run properly.
+
+Installation
+------------
+
+For installation you can use these commands:
+
+	./configure
+	make install
+
+If ./configure command complain that alsa-lib package isn't installed,
+please, check if --prefix option is same for alsa-lib and alsa-utils
+package. The configure script from alsa-utils package probably cannot find
+header file asoundlib.h in $prefix/include/alsa directory (usually in
+/usr/include/alsa directory).
+
+Compilation from HG sources
+---------------------------
+
+You need also GNU packages autoconf and automake installed in your system
+to compile HG (Mercurial) sources of alsa-utils package.
+
+For compilation you can use these commands:
+
+	aclocal
+	gettextize
+	autoheader
+	automake --foreign --copy --add-missing
+	autoconf
+	./configure
+	make
+
+The included hgcompile script does this job for you.
+
+Note: Some automake packages have missing aclocal program. Use newer version
+      in the case.
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..d24842f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+COPYING
\ No newline at end of file
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..21d69ab
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,45 @@
+INCLUDES=-I$(top_srcdir)/include
+
+SUBDIRS = include alsactl alsaucm utils m4 po
+if ALSAMIXER
+SUBDIRS += alsamixer
+endif
+if HAVE_MIXER
+SUBDIRS += amixer
+endif
+if HAVE_RAWMIDI
+SUBDIRS += amidi
+endif
+if ALSACONF
+SUBDIRS += alsaconf
+endif
+if HAVE_PCM
+SUBDIRS += aplay iecset speaker-test
+if ALSALOOP
+SUBDIRS += alsaloop
+endif
+endif
+if HAVE_SEQ
+SUBDIRS += seq
+endif
+
+EXTRA_DIST= TODO gitcompile
+AUTOMAKE_OPTIONS=foreign
+ACLOCAL_AMFLAGS = -I m4
+
+rpm: dist
+	$(MAKE) -C $@
+
+dist-hook:
+	-chmod -R a+r $(distdir)
+	@if ! test -z "$(AMTAR)"; then \
+		$(AMTAR) --create --verbose --file=- $(distdir) | bzip2 -c -9 > $(distdir).tar.bz2 ; \
+	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/README b/README
new file mode 100644
index 0000000..c7e0681
--- /dev/null
+++ b/README
@@ -0,0 +1,20 @@
+
+		Advanced Linux Sound Architecture - Utilities
+		=============================================
+
+This package contains the command line utilities for the ALSA project.
+The package can be compiled only with the installed ALSA driver and
+the ALSA C library.
+
+alsaconf	- the ALSA driver configurator script
+alsactl		- an utility for soundcard settings management
+aplay/arecord	- an utility for the playback / capture of .wav,.voc,.au files
+amixer		- a command line mixer
+alsamixer	- a ncurses mixer
+amidi		- a utility to send/receive sysex dumps or other MIDI data
+iecset		- a utility to show/set the IEC958 status bits
+
+You may give a look for more information about the ALSA project to URL
+http://www.alsa-project.org.
+
+					Jaroslav Kysela <perex@perex.cz>
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..a1d880f
--- /dev/null
+++ b/TODO
@@ -0,0 +1,4 @@
+H add options to aplay to avoid xrun using arecord | aplay
+M Rewrite aplay/arecord tool and separate the experimental stuff
+M Write a *good* mixer
+L Add support for OSS setups to alsactl
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644
index 0000000..49550f1
--- /dev/null
+++ b/acinclude.m4
@@ -0,0 +1,12 @@
+AC_DEFUN([SAVE_UTIL_VERSION], [
+SND_UTIL_VERSION=$VERSION
+echo $VERSION > $srcdir/version
+AC_DEFINE_UNQUOTED(VERSION, "$SND_UTIL_VERSION", [ALSA util version])
+AC_SUBST(SND_UTIL_VERSION)
+SND_UTIL_MAJOR=`echo $VERSION | cut -d . -f 1`
+AC_SUBST(SND_UTIL_MAJOR)
+SND_UTIL_MINOR=`echo $VERSION | cut -d . -f 2`
+AC_SUBST(SND_UTIL_MINOR)
+SND_UTIL_SUBMINOR=`echo $VERSION | cut -d . -f 3 | sed -e 's/pre[[0-9]]*//g'`
+AC_SUBST(SND_UTIL_SUBMINOR)
+])
diff --git a/alsaconf/Makefile.am b/alsaconf/Makefile.am
new file mode 100644
index 0000000..a4f5e4a
--- /dev/null
+++ b/alsaconf/Makefile.am
@@ -0,0 +1,37 @@
+sbin_SCRIPTS = alsaconf
+man_MANS = alsaconf.8 alsaconf.fr.8
+EXTRA_DIST = $(man_MANS)
+SUBDIRS = po
+
+install-man8:
+	@for i in $(man_MANS); do \
+	  ext=`echo $$i | $(SED) -e 's/^.*\\.//'`; \
+	  inst=`echo $$i | $(SED) -e 's/\\.[^.]*$$//'`; \
+	  case $$inst in \
+	  *.[a-za-z]*)\
+	    loc=`echo $$inst | $(SED) -e 's/^.*\\.//'`; \
+	    loc="/$$loc"; \
+	    inst=`echo $$inst | $(SED) -e 's/\\.[^.]*$$//'`;; \
+	  *)\
+	    loc="";; \
+	  esac; \
+	  $(mkinstalldirs) $(DESTDIR)$(mandir)$$loc/man$$ext; \
+	  echo " $(INSTALL_DATA) $$i $(DESTDIR)$(mandir)$$loc/man$$ext/$$inst.$$ext"; \
+	  $(INSTALL_DATA) $(srcdir)/$$i $(DESTDIR)$(mandir)$$loc/man$$ext/$$inst.$$ext; \
+	done
+
+uninstall-man8:
+	@for i in $(man_MANS); do \
+	  ext=`echo $$i | $(SED) -e 's/^.*\\.//'`; \
+	  inst=`echo $$i | $(SED) -e 's/\\.[^.]*$$//'`; \
+	  case $$inst in \
+	  *.[a-za-z]*)\
+	    loc=`echo $$inst | $(SED) -e 's/^.*\\.//'`; \
+	    loc="/$$loc"; \
+	    inst=`echo $$inst | $(SED) -e 's/\\.[^.]*$$//'`;; \
+	  *)\
+	    loc="";; \
+	  esac; \
+	  echo " rm -f $(DESTDIR)$(mandir)$$loc/man$$ext/$$inst.$$ext"; \
+	  rm -f $(DESTDIR)$(mandir)$$loc/man$$ext/$$inst.$$ext; \
+	done
diff --git a/alsaconf/alsaconf.8 b/alsaconf/alsaconf.8
new file mode 100644
index 0000000..a3e1296
--- /dev/null
+++ b/alsaconf/alsaconf.8
@@ -0,0 +1,102 @@
+.\"                                      Hey, EMACS: -*- nroff -*-
+.\" alsaconf.8 is copyright 2003 by Jordi Mallach <jordi@debian.org>
+.\" 
+.\" This is free documentation, see the latest version of the GNU
+.\" General Public License for copying conditions. There is NO warranty.
+.TH ALSACONF 8 "February 23, 2003"
+
+.SH NAME
+alsaconf \- configuration tool for the Advanced Linux Sound Architecture
+
+.SH SYNOPSIS
+.B alsaconf
+.RI [ options ]
+
+.SH DESCRIPTION
+This manual page documents briefly the
+.B alsaconf
+command.
+This manual page was written for the Debian distribution because the
+original program does not have a manual page.
+.PP
+\fBAlsaconf\fP is a simple shell script which tries to detect the sound cards
+on your system and writes a suitable configuration file for ALSA. It will try
+to guess what GNU/Linux distribution you're running, and will act accordingly
+to the standards of that distribution, if specific support is available.
+.PP
+Alsaconf will write a module-init-tools (or modutils) snippet which can be then used by module-init-tools (or modutils)
+to load the correct parameters for your sound card.
+
+.SH OPTIONS
+Alsaconf accepts the following options:
+.TP
+.B \-c, \-\-config file
+Specify the module config file.
+As default,
+.B alsaconf
+probes the available config file automatically.
+.TP
+.B \-d, \-\-devmode mode
+Set the device mode for the ALSA devices (default = 0666).
+This option is obsolete in the recent ALSA version.
+.TP
+.B \-g, \-\-gid gid
+Set the gid for the ALSA devices (default = 0).
+This option is obsolete in the recent ALSA version.
+.TP
+.B \-h, \-\-help
+Displays this help text.
+.TP
+.B \-L, \-\-log file
+Logging on the given file.  The log is appended to the file.
+This option is for debugging purpose only.
+.TP
+.B \-l, \-\-legacy
+Check only for legacy non-isapnp cards.
+.TP
+.B \-m, \-\-modinfo
+Read module descriptions instead of reading a card database.
+.TP
+.B \-P, \-\-listprobe
+List the supported legacy card modules.
+.TP
+.B \-p, \-\-probe card-name
+Probe a legacy non-isapnp card and print module options.
+.TP
+.B \-r, \-\-strict
+Set strict device mode (equiv. with \-g 17 \-d 0660).
+This option is obsolete in the recent ALSA version.
+.TP
+.B \-s, \-\-sound\-wav\-file
+Use the specified wav file as a test sound.
+.TP
+.B \-u, \-\-uid uid
+Set the uid for the ALSA devices (default = 0).
+This option is obsolete in the recent ALSA version.
+
+.SH DEBIAN SPECIFIC
+In Debian, the default gid of the device files is 29 (corresponding to the
+audio group) and the default device mode is 0660.
+
+For the ALSA base package, see also
+.I /usr/share/doc/alsa\-base/
+
+.SH SEE ALSO
+\fB
+alsamixer(1),
+amixer(1),
+aplay(1),
+arecord(1)
+\fP
+
+.SH HOMEPAGE
+http://www.alsa\-project.org/
+
+.SH AUTHOR
+The alsaconf script was written by
+Takashi Iwai <tiwai@suse.de>,
+Bernd Kaindl <bk@suse.de> and
+Jan Ondrej (SAL) <ondrejj@salstar.sk>
+
+This manual page was written by Jordi Mallach <jordi@debian.org>,
+for the Debian system (but may be used by others).
diff --git a/alsaconf/alsaconf.fr.8 b/alsaconf/alsaconf.fr.8
new file mode 100644
index 0000000..14bbea8
--- /dev/null
+++ b/alsaconf/alsaconf.fr.8
@@ -0,0 +1,111 @@
+.\"                                      Hey, EMACS: -*- nroff -*-
+.\" alsaconf.8 is copyright 2003 by Jordi Mallach <jordi@debian.org>
+.\" 
+.\" This is free documentation, see the latest version of the GNU
+.\" General Public License for copying conditions. There is NO warranty.
+.TH ALSACONF 8 "15 mars 2005"
+
+.SH NOM
+alsaconf \- outil de configuration pour ALSA (architecture de son avancée de Linux)
+.\" configuration tool for the Advanced Linux Sound Architecture
+
+.SH SYNOPSIS
+.B alsaconf
+.RI [ options ]
+
+.SH DESCRIPTION
+Cette page de manuel documente brièvement la commande 
+.B alsaconf.
+
+.PP
+\fBAlsaconf\fP 
+est un simple script shell qui essaye de détecter les cartes son sur votre
+système et écrit un fichier de configuration adéquat pour ALSA. Il va
+essayer de deviner quelle distribution GNU/Linux vous utilisez, et agira
+conformément aux standards de cette distribution, si un support spécifique
+est disponible.
+
+.PP
+Alsaconf va écrire un fragment de code modutils qui peut être ensuite
+utilisé par modutils pour charger les paramètres corrects de votre carte
+son. 
+
+.SH OPTIONS
+Alsaconf accepte les options suivantes\ :
+
+.TP
+.B \-l, \-\-legacy
+Cherche seulement les anciennes cartes non-isapnp
+
+.TP
+.B \-m, \-\-modinfo
+Lit les descriptions de modules au lieu de lire une base de données de
+carte
+
+.TP
+.B \-s, \-\-sound\-wav\-file
+Utilise le fichier wav spécifié comme son de test
+
+.TP
+.B \-u, \-\-uid uid
+Choisit l'uid pour les périphériques ALSA (valeur par défaut\ = 0)
+
+.TP
+.B \-g, \-\-gid gid
+Choisit le gid pour les périphériques ALSA (valeur par défaut\ = 0)
+
+.TP
+.B \-d, \-\-devmode mode
+Choisit le mode du périphérique pour les périphériques ALSA (valeur par
+défaut\ : 0666)
+
+.TP
+.B \-r, \-\-strict
+Choisit le mode de périphérique strict (équivaut à -g\ 17 -d\ 0660)
+
+.TP
+.B \-L, \-\-log
+Mise en journal dans /tmp/alsaconf.log
+
+.TP
+.B \-p, \-\-probe card-name
+Teste une ancienne carte non-isapnp et affiche les options du module
+
+.TP
+.B \-h, \-\-help
+Affiche ce message d'aide
+
+
+.SH POUR DEBIAN EXCLUSIVEMENT
+Sous Debian, le gid par défaut des fichiers de périphériques est 29 (qui
+correspond au groupe audio) et le mode par défaut des périphériques est
+0660.
+
+Pour le paquetage de base ALSA, voir aussi
+.I /usr/share/doc/alsa-base/
+
+.SH VOIR AUSSI
+\fB
+alsamixer(1),
+amixer(1),
+aplay(1),
+arecord(1)
+\fP
+
+.SH PAGE D'ACCUEIL
+http://www.alsa-project.org/
+
+.SH AUTEUR
+Le script alsaconf fut écrit par
+Takashi Iwai <tiwai@suse.de>,
+Bernd Kaindl <bk@suse.de> et
+Jan Ondrej (SAL) <ondrejj@salstar.sk>
+
+Cette page de manuel fut écrite par Jordi Mallach <jordi@debian.org>, pour
+le système Debian (mais elle peut être utilisée par d'autres).
+
+.SH TRADUCTION
+Baptiste Mélès 2005.
+
+N'hésitez pas à signaler à l'auteur ou au traducteur, selon le cas, toute
+erreur dans cette page de manuel.
diff --git a/usr/sbin/alsaconf b/alsaconf/alsaconf.in
old mode 100755
new mode 100644
similarity index 98%
rename from usr/sbin/alsaconf
rename to alsaconf/alsaconf.in
index aee657c..5c23787
--- a/usr/sbin/alsaconf
+++ b/alsaconf/alsaconf.in
@@ -19,12 +19,12 @@
 
 export TEXTDOMAIN=alsaconf
 
-prefix=/usr
-exec_prefix=/usr
-bindir=${exec_prefix}/bin
-sbindir=${exec_prefix}/sbin
-version=1.0.25
-USE_NLS=yes
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+bindir=@bindir@
+sbindir=@sbindir@
+version=@VERSION@
+USE_NLS=@USE_NLS@
 
 # Useful for debugging
 PROCFS="/proc"
@@ -111,7 +111,7 @@
 alsa_mode=0666
 legacy_probe_card=""
 LOGFILE=""
-TESTSOUND="/usr/share/test.wav"
+TESTSOUND="@TESTSOUND@"
 try_all_combination=0
 resources="false"
 
@@ -956,9 +956,9 @@
 	  aplay -N $TESTSOUND
       fi
     fi
-    mkdir -p -m 0755 /var/lib/alsa
-    if [ ! -r /var/lib/alsa/asound.state ]; then
-	xecho "Saving the mixer setup used for this in /var/lib/alsa/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/alsaconf/po/Makefile.in b/alsaconf/po/Makefile.in
new file mode 100644
index 0000000..f1fa9aa
--- /dev/null
+++ b/alsaconf/po/Makefile.in
@@ -0,0 +1,91 @@
+POFILES = ja.po ru.po
+
+DOMAIN = alsaconf
+GETTEXT_SRC = ../alsaconf.in
+
+XGETTEXT_OPTIONS = -kxmsg -kxecho
+
+top_builddir = ../..
+
+srcdir = @srcdir@
+datadir = @datadir@
+exec_prefix = @exec_prefix@
+prefix = @prefix@
+localedir = $(datadir)/locale
+
+USE_NLS = @USE_NLS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+# mkdir_p = @mkdir_p@
+mkdir_p = mkdir -p --
+XGETTEXT = @XGETTEXT@
+MSGFMT = @MSGFMT@
+GMSGFMT = @GMSGFMT@
+MSGMERGE = @MSGMERGE@
+MSGMERGE_UPDATE = @MSGMERGE@ --update
+
+DISTFILES = $(POFILES) Makefile.in
+CATALOGS = $(POFILES:.po=.gmo)
+
+all: all-@USE_NLS@
+
+all-yes: $(CATALOGS)
+all-no:
+
+.SUFFIXES: .po .gmo .mo
+
+%.mo : $(srcdir)/%.po
+	$(MSGFMT) -c -o t-$@ $< && mv t-$@ $@
+
+%.gmo : $(srcdir)/%.po
+	@lang=`echo $(srcdir)/$* | sed -e 's,.*/,,'`; \
+	rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics -o t-$${lang}.gmo $(srcdir)/$${lang}.po && mv t-$${lang}.gmo $${lang}.gmo
+
+$(DOMAIN).pot: $(GETTEXT_SRC)
+	$(XGETTEXT) -o$@ --default-domain=$(DOMAIN) \
+	  --add-comments=TRANSLATORS: --language=Shell \
+	  $(XGETTEXT_OPTIONS) $<
+
+check check_recursive:
+
+clean:
+	rm -f $(CATALOGS) $(POFILES:.po=.mo) $(DOMAIN).pot
+
+update-po: $(DOMAIN).pot
+	@for po in $(POFILES); do \
+	$(MSGMERGE_UPDATE) $(srcdir)/$$po $(DOMAIN).pot; \
+	done
+
+install-data-no:
+install-data-yes: all
+	$(mkdir_p) $(DESTDIR)$(datadir)
+	@catalogs='$(CATALOGS)'; \
+	for cat in $$catalogs; do \
+	  lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+	  dir=$(localedir)/$$lang/LC_MESSAGES; \
+	  $(mkdir_p) $(DESTDIR)$$dir; \
+	  $(INSTALL_DATA) $$cat $(DESTDIR)$$dir/$(DOMAIN).mo; \
+	done
+
+install-data: install-data-@USE_NLS@
+install: install-data
+
+uninstall-data-no:
+uninstall-data-yes:
+	@catalogs='$(CATALOGS)'; \
+	for cat in $$catalogs; do \
+	  cat=`basename $$cat`; \
+	  lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+	  rm -f $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo; \
+	done
+
+uninstall-data: uninstall-data-@USE_NLS@
+uninstall: uninstall-data
+
+dist distdir: $(DISTFILES)
+	@for file in $(DISTFILES); do \
+	  cp -p $$file $(distdir); \
+	done
+
+distclean: clean
+	rm -f Makefile *~
diff --git a/alsaconf/po/ja.po b/alsaconf/po/ja.po
new file mode 100644
index 0000000..170f708
--- /dev/null
+++ b/alsaconf/po/ja.po
@@ -0,0 +1,362 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: alsaconf\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2005-12-02 12:37+0100\n"
+"PO-Revision-Date: 2005-12-02 12:39+0100\n"
+"Last-Translator: Takashi Iwai <tiwai@suse.de>\n"
+"Language-Team: Japanese <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../alsaconf.in:36
+msgid "-s"
+msgstr ""
+
+#: ../alsaconf.in:80
+msgid "ALSA configurator"
+msgstr "ALSA 設定ツール"
+
+#: ../alsaconf.in:82
+msgid ""
+"usage: alsaconf [options]\n"
+"  -l|--legacy    check only legacy non-isapnp cards\n"
+"  -m|--modinfo   read module descriptions instead of reading card db\n"
+"  -s|--sound wav-file\n"
+"                 use the specified wav file as a test sound\n"
+"  -u|--uid uid   set the uid for the ALSA devices (default = 0) [obsoleted]\n"
+"  -g|--gid gid   set the gid for the ALSA devices (default = 0) [obsoleted]\n"
+"  -d|--devmode mode\n"
+"                 set the permission for ALSA devices (default = 0666) "
+"[obs.]\n"
+"  -r|--strict    set strict device mode (equiv. with -g 17 -d 0660) "
+"[obsoleted]\n"
+"  -L|--log file  logging on the specified file (for debugging purpose only)\n"
+"  -p|--probe card-name\n"
+"                 probe a legacy non-isapnp card and print module options\n"
+"  -P|--listprobe list the supported legacy card modules\n"
+"  -c|--config file\n"
+"                 specify the module config file\n"
+"  -R|--resources list available DMA and IRQ resources with debug for legacy\n"
+"  -h|--help      what you're reading"
+msgstr ""
+"使用法: alsaconf [オプション]\n"
+"  -l|--legacy    非 ISAPnP カードのみチェックする\n"
+"  -m|--modinfo   カード DB を読む代わりにモジュール記述を読み込み\n"
+"  -s|--sound WAVファイル\n"
+"                 指定の WAV ファイルをテスト音として用いる\n"
+"  -u|--uid uid   ALSA デバイスの uid (デフォルト = 0) [旧オプション・無視されます]\n"
+"  -g|--gid gid   ALSA デバイスの gid (デフォルト = 0) [旧]\\n\"\n"
+"  -d|--devmode モード\n"
+"                 ALSA デバイスのパーミッション(デフォルト = 0666) [旧]\n"
+"  -r|--strict    厳格なモード (-g 17 -d 0660 と同等) [旧]\n"
+"  -L|--log file  指定のファイルにログを記録する (デバッグ用途)\n"
+"  -p|--probe カード名\n"
+"                 指定の 非IsaPnP カードを検索しモジュールオプションを表示する\n"
+"  -P|--listprobe サポートされている旧式の(非isapnp)カードのモジュール一覧表示\n"
+"  -c|--config ファイル\n"
+"                 指定のモジュール設定ファイルを用いる\n"
+"  -R|--resources レガシーデバイスのデバッグ用: 可能な DMA と IRQ を表示する\n"
+"  -h|--help      今見ています"
+
+#: ../alsaconf.in:252
+msgid "You must be root to use this script."
+msgstr "ルートのみ実行可能です"
+
+#: ../alsaconf.in:294
+msgid "ERROR: The config file doesn't exist: "
+msgstr "エラー: 設定ファイルが存在しません: "
+
+#: ../alsaconf.in:336
+msgid "Error, dialog or whiptail not found."
+msgstr "エラー: dialog または whiptail が見つかりません"
+
+#: ../alsaconf.in:342
+msgid "Error, awk not found. Can't continue."
+msgstr "エラー: awk が見つかりません"
+
+#: ../alsaconf.in:439
+msgid ""
+"\n"
+"                   ALSA  CONFIGURATOR\n"
+"                   version %s\n"
+"\n"
+"            This script is a configurator for\n"
+"    Advanced Linux Sound Architecture (ALSA) driver.\n"
+"\n"
+"\n"
+"  If ALSA is already running, you should close all sound\n"
+"  apps now and stop the sound driver.\n"
+"  alsaconf will try to do this, but it's not 100%% sure."
+msgstr ""
+"\n"
+"                   ALSA 設定ツール\n"
+"                  バージョン %s\n"
+"\n"
+"     このスクリプトは、Advanced Linux Sound Architecture\n"
+"    (ALSA) ドライバの設定を行うツールです。\n"
+"\n"
+"\n"
+"   もし既に ALSA が起動している場合は、実行前に\n"
+"   全てのサウンドアプリケーションを閉じて、サウンド\n"
+"   ドライバを停止してください。\n"
+"   alsaconf の動作は 100%% 保証できません。"
+
+#: ../alsaconf.in:455
+msgid ""
+"\n"
+"\n"
+"     OK, sound driver is configured.\n"
+"\n"
+"                  ALSA  CONFIGURATOR\n"
+"\n"
+"          will prepare the card for playing now.\n"
+"\n"
+"     Now I'll run alsasound init script, then I'll use\n"
+"     amixer to raise the default volumes.\n"
+"     You can change the volume later via a mixer\n"
+"     program such as alsamixer or gamix.\n"
+"  \n"
+"  "
+msgstr ""
+"\n"
+"\n"
+"    サウンドドライバが設定されました。\n"
+"\n"
+"    これからカードの再生の準備を行います。\n"
+"\n"
+"\n"
+"    alsasound スクリプトを実行し、その後 amixer で\n"
+"    デフォルトの音量に設定します。\n"
+"    後で、alsamixer や gamix などのミキサープログラム\n"
+"    を用いて、音量を変更することが可能です。\n"
+"  "
+
+#: ../alsaconf.in:518 ../alsaconf.in:523 ../alsaconf.in:528 ../alsaconf.in:533
+msgid "Can't create temp file, exiting..."
+msgstr "一時ファイルを作成できませんでした。中止します..."
+
+#: ../alsaconf.in:643
+msgid "Building card database.."
+msgstr "カードデータベースを作成中..."
+
+#: ../alsaconf.in:647
+msgid "No card database is found.."
+msgstr "カードが見つかりませんでした..."
+
+#: ../alsaconf.in:652
+msgid "Searching sound cards"
+msgstr "サウンドカードを検索中"
+
+#: ../alsaconf.in:806
+msgid ""
+"\n"
+"Configuring %s\n"
+"Do you want to modify %s (and %s if present)?"
+msgstr ""
+"\n"
+"%s を設定します\n"
+"%s (および %s) を変更しますか?"
+
+#: ../alsaconf.in:811
+msgid ""
+"\n"
+"Configuring %s\n"
+"Do you want to modify %s?"
+msgstr ""
+"\n"
+"%s を設定します\n"
+"%s を変更しますか?"
+
+#: ../alsaconf.in:900
+msgid "Running modules-update..."
+msgstr "modules-update を実行中..."
+
+#: ../alsaconf.in:903
+msgid "Running update-modules..."
+msgstr "update-modules を実行中..."
+
+#: ../alsaconf.in:915
+msgid ""
+"\n"
+"       The mixer is set up now for for playing.\n"
+"       Shall I try to play a sound sample now?\n"
+"\n"
+"                           NOTE:\n"
+"If you have a big amplifier, lower your volumes or say no.\n"
+"    Otherwise check that your speaker volume is open,\n"
+"          and look if you can hear test sound.\n"
+msgstr ""
+"\n"
+"       音量を再生用に準備し終えました。\n"
+"       サンプルのサウンドを再生しますか?\n"
+"\n"
+"  注意:\n"
+"  大容量アンプの場合は、音量を下げておくか、ここで「いいえ」\n"
+"  を選択してください。スピーカーの音量をチェックして、\n"
+"  テスト音が聞こえるかどうか確認してください。\n"
+
+#: ../alsaconf.in:932
+msgid "Saving the mixer setup used for this in /etc/asound.state."
+msgstr "ミキサーの設定を /etc/asound.state に保存します"
+
+#: ../alsaconf.in:936
+msgid ""
+"\n"
+"===============================================================================\n"
+"\n"
+" Now ALSA is ready to use.\n"
+" For adjustment of volumes, use your favorite mixer.\n"
+"\n"
+" Have a lot of fun!\n"
+"\n"
+msgstr ""
+"\n"
+"===============================================================================\n"
+"\n"
+" これで ALSA が使用可能になりました。\n"
+" 音量の調整には、ミキサープログラムをご使用下さい。\n"
+"\n"
+
+#: ../alsaconf.in:1244
+msgid "WARNING"
+msgstr "注意"
+
+#: ../alsaconf.in:1245
+msgid ""
+"\n"
+"   Probing legacy ISA cards might make\n"
+"   your system unstable.\n"
+"\n"
+"        Do you want to proceed?\n"
+"\n"
+msgstr ""
+"\n"
+"  旧式の ISA カードの検索を行うと\n"
+"  システムが不安定になる場合があります。\n"
+"\n"
+"  このまま続けますか?\n"
+"\n"
+
+#: ../alsaconf.in:1268
+msgid ""
+"No legacy drivers are available\n"
+"   for your machine"
+msgstr ""
+"旧式の ISA カードは\n"
+"検出されませんでした"
+
+#: ../alsaconf.in:1273
+msgid "Driver Selection"
+msgstr "ドライバの選択"
+
+#: ../alsaconf.in:1274
+msgid ""
+"           Probing legacy ISA cards\n"
+"\n"
+"        Please select the drivers to probe:"
+msgstr ""
+"    旧式の ISA カードの検出\n"
+"\n"
+"    検出するドライバを選択してください:"
+
+#: ../alsaconf.in:1281
+msgid ""
+"\n"
+" Shall I try all possible DMA and IRQ combinations?\n"
+" With this option, some unconventional configuration\n"
+" might be found, but it will take much longer time."
+msgstr ""
+"\n"
+" 可能な DMA と IRQ の全ての組合せを試みますか?\n"
+" このオプションでは、まれな設定も検出されることが\n"
+" 可能ですが、より長い時間がかかります。"
+
+#: ../alsaconf.in:1291
+msgid "Probing legacy cards..   This may take a few minutes.."
+msgstr "旧式カードの検出中...  長時間がかかることがあります..."
+
+#: ../alsaconf.in:1292
+msgid "Probing: "
+msgstr "検出中: "
+
+#: ../alsaconf.in:1298
+msgid " : FOUND!!"
+msgstr " : 発見!!"
+
+#: ../alsaconf.in:1304
+msgid "Result"
+msgstr "結果"
+
+#: ../alsaconf.in:1305
+msgid "No legacy cards found"
+msgstr "旧式カードが見つかりませんでした"
+
+#: ../alsaconf.in:1364
+msgid ""
+"\n"
+" Looks like you having a Thinkpad 600E or 770 notebook.\n"
+" On this notebook, CS4236 driver should be used\n"
+" although CS46xx chip is detected.\n"
+"\n"
+" Shall I try to snd-cs4236 driver and probe\n"
+" the legacy ISA configuration?"
+msgstr ""
+"\n"
+" Thinkpad 600E または 770 をご使用のようです。\n"
+" このマシンでは、CS46xx チップが検出されますが\n"
+" CS4236 ドライバを用いる必要があります。\n"
+"\n"
+" snd-cs4236 ドライバを試用して、旧式の ISA 設定\n"
+" の検出を行いますか?"
+
+#: ../alsaconf.in:1378
+msgid ""
+"\n"
+" Looks like you having a Dell Dimension machine.\n"
+" On this machine, CS4232 driver should be used\n"
+" although CS46xx chip is detected.\n"
+"\n"
+" Shall I try to snd-cs4232 driver and probe\n"
+" the legacy ISA configuration?"
+msgstr ""
+"\n"
+" Dell Dimension マシンをご使用のようです。\n"
+" このマシンでは、CS46xx チップが検出されますが\n"
+" CS4232 ドライバを用いる必要があります。\n"
+"\n"
+" snd-cs4232 ドライバを試用して、旧式の ISA 設定\n"
+" の検出を行いますか?"
+
+#: ../alsaconf.in:1395
+msgid "Soundcard Selection"
+msgstr "サウンドカードの選択"
+
+#: ../alsaconf.in:1396
+msgid ""
+"\n"
+"         Following card(s) are found on your system.\n"
+"         Choose a soundcard to configure:\n"
+msgstr ""
+"\n"
+"        以下のカードがシステムから検出されました。\n"
+"        設定するカードを選択してください:\n"
+
+#: ../alsaconf.in:1409
+msgid ""
+"\n"
+"        No supported PnP or PCI card found.\n"
+"\n"
+" Would you like to probe legacy ISA sound cards/chips?\n"
+"\n"
+msgstr ""
+"\n"
+"   PnP または PCI カードが検出されませんでした。\n"
+"\n"
+" 旧式の ISA サウンドカードの検出を行いますか?\n"
diff --git a/alsaconf/po/ru.po b/alsaconf/po/ru.po
new file mode 100644
index 0000000..af4fa0d
--- /dev/null
+++ b/alsaconf/po/ru.po
@@ -0,0 +1,373 @@
+# Russian translations for alsa-utils package
+# Английские переводы для пакета alsa-utils.
+# Copyright (C) 2005 THE alsa-utils'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the alsa-utils package.
+#  <dushistov@mail.ru>, 2005.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: alsa-utils 1.0.9rc4a\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2005-12-02 12:37+0100\n"
+"PO-Revision-Date: 2005-12-02 12:39+0100\n"
+"Last-Translator:  <dushistov@mail.ru>\n"
+"Language-Team: Russian <ru@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%"
+"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: ../alsaconf.in:36
+msgid "-s"
+msgstr ""
+
+#: ../alsaconf.in:80
+msgid "ALSA configurator"
+msgstr "Найстройщик ALSA"
+
+#: ../alsaconf.in:82
+#, fuzzy
+msgid ""
+"usage: alsaconf [options]\n"
+"  -l|--legacy    check only legacy non-isapnp cards\n"
+"  -m|--modinfo   read module descriptions instead of reading card db\n"
+"  -s|--sound wav-file\n"
+"                 use the specified wav file as a test sound\n"
+"  -u|--uid uid   set the uid for the ALSA devices (default = 0) [obsoleted]\n"
+"  -g|--gid gid   set the gid for the ALSA devices (default = 0) [obsoleted]\n"
+"  -d|--devmode mode\n"
+"                 set the permission for ALSA devices (default = 0666) "
+"[obs.]\n"
+"  -r|--strict    set strict device mode (equiv. with -g 17 -d 0660) "
+"[obsoleted]\n"
+"  -L|--log file  logging on the specified file (for debugging purpose only)\n"
+"  -p|--probe card-name\n"
+"                 probe a legacy non-isapnp card and print module options\n"
+"  -P|--listprobe list the supported legacy card modules\n"
+"  -c|--config file\n"
+"                 specify the module config file\n"
+"  -R|--resources list available DMA and IRQ resources with debug for legacy\n"
+"  -h|--help      what you're reading"
+msgstr ""
+"использование: alsaconf [опции]\n"
+"  -l|--legacy    проверять только не ISAPnP карты\n"
+"  -m|--modinfo   читать описание модулей вместо чтения бызы данных карт\n"
+"  -s|--sound WAV файл\n"
+"                 использовать указанный WAV файл в качестве тестового\n"
+"  -u|--uid uid   устанавливает uid для ALSA(по умолчанию = 0) [устарело]\n"
+"  -g|--gid gid   устанавливает gid для ALSA(по умолчанию = 0) [устарело]\n"
+"  -d|--devmode режим\n"
+"                 устанавливает права доступа для ALSA (по умолчанию = 0666) "
+"[уст.]\n"
+"  -r|--strict    устанавливает строгие права для ALSA (экв. -g 17 -d 0660) "
+"[уст.]\n"
+"  -L|--log файл  записывать события в определенный файл (в целях отладки)\n"
+"  -p|--probe имя звуковой карты\n"
+"                 проверить обычную не IsaPnP карту и вывести опции модуля\n"
+"  -P|--listprobe вывести список поддерживаемых не IsaPnP карт\n"
+"  -c|--config файл\n"
+"                 указывает файл с настройками модуля\n"
+"  -h|--help      вывести это сообщение"
+
+#: ../alsaconf.in:252
+msgid "You must be root to use this script."
+msgstr "Вы должны быть суперпользователем, чтобы использовать этот скрипт."
+
+#: ../alsaconf.in:294
+msgid "ERROR: The config file doesn't exist: "
+msgstr "ОШИБКА: Файл с настройками не существует: "
+
+#: ../alsaconf.in:336
+msgid "Error, dialog or whiptail not found."
+msgstr "Ошибка, dialog или whiptail не найдены."
+
+#: ../alsaconf.in:342
+msgid "Error, awk not found. Can't continue."
+msgstr "Ошибка, awk не найден. Не могу продолжить работу."
+
+#: ../alsaconf.in:439
+msgid ""
+"\n"
+"                   ALSA  CONFIGURATOR\n"
+"                   version %s\n"
+"\n"
+"            This script is a configurator for\n"
+"    Advanced Linux Sound Architecture (ALSA) driver.\n"
+"\n"
+"\n"
+"  If ALSA is already running, you should close all sound\n"
+"  apps now and stop the sound driver.\n"
+"  alsaconf will try to do this, but it's not 100%% sure."
+msgstr ""
+"\n"
+"                   Настройщик ALSA\n"
+"                  версия %s\n"
+"\n"
+"    Этот скрипт настраивает Advanced Linux Sound Architecture\n"
+"\n"
+"\n"
+"   Если какие-нибудь приложения входящие в пакет ALSA уже запущены,\n"
+"   вы должны закрыть все приложения работающие со звуком\n"
+"   и остановаить звуковой драйвер.\n"
+"   alsaconf попытается это сделать, но результат не гарантируется на все 100%"
+"%."
+
+#: ../alsaconf.in:455
+msgid ""
+"\n"
+"\n"
+"     OK, sound driver is configured.\n"
+"\n"
+"                  ALSA  CONFIGURATOR\n"
+"\n"
+"          will prepare the card for playing now.\n"
+"\n"
+"     Now I'll run alsasound init script, then I'll use\n"
+"     amixer to raise the default volumes.\n"
+"     You can change the volume later via a mixer\n"
+"     program such as alsamixer or gamix.\n"
+"  \n"
+"  "
+msgstr ""
+"\n"
+"\n"
+"    Хорошо, драйвер звуковой карты настроен.\n"
+"\n"
+"            Сейчас Настройщик ALSA\n"
+"\n"
+"         подготовит звуковую карту для проигрования.\n"
+"\n"
+"    Сейчас я запущу скрипт, инициализирующий alsasound, потом использую "
+"amixer\n"
+"    для увеличения уровня громкости по умолчания.\n"
+"    Вы можете изменить уровень громкости по умолчания позже\n"
+"    с помощью таких программ как alsamixer или gamix.\n"
+"  "
+
+#: ../alsaconf.in:518 ../alsaconf.in:523 ../alsaconf.in:528 ../alsaconf.in:533
+msgid "Can't create temp file, exiting..."
+msgstr "Не могу создать временный файл, прекращаю работу..."
+
+#: ../alsaconf.in:643
+msgid "Building card database.."
+msgstr "Создаю базу данных с информацией о звуковых карточках.."
+
+#: ../alsaconf.in:647
+msgid "No card database is found.."
+msgstr "База данных, содержащая информацию о звуковых карточках, не найдена.."
+
+#: ../alsaconf.in:652
+msgid "Searching sound cards"
+msgstr "Ищем звуковые карточки"
+
+#: ../alsaconf.in:806
+msgid ""
+"\n"
+"Configuring %s\n"
+"Do you want to modify %s (and %s if present)?"
+msgstr ""
+"\n"
+"Настраиваем %s\n"
+"Вы хотите изменить %s (и %s, если существует)?"
+
+#: ../alsaconf.in:811
+msgid ""
+"\n"
+"Configuring %s\n"
+"Do you want to modify %s?"
+msgstr ""
+"\n"
+"Настраиваем %s\n"
+"Вы хотите изменить %s?"
+
+#: ../alsaconf.in:900
+msgid "Running modules-update..."
+msgstr "Запускаем modules-update..."
+
+#: ../alsaconf.in:903
+msgid "Running update-modules..."
+msgstr "Запускаем update-modules..."
+
+#: ../alsaconf.in:915
+msgid ""
+"\n"
+"       The mixer is set up now for for playing.\n"
+"       Shall I try to play a sound sample now?\n"
+"\n"
+"                           NOTE:\n"
+"If you have a big amplifier, lower your volumes or say no.\n"
+"    Otherwise check that your speaker volume is open,\n"
+"          and look if you can hear test sound.\n"
+msgstr ""
+"\n"
+"       Микшер настроен для проигровования.\n"
+"       Проиграть тестовый пример?\n"
+"\n"
+"  Примечание:\n"
+"  Если у вас большой усилитель, уменьшите уровень громкости или скажите "
+"нет.\n"
+"  В противном случае проверьте, что ваши колонки включены,\n"
+"  и удостоверьтесь что вы слышите тестовый пример.\n"
+
+#: ../alsaconf.in:932
+msgid "Saving the mixer setup used for this in /etc/asound.state."
+msgstr "Сохраняем настройки микшера в /etc/asound.state."
+
+#: ../alsaconf.in:936
+msgid ""
+"\n"
+"===============================================================================\n"
+"\n"
+" Now ALSA is ready to use.\n"
+" For adjustment of volumes, use your favorite mixer.\n"
+"\n"
+" Have a lot of fun!\n"
+"\n"
+msgstr ""
+"\n"
+"===============================================================================\n"
+"\n"
+" Теперь ALSA готова к использованию.\n"
+" Для регулировки уровня громкости, используйте ваш любимый микшер.\n"
+"\n"
+" Оторвись по полной!\n"
+"\n"
+
+#: ../alsaconf.in:1244
+msgid "WARNING"
+msgstr "ПРЕДУПРЕЖДЕНИЕ"
+
+#: ../alsaconf.in:1245
+msgid ""
+"\n"
+"   Probing legacy ISA cards might make\n"
+"   your system unstable.\n"
+"\n"
+"        Do you want to proceed?\n"
+"\n"
+msgstr ""
+"\n"
+"  Поиск ISA звуковых карточек может сделать\n"
+"  вашу систему нестабильной.\n"
+"\n"
+"  Вы уверены?\n"
+"\n"
+
+#: ../alsaconf.in:1268
+msgid ""
+"No legacy drivers are available\n"
+"   for your machine"
+msgstr ""
+"Нету подходяшего драйвера для\n"
+"  вашей машины"
+
+#: ../alsaconf.in:1273
+msgid "Driver Selection"
+msgstr "Выбор Драйвера"
+
+#: ../alsaconf.in:1274
+msgid ""
+"           Probing legacy ISA cards\n"
+"\n"
+"        Please select the drivers to probe:"
+msgstr ""
+"    Поиск ISA звуковых карт\n"
+"\n"
+"    Пожалуйста, выберите драйверы для проверки:"
+
+#: ../alsaconf.in:1281
+msgid ""
+"\n"
+" Shall I try all possible DMA and IRQ combinations?\n"
+" With this option, some unconventional configuration\n"
+" might be found, but it will take much longer time."
+msgstr ""
+"\n"
+" Попробовать все доступные DMA и IRQ комбинации?\n"
+" С этой опцией, могут быть найдены некоторые нестандартные\n"
+" конфигураии, но это займет намного больше времени."
+
+#: ../alsaconf.in:1291
+msgid "Probing legacy cards..   This may take a few minutes.."
+msgstr "Поиск звуковых карточек..  Это может занять несколько минут.."
+
+#: ../alsaconf.in:1292
+msgid "Probing: "
+msgstr "Проверка: "
+
+#: ../alsaconf.in:1298
+msgid " : FOUND!!"
+msgstr " : НАШЕЛ!!"
+
+#: ../alsaconf.in:1304
+msgid "Result"
+msgstr "Результат"
+
+#: ../alsaconf.in:1305
+msgid "No legacy cards found"
+msgstr "Звуковых карточек не найдено"
+
+#: ../alsaconf.in:1364
+msgid ""
+"\n"
+" Looks like you having a Thinkpad 600E or 770 notebook.\n"
+" On this notebook, CS4236 driver should be used\n"
+" although CS46xx chip is detected.\n"
+"\n"
+" Shall I try to snd-cs4236 driver and probe\n"
+" the legacy ISA configuration?"
+msgstr ""
+"\n"
+" Кажется, мы имеем дело с ноутбуком Thinkpad 600E или 770.\n"
+" На этом ноутбуке, драйвер CS4236 должен использоваться\n"
+" хотя определяется CS46xx\n"
+"\n"
+" Использовать snd-cs4236 и проверить правильность ISA\n"
+" конфигурации?"
+
+#: ../alsaconf.in:1378
+msgid ""
+"\n"
+" Looks like you having a Dell Dimension machine.\n"
+" On this machine, CS4232 driver should be used\n"
+" although CS46xx chip is detected.\n"
+"\n"
+" Shall I try to snd-cs4232 driver and probe\n"
+" the legacy ISA configuration?"
+msgstr ""
+"\n"
+" Похоже что вы имеем дело с машиной Dell Dimension.\n"
+" На этой машине CS4232 драйвер должен исползьовать, チップが検出されますが\n"
+" хотя чип звуковой карты определяется как CS46xx.\n"
+"\n"
+" Использовать snd-cs4232 и проверить ISA валидность\n"
+" этой конфигурации?"
+
+#: ../alsaconf.in:1395
+msgid "Soundcard Selection"
+msgstr "Выбор Звуковой карты"
+
+#: ../alsaconf.in:1396
+msgid ""
+"\n"
+"         Following card(s) are found on your system.\n"
+"         Choose a soundcard to configure:\n"
+msgstr ""
+"\n"
+"        Следущее звуковая(ые) карта(ы) найдены в вашей системе.\n"
+"        Выберите звуковую карту для настройки:\n"
+
+#: ../alsaconf.in:1409
+msgid ""
+"\n"
+"        No supported PnP or PCI card found.\n"
+"\n"
+" Would you like to probe legacy ISA sound cards/chips?\n"
+"\n"
+msgstr ""
+"\n"
+"   Найдена не поддерживаемая PnP или PCI карта.\n"
+"\n"
+" Желаете проверить ISA звуковые карты/чипы?\n"
+"\n"
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
new file mode 100644
index 0000000..c434e44
--- /dev/null
+++ b/alsactl/Makefile.am
@@ -0,0 +1,59 @@
+SUBDIRS = init
+
+sbin_PROGRAMS=alsactl
+man_MANS=alsactl.1
+if USE_XMLTO
+man_MANS += alsactl_init.7
+endif
+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) -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
new file mode 100644
index 0000000..eb5968c
--- /dev/null
+++ b/alsactl/alsactl.1
@@ -0,0 +1,118 @@
+.TH ALSACTL 1 "15 May 2001"
+.SH NAME
+alsactl \- advanced controls for ALSA soundcard driver
+
+.SH SYNOPSIS
+
+\fBalsactl\fP [\fIoptions\fP] [\fIstore\fP|\fIrestore\fP|\fIinit\fP] <card # or id or device>
+
+.SH DESCRIPTION
+\fBalsactl\fP is used to control advanced settings for the ALSA
+soundcard drivers. It supports multiple soundcards. If your card has
+features that you can't seem to control from a mixer application,
+you have come to the right place.
+
+.SH COMMANDS
+
+\fIstore\fP saves the current driver state for the selected soundcard
+to the configuration file.
+
+\fIrestore\fP loads driver state for the selected soundcard from the
+configuration file. If restoring fails (eventually partly), the init
+action is called.
+
+\fIinit\fP tries to initialize all devices to a default state. If device
+is not known, error code 99 is returned.
+
+If no soundcards are specified, setup for all cards will be saved or
+loaded.
+
+.SH OPTIONS
+
+.TP
+\fI\-h, \-\-help\fP 
+Help: show available flags and commands.
+
+.TP
+\fI\-d, \-\-debug\fP
+Use debug mode: a bit more verbose.
+
+.TP
+\fI\-v, \-\-version\fP
+Print alsactl version number.
+
+.TP
+\fI\-f, \-\-file\fP
+Select the configuration file to use. The default is /var/lib/alsa/asound.state.
+
+.TP
+\fI\-F, \-\-force\fP
+Used with restore command.  Try to restore the matching control elements
+as much as possible.  This option is set as default now.
+
+.TP
+\fI\-g, \-\-ignore\fP
+Used with store and restore commands. Do not show 'No soundcards found'
+and do not set an error exit code when soundcards are not installed.
+
+.TP
+\fI\-P, \-\-pedantic\fP
+Used with restore command.  Don't restore mismatching control elements.
+This option was the old default behavior.
+
+.TP
+\fI\-I, \-\-no\-init\-fallback\fP
+Don't initialize cards if restore fails.  Since version 1.0.18,
+\fBalsactl\fP tries to initialize the card with the restore operation
+as default.  But this can cause incompatibility with the older version.
+The caller may expect that the state won't be touched if no state file
+exists.  This option takes the restore behavior back to the older
+version by suppressing the initialization.
+
+.TP
+\fI\-r, \-\-runstate\fP
+Save restore and init state to this file. The file will contain only errors.
+Errors are appended with the soundcard id to the end of file.
+
+.TP
+\fI\-R, \-\-remove\fP
+Remove runstate file at first.
+
+.TP
+\fI\-E, \-\-env\fP #=#
+Set environment variable (useful for init action or you may override
+ALSA_CONFIG_PATH to read different or optimized configuration - may be
+useful for "boot" scripts).
+
+.TP
+\fI\-i, \-\-initfile\fP #=#
+The configuration file for init. By default, PREFIX/share/alsa/init/00main
+is used.
+
+.SH FILES
+\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
+capable of controlling other card-specific features that mixer apps
+usually don't know about.
+
+The configuration file is generated automatically by running
+\fBalsactl store\fP. Editing the configuration file by hand may be
+necessary for some soundcard features (e.g. enabling/disabling
+automatic mic gain, digital output, joystick/game ports, some future MIDI
+routing options, etc).
+
+.SH SEE ALSO
+\fB
+amixer(1),
+alsamixer(1),
+aplay(1)
+\fP
+
+.SH BUGS 
+None known.
+
+.SH AUTHOR
+\fBalsactl\fP is by Jaroslav Kysela <perex@perex.cz> and Abramo Bagnara
+<abramo@alsa\-project.org>. This document is by Paul Winkler <zarmzarm@erols.com>.
diff --git a/alsactl/alsactl.c b/alsactl/alsactl.c
new file mode 100644
index 0000000..1792747
--- /dev/null
+++ b/alsactl/alsactl.c
@@ -0,0 +1,193 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *                   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"
+#include "version.h"
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <alsa/asoundlib.h>
+#include "alsactl.h"
+
+#ifndef SYS_ASOUNDRC
+#define SYS_ASOUNDRC "/var/lib/alsa/asound.state"
+#endif
+
+int debugflag = 0;
+int force_restore = 1;
+int ignore_nocards = 0;
+char *command;
+char *statefile = NULL;
+
+static void help(void)
+{
+	printf("Usage: alsactl <options> command\n");
+	printf("\nAvailable global options:\n");
+	printf("  -h,--help        this help\n");
+	printf("  -d,--debug       debug mode\n");
+	printf("  -v,--version     print version of this program\n");
+	printf("\nAvailable state options:\n");
+	printf("  -f,--file #      configuration file (default " SYS_ASOUNDRC ")\n");
+	printf("  -F,--force       try to restore the matching controls as much as possible\n");
+	printf("                   (default mode)\n");
+	printf("  -g,--ignore      ignore 'No soundcards found' error\n");
+	printf("  -P,--pedantic    do not restore mismatching controls (old default)\n");
+	printf("  -I,--no-init-fallback\n"
+	       "                   don't initialize even if restore fails\n");
+	printf("  -r,--runstate #  save restore and init state to this file (only errors)\n");
+	printf("                   default settings is 'no file set'\n");
+	printf("  -R,--remove      remove runstate file at first, otherwise append errors\n");
+	printf("\nAvailable init options:\n");
+	printf("  -E,--env #=#	   set environment variable for init phase (NAME=VALUE)\n");
+	printf("  -i,--initfile #  main configuation file for init phase (default " DATADIR "/init/00main)\n");
+	printf("\n");
+	printf("\nAvailable commands:\n");
+	printf("  store   <card #> save current driver setup for one or each soundcards\n");
+	printf("                   to configuration file\n");
+	printf("  restore <card #> load current driver setup for one or each soundcards\n");
+	printf("                   from configuration file\n");
+	printf("  init	  <card #> initialize driver to a default state\n");
+}
+
+int main(int argc, char *argv[])
+{
+	static const struct option long_option[] =
+	{
+		{"help", 0, NULL, 'h'},
+		{"file", 1, NULL, 'f'},
+		{"env", 1, NULL, 'E'},
+		{"initfile", 1, NULL, 'i'},
+		{"no-init-fallback", 0, NULL, 'I'},
+		{"force", 0, NULL, 'F'},
+		{"ignore", 0, NULL, 'g'},
+		{"pedantic", 0, NULL, 'P'},
+		{"runstate", 0, NULL, 'r'},
+		{"remove", 0, NULL, 'R'},
+		{"debug", 0, NULL, 'd'},
+		{"version", 0, NULL, 'v'},
+		{NULL, 0, NULL, 0},
+	};
+	static const char *const devfiles[] = {
+		"/dev/snd/controlC",
+		"/dev/snd/pcmC",
+		"/dev/snd/midiC",
+		"/dev/snd/hwC",
+		NULL
+	};
+	char *cfgfile = SYS_ASOUNDRC;
+	char *initfile = DATADIR "/init/00main";
+	char *cardname, ncardname[16];
+	const char *const *tmp;
+	int removestate = 0;
+	int init_fallback = 1; /* new default behavior */
+	int res;
+
+	command = argv[0];
+	while (1) {
+		int c;
+
+		if ((c = getopt_long(argc, argv, "hdvf:FgE:i:IPr:R", long_option, NULL)) < 0)
+			break;
+		switch (c) {
+		case 'h':
+			help();
+			return EXIT_SUCCESS;
+		case 'f':
+			cfgfile = optarg;
+			break;
+		case 'F':
+			force_restore = 1;
+			break;
+		case 'g':
+			ignore_nocards = 1;
+			break;
+		case 'E':
+			if (putenv(optarg)) {
+				fprintf(stderr, "environment string '%s' is wrong\n", optarg);
+				return EXIT_FAILURE;
+			}
+			break;
+		case 'i':
+			initfile = optarg;
+			break;
+		case 'I':
+			init_fallback = 0;
+			break;
+		case 'r':
+			statefile = optarg;
+			break;
+		case 'R':
+			removestate = 1;
+			break;
+		case 'P':
+			force_restore = 0;
+			break;
+		case 'd':
+			debugflag = 1;
+			break;
+		case 'v':
+			printf("alsactl version " SND_UTIL_VERSION_STR "\n");
+			return EXIT_SUCCESS;
+		case '?':		// error msg already printed
+			help();
+			return EXIT_FAILURE;
+			break;
+		default:		// should never happen
+			fprintf(stderr, 
+			"Invalid option '%c' (%d) not handled??\n", c, c);
+		}
+	}
+	if (argc - optind <= 0) {
+		fprintf(stderr, "alsactl: Specify command...\n");
+		return 0;
+	}
+
+	cardname = argc - optind > 1 ? argv[optind + 1] : NULL;
+	for (tmp = devfiles; cardname != NULL && *tmp != NULL; tmp++) {
+		int len = strlen(*tmp);
+		if (!strncmp(cardname, *tmp, len)) {
+			long l = strtol(cardname + len, NULL, 0);
+			sprintf(ncardname, "%li", l);
+			cardname = ncardname;
+			break;
+		}
+	}
+
+	if (!strcmp(argv[optind], "init")) {
+		res = init(initfile, cardname);
+	} else if (!strcmp(argv[optind], "store")) {
+		res = save_state(cfgfile, cardname);
+	} else if (!strcmp(argv[optind], "restore")) {
+		if (removestate)
+			remove(statefile);
+		res = load_state(cfgfile, initfile, cardname, init_fallback);
+	} else {
+		fprintf(stderr, "alsactl: Unknown command '%s'...\n", 
+			argv[optind]);
+		res = -ENODEV;
+	}
+
+	snd_config_update_free_global();
+	return res < 0 ? -res : 0;
+}
diff --git a/alsactl/alsactl.h b/alsactl/alsactl.h
new file mode 100644
index 0000000..be90efb
--- /dev/null
+++ b/alsactl/alsactl.h
@@ -0,0 +1,94 @@
+extern int debugflag;
+extern int force_restore;
+extern int ignore_nocards;
+extern char *command;
+extern char *statefile;
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define info(...) do {\
+	fprintf(stdout, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+	fprintf(stdout, __VA_ARGS__); \
+	putc('\n', stdout); \
+} while (0)
+#else
+#define info(args...) do {\
+	fprintf(stdout, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+	fprintf(stdout, ##args); \
+	putc('\n', stdout); \
+} while (0)
+#endif	
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define error(...) do {\
+	fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+	fprintf(stderr, __VA_ARGS__); \
+	putc('\n', stderr); \
+} while (0)
+#else
+#define error(args...) do {\
+	fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+	fprintf(stderr, ##args); \
+	putc('\n', stderr); \
+} while (0)
+#endif	
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define cerror(cond, ...) do {\
+	if (cond || debugflag) { \
+		fprintf(stderr, "%s%s: %s:%d: ", debugflag ? "WARNING: " : "", command, __FUNCTION__, __LINE__); \
+		fprintf(stderr, __VA_ARGS__); \
+		putc('\n', stderr); \
+	} \
+} while (0)
+#else
+#define cerror(cond, args...) do {\
+	if (cond || debugflag) { \
+		fprintf(stderr, "%s%s: %s:%d: ", debugflag ? "WARNING: " : "", command, __FUNCTION__, __LINE__); \
+		fprintf(stderr, ##args); \
+		putc('\n', stderr); \
+	} \
+} while (0)
+#endif	
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define dbg(...) do {\
+	if (!debugflag) break; \
+	fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+	fprintf(stderr, __VA_ARGS__); \
+	putc('\n', stderr); \
+} while (0)
+#else
+#define dbg(args...) do {\
+	if (!debugflag) break; \
+	fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+	fprintf(stderr, ##args); \
+	putc('\n', stderr); \
+} while (0)
+#endif	
+
+int init(const char *file, const char *cardname);
+int save_state(const char *file, const char *cardname);
+int load_state(const char *file, const char *initfile, const char *cardname,
+	       int do_init);
+int power(const char *argv[], int argc);
+int generate_names(const char *cfgfile);
+
+/* utils */
+
+int file_map(const char *filename, char **buf, size_t *bufsize);
+void file_unmap(void *buf, size_t bufsize);
+size_t line_width(const char *buf, size_t bufsize, size_t pos);
+void initfailed(int cardnumber, const char *reason, int exitcode);
+
+static inline int hextodigit(int c)
+{
+        if (c >= '0' && c <= '9')
+                c -= '0';
+        else if (c >= 'a' && c <= 'f')
+                c = c - 'a' + 10;
+        else if (c >= 'A' && c <= 'F')
+                c = c - 'A' + 10;
+        else
+                return -1;
+        return c;
+}
diff --git a/alsactl/alsactl_init.xml b/alsactl/alsactl_init.xml
new file mode 100644
index 0000000..eefe9ef
--- /dev/null
+++ b/alsactl/alsactl_init.xml
@@ -0,0 +1,625 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<article>
+  <section>
+    <title>alsactl init</title>
+    <refentry>
+      <refentryinfo>
+        <title>alsactl init</title>
+        <date>July 2008</date>
+        <productname>alsactl</productname>
+      </refentryinfo>
+
+      <refmeta>
+        <refentrytitle>alsactl_init</refentrytitle>
+        <manvolnum>7</manvolnum>
+        <refmiscinfo class="version"></refmiscinfo>
+      </refmeta>
+
+      <refnamediv>
+        <refname>alsactl init</refname>
+        <refpurpose>alsa control management - initialization</refpurpose>
+      </refnamediv>
+
+      <refsect1><title>DESCRIPTION</title>
+        <para>"alsactl init" provides soundcard specific initialization.</para>
+      </refsect1>
+
+      <refsect1><title>CONFIGURATION</title>
+        <para>All "alsactl init" configuration files are placed in
+        <filename>/usr/share/alsa/init/</filename> directory. The top level
+        configuration file is <filename>/usr/share/alsa/init/00main</filename>.
+	The default top-level file can be also specified using -i or
+	--initfile parameter for the alsactl tool.
+	Every file consists of a set of lines of text. All empty lines or
+        lines beginning with '#' will be ignored.</para>
+
+        <refsect2><title>Rules files</title>
+          <para>The "alsactl init" rules are read from the files located
+          in the <filename>/usr/share/alsa/init/*</filename>. The top
+          level configuration file is <filename>/usr/share/alsa/init/00main</filename>.
+          Every line in the rules file contains at least one key value pair.
+          There are two kind of keys, match and assignment keys. If all match
+          keys are matching against its value, the rule gets applied and the
+          assign keys get the specified value assigned.</para>
+          
+          <para>A rule may consists of a list of one or more key value pairs
+          separated by a comma. Each key has a distinct operation, depending
+          on the used operator. Valid operators are:</para>
+          <variablelist>
+            <varlistentry>
+              <term><option>==</option></term>
+              <listitem>
+                <para>Compare for equality.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>!=</option></term>
+              <listitem>
+                <para>Compare for non-equality.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>=</option></term>
+              <listitem>
+                <para>Assign a value to a key. Keys that represent a list,
+                are reset and only this single value is assigned.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>+=</option></term>
+              <listitem>
+                <para>Add the value to a key that holds a list
+                of entries.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>:=</option></term>
+              <listitem>
+                <para>Assign  a  value  to  a key finally; disallow any
+                later changes, which may be used to prevent changes by
+                any later rules.</para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+
+          <para>The following key names can be used to match against device
+          properties:</para>
+          <variablelist>
+            <varlistentry>
+              <term><option>CARDINDEX</option></term>
+              <listitem>
+                <para>Match the card index of the ALSA driver.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>CTL{<replaceable>attribute</replaceable>}</option></term>
+              <listitem>
+                <para>Set or test universal control attribute. Possible
+		attributes:</para>
+                <variablelist>
+                  <varlistentry>
+                    <term><option>numid</option></term>
+                    <listitem>
+                      <para>Numeric control identification.</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>iface</option>, <option>interface</option></term>
+                    <listitem>
+                      <para>Control interface name (CARD, HWEDEP, MIXER, PCM, RAWMIDI, TIMER, SEQUENCER)</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>subdev</option>, <option>subdevice</option></term>
+                    <listitem>
+                      <para>Subdevice number.</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>name</option></term>
+                    <listitem>
+                      <para>Control name</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>index</option></term>
+                    <listitem>
+                      <para>Control index</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>type</option></term>
+                    <listitem>
+                      <para>Control type (BOOLEAN, INTEGER, INTEGER64, ENUMERATED, BYTES, IEC958)</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>attr</option>, <option>attribute</option></term>
+                    <listitem>
+                      <para>Attributes (stored in a string - use match characters * and ?):</para>
+	              <variablelist>
+	                <varlistentry>
+	                  <term><option>r</option></term>
+	                  <listitem>
+	                    <para>control is readable</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>w</option></term>
+	                  <listitem>
+	                    <para>control is writable</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>v</option></term>
+	                  <listitem>
+	                    <para>control is volatile</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>i</option></term>
+	                  <listitem>
+	                    <para>control is inactive</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>l</option></term>
+	                  <listitem>
+	                    <para>control is locked</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>R</option></term>
+	                  <listitem>
+	                    <para>control is TLV readable</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>W</option></term>
+	                  <listitem>
+	                    <para>control is TLV writable</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>C</option></term>
+	                  <listitem>
+	                    <para>control is TLV commandable</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>o</option></term>
+	                  <listitem>
+	                    <para>process is owner of this control</para>
+	                  </listitem>
+	                </varlistentry>
+	                <varlistentry>
+	                  <term><option>u</option></term>
+	                  <listitem>
+	                    <para>control created in user space</para>
+	                  </listitem>
+	                </varlistentry>
+	              </variablelist>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>owner</option></term>
+                    <listitem>
+                      <para>Control owner process PID number</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>count</option></term>
+                    <listitem>
+                      <para>Control count of values</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>min</option></term>
+                    <listitem>
+                      <para>Value range - minimum value</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>max</option></term>
+                    <listitem>
+                      <para>Value range - maximum value</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>step</option></term>
+                    <listitem>
+                      <para>Value range - step value</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>dBmin</option></term>
+                    <listitem>
+                      <para>Value range - minimum dB value</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>dBmax</option></term>
+                    <listitem>
+                      <para>Value range - maximum dB value</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>items</option></term>
+                    <listitem>
+                      <para>Enumerated value - number of text items</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>enums</option></term>
+                    <listitem>
+                      <para>Enumerated value - list of text names stored between '|' character</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>value</option></term>
+                    <listitem>
+                      <para>Value of control stored to a string delimited by
+                      comma (,).</para>
+                    </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>do_search</option></term>
+                    <listitem>
+                      <para>Search for a control. Value "1" is returned
+                      if a control was found. The CTL{name} key might
+		      contain match characters * and ?. An control index
+		      might be specified as first argument starting from
+		      zero (e.g. CTL{do_search 2}="1").</para>
+                     </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>do_count</option></term>
+                    <listitem>
+                      <para>Search for a controls and return total count
+                      of matched ones. The CTL{name} key might contain match
+                      characters * and ?.</para>
+                     </listitem>
+                  </varlistentry>
+                </variablelist>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>CONFIG{sysfs_device}</option></term>
+              <listitem>
+                <para>The relative path to sysfs subsystem specifying
+                the root directory of a soundcard device. Usually,
+                it should be set to "/class/sound/card$cardinfo{card}/device".
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ATTR{<replaceable>filename</replaceable>}</option></term>
+              <listitem>
+                <para>Match sysfs attribute values of the soundcard device.
+                The relative path to sysfs tree must be defined by
+		CONFIG{sysfs_device} key. Trailing whitespace in the attribute
+		values is ignored, if the specified match value does
+		not contain trailing whitespace itself. Depending on
+		the type of operator, this key is also used to set
+		the value of a sysfs attribute.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ENV{<replaceable>key</replaceable>}</option></term>
+              <listitem>
+                <para>Match against the value of an environment variable. Up
+                to five <option>ENV</option> keys can be specified per rule.
+                Depending on the type of operator, this key is also used
+                to export a variable to the environment.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>PROGRAM</option></term>
+              <listitem>
+                <para>Execute external program. The key is true, if
+                the program returns without exit code zero. The whole event
+                environment is available to the executed program. The
+                program's output printed to stdout is available for
+                the RESULT key.</para>
+                <para>Several buildin commands are available:</para>
+                <variablelist>
+                  <varlistentry>
+                    <term><option>__ctl_search</option></term>
+                    <listitem>
+                      <para>Search for a control. The CTL{name} key might
+		      contain match characters * and ?. An control index
+		      might be specified as first argument starting from
+		      zero (e.g. PROGRAM="__ctl_search 2").</para>
+                     </listitem>
+                  </varlistentry>
+                  <varlistentry>
+                    <term><option>__ctl_count</option></term>
+                    <listitem>
+                      <para>Search for a controls and return total count
+                      of matched ones. The CTL{name} key might contain match
+                      characters * and ?.</para>
+                     </listitem>
+                  </varlistentry>
+		</variablelist>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>RESULT</option></term>
+              <listitem>
+                <para>Match the returned string of the last PROGRAM call.
+                This key can be used in the same or in any later rule
+                after a PROGRAM call.</para>
+              </listitem>
+            </varlistentry>
+
+          </variablelist>
+
+          <para>Most of the fields support a shell style pattern matching.
+          The following pattern characters are supported:</para>
+          <variablelist>
+            <varlistentry>
+              <term><option>*</option></term>
+              <listitem>
+                <para>Matches zero, or any number of characters.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><option>?</option></term>
+              <listitem>
+                <para>Matches any single character.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><option>[]</option></term>
+              <listitem>
+                <para>Matches any single character specified within
+                the brackets. For example, the pattern string 'tty[SR]'
+                would match either 'ttyS' or 'ttyR'. Ranges are also
+                supported within this match with the '-' character.
+      		For example, to match on the range of all digits,
+      		the pattern [0-9] would be used. If the first character
+      		following the '[' is a '!', any characters
+                not enclosed are matched.</para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+
+          <para>The following keys can get values assigned:</para>
+          <variablelist>
+            <varlistentry>
+              <term><option>CTL{numid}</option>, <option>CTL{iface}</option>,
+		    <option>CTL{device}</option>, <option>CTL{subdev}</option>, 
+		    <option>CTL{name}</option>,  <option>CTL{index}</option>, 
+	      </term>
+              <listitem>
+              	 <para>Select universal control element.</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><option>CTL{value}</option></term>
+              <listitem>
+              	 <para>Value is set (written) also to soundcard's control
+              	 device and RESULT key is set to errno code. The result of
+                 set operation is always true (it means continue with
+                 next key on line).</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><option>CTL{values}</option></term>
+              <listitem>
+              	 <para>Value is set (written) also to soundcard's control
+              	 device (all control values are set to specified value) and
+              	 RESULT key is set to errno code. The result of
+                 set operation is always true (it means continue with
+                 next key on line).</para>
+              </listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><option>CTL{write}</option></term>
+              <listitem>
+              	 <para>Value is set (written) also to soundcard's control
+              	 device (all control values are set to specified value).
+              	 The result of set operation is true when operation
+              	 succeed (it means continue with next key on line).</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ENV{<replaceable>key</replaceable>}</option></term>
+              <listitem>
+                <para>Export a variable to the environment. Depending on the type of operator,
+                this key is also to match against an environment variable.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>RESULT</option></term>
+              <listitem>
+                <para>Set RESULT variable. Note that PROGRAM also sets
+                this variable, but setting this variable manually
+                might be useful to change code execution order (included
+                files).</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>LABEL</option></term>
+              <listitem>
+                <para>Named label where a GOTO can jump to.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>GOTO</option></term>
+              <listitem>
+                <para>Jumps to the next LABEL with a matching name.
+                      The goto cannot jump backward.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>INCLUDE</option></term>
+              <listitem>
+                <para>Include specified filename or all files in specified directory</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ACCESS</option></term>
+              <listitem>
+                <para>Check if specified file or directory exists</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>CONFIG{sysfs_device}</option></term>
+              <listitem>
+                <para>The relative path to sysfs subsystem specifying
+                the root directory of a soundcard device. Usually,
+                it should be set to "/class/sound/card$cardinfo{card}/device".
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>PRINT</option></term>
+              <listitem>
+                <para>PRINT value to stdout.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>ERROR</option></term>
+              <listitem>
+                <para>PRINT value to stderr.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>EXIT</option></term>
+              <listitem>
+                <para>Exit immediately and set program exit code to value
+                (should be integer). If value is "return" string,
+                parser leaves current included file and returns to parent
+                configuration file.</para>
+              </listitem>
+            </varlistentry>
+
+          </variablelist>
+
+          <para>The <option>PROGRAM</option>, <option>RESULT</option>,
+          <option>CTL{value}</option>,
+	  <option>PRINT</option>, <option>ERROR</option>,
+	  <option>EXIT</option>, <option>CONFIG{}</option>
+	  fields support simple printf-like string substitutions.
+          It allows the use of the complete environment set by earlier matching
+          rules. For all other fields, substitutions are applied while the individual rule is
+          being processed. The available substitutions are:</para>
+          <variablelist>
+            <varlistentry>
+	      <term><option>$cardinfo{<replaceable>attribute</replaceable>}</option>, <option>%i{<replaceable>attribute</replaceable>}</option></term>
+              <listitem>
+                <para>See CARDINFO{} for more details.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$ctl{<replaceable>attribute</replaceable>}</option>, <option>%C{<replaceable>attribute</replaceable>}</option></term>
+              <listitem>
+                <para>See CTL{} for more details.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$attr{<replaceable>file</replaceable>}</option>, <option>%s{<replaceable>file</replaceable>}</option></term>
+              <listitem>
+                <para>The value of a sysfs attribute found at the device, where
+                all keys of the rule have matched.
+                If the attribute is a symlink, the last element of the symlink target is
+                returned as the value.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$env{<replaceable>key</replaceable>}</option>, <option>%E{<replaceable>key</replaceable>}</option></term>
+              <listitem>
+                <para>The value of an environment variable.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$result</option>, <option>%c</option></term>
+              <listitem>
+                <para>The string returned by the external program requested with PROGRAM.
+                A single part of the string, separated by a space character may be selected
+                by specifying the part number as an attribute: <option>%c{N}</option>.
+                If the number is followed by the '+' char this part plus all remaining parts
+                of the result string are substituted: <option>%c{N+}</option></para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$sysfsroot</option>, <option>%r</option></term>
+              <listitem>
+                <para>Root directory where sysfs file-system is mounted.
+                Ususally, this value is just "/sys".</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$config{<replaceable>key</replaceable>}</option>, <option>%g{<replaceable>key</replaceable>}</option></term>
+              <listitem>
+                <para>The value of a configuration variable. See CONFIG{} for more details.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>%%</option></term>
+              <listitem>
+              <para>The '%' character itself.</para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><option>$$</option></term>
+              <listitem>
+              <para>The '$' character itself.</para>
+              </listitem>
+            </varlistentry>
+          </variablelist>
+          <para>The count of characters to be substituted may be limited
+          by specifying the format length value. For example, '%3s{file}'
+          will only insert the first three characters of the sysfs
+          attribute</para>
+        </refsect2>
+      </refsect1>
+
+      <refsect1><title>AUTHOR</title>
+        <para>Written by Jaroslav Kysela <email>perex@perex.cz</email></para>
+        <para>Some portions are written by Greg Kroah-Hartman <email>greg@kroah.com</email> and
+        Kay Sievers <email>kay.sievers@vrfy.org</email>.</para>
+      </refsect1>
+
+      <refsect1>
+        <title>SEE ALSO</title>
+        <para><citerefentry>
+            <refentrytitle>alsactl</refentrytitle><manvolnum>1</manvolnum>
+          </citerefentry></para>
+      </refsect1>
+    </refentry>
+  </section>
+</article>
diff --git a/usr/share/alsa/init/00main b/alsactl/init/00main
similarity index 100%
rename from usr/share/alsa/init/00main
rename to alsactl/init/00main
diff --git a/alsactl/init/Makefile.am b/alsactl/init/Makefile.am
new file mode 100644
index 0000000..9e2b83d
--- /dev/null
+++ b/alsactl/init/Makefile.am
@@ -0,0 +1,7 @@
+
+init_files = \
+	00main default help info test \
+	hda
+EXTRA_DIST = $(init_files)
+alsainitdir = $(datadir)/alsa/init
+alsainit_DATA = $(init_files)
diff --git a/alsactl/init/ca0106 b/alsactl/init/ca0106
new file mode 100644
index 0000000..7afd4df
--- /dev/null
+++ b/alsactl/init/ca0106
@@ -0,0 +1,31 @@
+# Configuration for CA0106 driver
+
+CTL{reset}="mixer"
+CTL{name}="Master Playback Volume", CTL{value}="-20dB"
+CTL{name}="Master Playback Switch", CTL{value}="on"
+CTL{name}="Analog Front Playback Volume", CTL{value}="0dB"
+CTL{name}="Analog Front Playback Switch", CTL{value}="on"
+CTL{name}="Analog Rear Playback Volume", CTL{value}="0dB"
+CTL{name}="Analog Rear Playback Switch", CTL{value}="on"
+CTL{name}="Analog Center/LFE Playback Volume", CTL{value}="0dB"
+CTL{name}="Analog Center/LFE Playback Switch", CTL{value}="on"
+CTL{name}="Analog Side Playback Volume", CTL{value}="0dB"
+CTL{name}="Analog Side Playback Switch", CTL{value}="on"
+CTL{name}="IEC958 Front Playback Volume", CTL{value}="0dB"
+CTL{name}="IEC958 Rear Playback Volume", CTL{value}="0dB"
+CTL{name}="IEC958 Center/LFE Playback Volume", CTL{value}="0dB"
+# capture
+CTL{name}="Analog Source Capture Enum", CTL{value}="Mic"
+CTL{name}="Mic Capture Volume", CTL{value}="6dB"
+CTL{name}="Shared Mic/Line in Capture Switch", CTL{value}="Mic in"
+
+# some variants have also AC97 mixer
+
+CTL{reset}="mixer"
+CTL{name}="AC97 Line Capture Volume",PROGRAM!="__ctl_search",GOTO="ac97_end"
+CTL{name}="Analog Source Capture Enum", CTL{value}="AC97 in"
+CTL{name}="AC97 Mic Capture Switch", "on"
+CTL{name}="AC97 Mic Capture Value", "6dB"
+LABEL="ac97_end"
+
+RESULT="true", EXIT="return"
diff --git a/usr/share/alsa/init/default b/alsactl/init/default
similarity index 100%
rename from usr/share/alsa/init/default
rename to alsactl/init/default
diff --git a/usr/share/alsa/init/hda b/alsactl/init/hda
similarity index 100%
rename from usr/share/alsa/init/hda
rename to alsactl/init/hda
diff --git a/usr/share/alsa/init/help b/alsactl/init/help
similarity index 100%
rename from usr/share/alsa/init/help
rename to alsactl/init/help
diff --git a/usr/share/alsa/init/info b/alsactl/init/info
similarity index 100%
rename from usr/share/alsa/init/info
rename to alsactl/init/info
diff --git a/usr/share/alsa/init/test b/alsactl/init/test
similarity index 100%
rename from usr/share/alsa/init/test
rename to alsactl/init/test
diff --git a/alsactl/init_parse.c b/alsactl/init_parse.c
new file mode 100644
index 0000000..51b515c
--- /dev/null
+++ b/alsactl/init_parse.c
@@ -0,0 +1,1777 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program - Parse initialization files
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
+ *		     Greg Kroah-Hartman <greg@kroah.com>,
+ *		     Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *
+ *   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 <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <math.h>
+#include <alsa/asoundlib.h>
+#include "aconfig.h"
+#include "alsactl.h"
+#include "list.h"
+
+#define PATH_SIZE	512
+#define NAME_SIZE	128
+#define EJUSTRETURN	0x7fffffff
+
+enum key_op {
+	KEY_OP_UNSET,
+	KEY_OP_MATCH,
+	KEY_OP_NOMATCH,
+	KEY_OP_ADD,
+	KEY_OP_ASSIGN,
+	KEY_OP_ASSIGN_FINAL
+};
+
+struct pair {
+	char *key;
+	char *value;
+	struct pair *next;
+};
+
+struct space {
+	struct pair *pairs;
+	char *rootdir;
+	char *go_to;
+	char *program_result;
+	const char *filename;
+	int linenum;
+	int log_run;
+	int exit_code;
+	int quit;
+	unsigned int ctl_id_changed;
+	snd_hctl_t *ctl_handle;
+	snd_ctl_card_info_t *ctl_card_info;
+	snd_ctl_elem_id_t *ctl_id;
+	snd_ctl_elem_info_t *ctl_info;
+	snd_ctl_elem_value_t *ctl_value;
+};
+
+static void Perror(struct space *space, const char *fmt, ...)
+{
+	va_list arg;
+	va_start(arg, fmt);
+	fprintf(stderr, "%s:%i: ", space->filename, space->linenum);
+	vfprintf(stderr, fmt, arg);
+	putc('\n', stderr);
+	va_end(arg);
+}
+
+#include "init_sysdeps.c"
+#include "init_utils_string.c"
+#include "init_utils_run.c"
+#include "init_sysfs.c"
+
+static void free_space(struct space *space)
+{
+	struct pair *pair = space->pairs;
+	struct pair *next = pair;
+
+	while (next) {
+		pair = next;
+		next = pair->next;
+		free(pair->value);
+		free(pair->key);
+		free(pair);
+	}
+	space->pairs = NULL;
+	if (space->ctl_value) {
+		snd_ctl_elem_value_free(space->ctl_value);
+		space->ctl_value = NULL;
+	}
+	if (space->ctl_info) {
+		snd_ctl_elem_info_free(space->ctl_info);
+		space->ctl_info = NULL;
+	}
+	if (space->ctl_id) {
+		snd_ctl_elem_id_free(space->ctl_id);
+		space->ctl_id = NULL;
+	}
+	if (space->ctl_card_info) {
+		snd_ctl_card_info_free(space->ctl_card_info);
+		space->ctl_card_info = NULL;
+	}
+	if (space->ctl_handle) {
+		snd_hctl_close(space->ctl_handle);
+		space->ctl_handle = NULL;
+	}
+	if (space->rootdir)
+		free(space->rootdir);
+	if (space->program_result)
+		free(space->program_result);
+	if (space->go_to)
+		free(space->go_to);
+	free(space);
+}
+
+static struct pair *value_find(struct space *space, const char *key)
+{
+	struct pair *pair = space->pairs;
+	
+	while (pair && strcmp(pair->key, key) != 0)
+		pair = pair->next;
+	return pair;
+}
+
+static int value_set(struct space *space, const char *key, const char *value)
+{
+	struct pair *pair;
+	
+	pair = value_find(space, key);
+	if (pair) {
+		free(pair->value);
+		pair->value = strdup(value);
+		if (pair->value == NULL)
+			return -ENOMEM;
+	} else {
+		pair = malloc(sizeof(struct pair));
+		if (pair == NULL)
+			return -ENOMEM;
+		pair->key = strdup(key);
+		if (pair->key == NULL) {
+			free(pair);
+			return -ENOMEM;
+		}
+		pair->value = strdup(value);
+		if (pair->value == NULL) {
+			free(pair->key);
+			free(pair);
+			return -ENOMEM;
+		}
+		pair->next = space->pairs;
+		space->pairs = pair;
+	}
+	return 0;
+}
+
+static int init_space(struct space **space, int card)
+{
+	struct space *res;
+	char device[16];
+	int err;
+
+	res = calloc(1, sizeof(struct space));
+	if (res == NULL)
+		return -ENOMEM;
+	res->ctl_id_changed = ~0;
+	res->linenum = -1;
+	sprintf(device, "hw:%u", card);
+	err = snd_hctl_open(&res->ctl_handle, device, 0);
+	if (err < 0)
+		goto error;
+	err = snd_hctl_load(res->ctl_handle);
+	if (err < 0)
+		goto error;
+	err = snd_ctl_card_info_malloc(&res->ctl_card_info);
+	if (err < 0)
+		goto error;
+	err = snd_ctl_card_info(snd_hctl_ctl(res->ctl_handle), res->ctl_card_info);
+	if (err < 0)
+		goto error;
+	err = snd_ctl_elem_id_malloc(&res->ctl_id);
+	if (err < 0)
+		goto error;
+	err = snd_ctl_elem_info_malloc(&res->ctl_info);
+	if (err < 0)
+		goto error;
+	err = snd_ctl_elem_value_malloc(&res->ctl_value);
+	if (err < 0)
+		goto error;
+	*space = res;
+	return 0;
+ error:
+ 	free_space(res);
+ 	return err;
+}
+
+static const char *cardinfo_get(struct space *space, const char *attr)
+{
+	if (strncasecmp(attr, "CARD", 4) == 0) {
+		static char res[16];
+		sprintf(res, "%u", snd_ctl_card_info_get_card(space->ctl_card_info));
+		return res;
+	}
+	if (strncasecmp(attr, "ID", 2) == 0)
+		return snd_ctl_card_info_get_id(space->ctl_card_info);
+	if (strncasecmp(attr, "DRIVER", 6) == 0)
+		return snd_ctl_card_info_get_driver(space->ctl_card_info);
+	if (strncasecmp(attr, "NAME", 4) == 0)
+		return snd_ctl_card_info_get_name(space->ctl_card_info);
+	if (strncasecmp(attr, "LONGNAME", 8) == 0)
+		return snd_ctl_card_info_get_longname(space->ctl_card_info);
+	if (strncasecmp(attr, "MIXERNAME", 9) == 0)
+		return snd_ctl_card_info_get_mixername(space->ctl_card_info);
+	if (strncasecmp(attr, "COMPONENTS", 10) == 0)
+		return snd_ctl_card_info_get_components(space->ctl_card_info);
+	Perror(space, "unknown cardinfo{} attribute '%s'", attr);
+	return NULL;
+}
+
+static int check_id_changed(struct space *space, unsigned int what)
+{
+	snd_hctl_elem_t *elem;
+	int err;
+
+	if ((space->ctl_id_changed & what & 1) != 0) {
+		snd_ctl_elem_id_set_numid(space->ctl_id, 0);
+		elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+		if (!elem)
+			return -ENOENT;
+		err = snd_hctl_elem_info(elem, space->ctl_info);
+		if (err == 0)
+			space->ctl_id_changed &= ~1;
+		return err;
+	}
+	if ((space->ctl_id_changed & what & 2) != 0) {
+		snd_ctl_elem_id_set_numid(space->ctl_id, 0);
+		elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+		if (!elem)
+			return -ENOENT;
+		err = snd_hctl_elem_read(elem, space->ctl_value);
+		if (err == 0)
+			space->ctl_id_changed &= ~2;
+		return err;
+	}
+	return 0;
+}
+
+static const char *get_ctl_value(struct space *space)
+{
+	snd_ctl_elem_type_t type;
+	unsigned int idx, count;
+	static char res[1024], tmp[16];
+	static const char hex[] = "0123456789abcdef";
+	char *pos;
+	const char *pos1;
+
+	type = snd_ctl_elem_info_get_type(space->ctl_info);
+	count = snd_ctl_elem_info_get_count(space->ctl_info);
+	res[0] = '\0';
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		for (idx = 0; idx < count; idx++) {
+			if (idx > 0)
+				strlcat(res, ",", sizeof(res));
+			strlcat(res, snd_ctl_elem_value_get_boolean(space->ctl_value, idx) ? "on" : "off", sizeof(res));
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		for (idx = 0; idx < count; idx++) {
+			if (idx > 0)
+				strlcat(res, ",", sizeof(res));
+			snprintf(tmp, sizeof(tmp), "%li", snd_ctl_elem_value_get_integer(space->ctl_value, idx));
+			strlcat(res, tmp, sizeof(res));
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		for (idx = 0; idx < count; idx++) {
+			if (idx > 0)
+				strlcat(res, ",", sizeof(res));
+			snprintf(tmp, sizeof(tmp), "%lli", snd_ctl_elem_value_get_integer64(space->ctl_value, idx));
+			strlcat(res, tmp, sizeof(res));
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		for (idx = 0; idx < count; idx++) {
+			if (idx > 0)
+				strlcat(res, ",", sizeof(res));
+			snprintf(tmp, sizeof(tmp), "%u", snd_ctl_elem_value_get_enumerated(space->ctl_value, idx));
+			strlcat(res, tmp, sizeof(res));
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_BYTES:
+	case SND_CTL_ELEM_TYPE_IEC958:
+		if (type == SND_CTL_ELEM_TYPE_IEC958)
+			count = sizeof(snd_aes_iec958_t);
+		if (count > (sizeof(res)-1)/2)
+			count = (sizeof(res)-1/2);
+		pos = res;
+		pos1 = snd_ctl_elem_value_get_bytes(space->ctl_value);
+		while (count > 0) {
+			idx = *pos1++;
+			*pos++ = hex[idx >> 4];
+			*pos++ = hex[idx & 0x0f];
+			count++;
+		}
+		*pos++ = '\0';
+		break;
+	default:
+		Perror(space, "unknown element type '%i'", type);
+		return NULL;
+	}
+	return res;
+}
+
+/* Function to convert from percentage to volume. val = percentage */
+#define convert_prange1(val, min, max) \
+        ceil((val) * ((max) - (min)) * 0.01 + (min))
+
+static int set_ctl_value(struct space *space, const char *value, int all)
+{
+	snd_ctl_elem_type_t type;
+	unsigned int idx, idx2, count, items;
+	const char *pos, *pos2;
+	snd_hctl_elem_t *elem;
+	int val;
+	long lval;
+
+	type = snd_ctl_elem_info_get_type(space->ctl_info);
+	count = snd_ctl_elem_info_get_count(space->ctl_info);
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		for (idx = 0; idx < count; idx++) {
+			while (*value == ' ')
+				value++;
+			if (*value == '\0')
+				goto missing;
+			val = strncasecmp(value, "true", 4) == 0 ||
+				strncasecmp(value, "yes", 3) == 0 ||
+				strncasecmp(value, "on", 2) == 0 ||
+				strncasecmp(value, "1", 1) == 0;
+			snd_ctl_elem_value_set_boolean(space->ctl_value, idx, val);
+			if (all)
+				continue;
+			pos = strchr(value, ',');
+			value = pos ? pos + 1 : value + strlen(value) - 1;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		for (idx = 0; idx < count; idx++) {
+			while (*value == ' ')
+				value++;
+			pos = strchr(value, ',');
+			if (pos)
+				*(char *)pos = '\0';
+			remove_trailing_chars((char *)value, ' ');
+			items = pos ? pos - value : strlen(value);
+			if (items > 1 && value[items-1] == '%') {
+				val = convert_prange1(strtol(value, NULL, 0), snd_ctl_elem_info_get_min(space->ctl_info), snd_ctl_elem_info_get_max(space->ctl_info));
+				snd_ctl_elem_value_set_integer(space->ctl_value, idx, val);
+			} else if (items > 2 && value[items-2] == 'd' && value[items-1] == 'B') {
+				val = strtol(value, NULL, 0) * 100;
+				if ((pos2 = strchr(value, '.')) != NULL) {
+					if (isdigit(*(pos2-1)) && isdigit(*(pos2-2))) {
+						if (val < 0)
+							val -= strtol(pos2 + 1, NULL, 0);
+						else
+							val += strtol(pos2 + 1, NULL, 0);
+					} else if (isdigit(*(pos2-1))) {
+						if (val < 0)
+							val -= strtol(pos2 + 1, NULL, 0) * 10;
+						else
+							val += strtol(pos2 + 1, NULL, 0) * 10;
+					}
+				}
+				val = snd_ctl_convert_from_dB(snd_hctl_ctl(space->ctl_handle), space->ctl_id, val, &lval, -1);
+				if (val < 0) {
+					dbg("unable to convert dB value '%s' to internal integer range", value);
+					return val;
+				}
+				snd_ctl_elem_value_set_integer(space->ctl_value, idx, lval);
+			} else {
+				snd_ctl_elem_value_set_integer(space->ctl_value, idx, strtol(value, NULL, 0));
+			}
+			if (all)
+				continue;
+			value = pos ? pos + 1 : value + strlen(value) - 1;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		for (idx = 0; idx < count; idx++) {
+			while (*value == ' ')
+				value++;
+			snd_ctl_elem_value_set_integer64(space->ctl_value, idx, strtoll(value, NULL, 0));
+			if (all)
+				continue;
+			pos = strchr(value, ',');
+			value = pos ? pos + 1 : value + strlen(value) - 1;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		for (idx = 0; idx < count; idx++) {
+			while (*value == ' ')
+				value++;
+			pos = strchr(value, ',');
+			if (isdigit(value[0]) || value[0] == '-') {
+				snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, strtol(value, NULL, 0));
+			} else {
+				if (pos)
+					*(char *)pos = '\0';
+				remove_trailing_chars((char *)value, ' ');
+				items = snd_ctl_elem_info_get_items(space->ctl_info);
+				for (idx2 = 0; idx2 < items; idx2++) {
+					snd_ctl_elem_info_set_item(space->ctl_info, idx2);
+					elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+					if (elem == NULL)
+						return -ENOENT;
+					val = snd_hctl_elem_info(elem, space->ctl_info);
+					if (val < 0)
+						return val;
+					if (strcasecmp(snd_ctl_elem_info_get_item_name(space->ctl_info), value) == 0) {
+						snd_ctl_elem_value_set_enumerated(space->ctl_value, idx, idx2);
+						break;
+					}
+				}
+				if (idx2 >= items) {
+					Perror(space, "wrong enum identifier '%s'", value);
+					return -EINVAL;
+				}
+			}
+			if (all)
+				continue;
+			value = pos ? pos + 1 : value + strlen(value) - 1;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_BYTES:
+	case SND_CTL_ELEM_TYPE_IEC958:
+		if (type == SND_CTL_ELEM_TYPE_IEC958)
+			count = sizeof(snd_aes_iec958_t);
+		while (*value == ' ')
+			value++;
+		if (strlen(value) != count * 2) {
+			Perror(space, "bad ctl value hexa length (should be %u bytes)", count);
+			return -EINVAL;
+		}
+		for (idx = 0; idx < count; idx += 2) {
+			val = hextodigit(*(value++)) << 4;
+			val |= hextodigit(*(value++));
+			if (val > 255) {
+				Perror(space, "bad ctl hexa value");
+				return -EINVAL;
+			}
+			snd_ctl_elem_value_set_byte(space->ctl_value, idx, val);
+		}
+		break;
+	default:
+		Perror(space, "unknown element type '%i'", type);
+		return -EINVAL;
+	}
+	return 0;
+  missing:
+  	printf("%i %i\n", type, count);
+  	Perror(space, "missing some ctl values (line %i)", space->linenum);
+  	return -EINVAL;
+}
+
+static int do_match(const char *key, enum key_op op,
+		    const char *key_value, const char *value)
+{
+	int match;
+
+	if (value == NULL)
+		return 0;
+	dbg("match %s '%s' <-> '%s'", key, key_value, value);
+	match = fnmatch(key_value, value, 0) == 0;
+	if (match && op == KEY_OP_MATCH) {
+		dbg("%s is true (matching value)", key);
+		return 1;
+	}
+	if (!match && op == KEY_OP_NOMATCH) {
+		dbg("%s is true (non-matching value)", key);
+		return 1;
+	}
+	dbg("%s is false", key);
+	return 0;
+}
+
+static int ctl_match(snd_ctl_elem_id_t *pattern, snd_ctl_elem_id_t *id)
+{
+	if (snd_ctl_elem_id_get_interface(pattern) != -1 &&
+	    snd_ctl_elem_id_get_interface(pattern) != snd_ctl_elem_id_get_interface(id))
+	    	return 0;
+	if (snd_ctl_elem_id_get_device(pattern) != -1 &&
+	    snd_ctl_elem_id_get_device(pattern) != snd_ctl_elem_id_get_device(id))
+		return 0;
+	if (snd_ctl_elem_id_get_subdevice(pattern) != -1 &&
+	    snd_ctl_elem_id_get_subdevice(pattern) != snd_ctl_elem_id_get_subdevice(id))
+	    	return 0;
+	if (snd_ctl_elem_id_get_index(pattern) != -1 &&
+	    snd_ctl_elem_id_get_index(pattern) != snd_ctl_elem_id_get_index(id))
+	    	return 0;
+	if (fnmatch(snd_ctl_elem_id_get_name(pattern), snd_ctl_elem_id_get_name(id), 0) != 0)
+		return 0;
+	return 1;
+}
+
+static const char *elemid_get(struct space *space, const char *attr)
+{
+	long long val;
+	snd_ctl_elem_type_t type;
+	static char res[256];
+
+	if (strncasecmp(attr, "numid", 5) == 0) {
+		val = snd_ctl_elem_id_get_numid(space->ctl_id);
+	    	goto value;
+	}
+	if (strncasecmp(attr, "iface", 5) == 0 ||
+	    strncasecmp(attr, "interface", 9) == 0)
+	    	return snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(space->ctl_id));
+	if (strncasecmp(attr, "device", 6) == 0) {
+		val = snd_ctl_elem_id_get_device(space->ctl_id);
+	    	goto value;
+	}
+	if (strncasecmp(attr, "subdev", 6) == 0) {
+		val = snd_ctl_elem_id_get_subdevice(space->ctl_id);
+	    	goto value;
+	}
+	if (strncasecmp(attr, "name", 4) == 0)
+		return snd_ctl_elem_id_get_name(space->ctl_id);
+	if (strncasecmp(attr, "index", 5) == 0) {
+		val = snd_ctl_elem_id_get_index(space->ctl_id);
+	    	goto value;
+	}
+	if (strncasecmp(attr, "type", 4) == 0) {
+		if (check_id_changed(space, 1))
+			return NULL;
+		return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(space->ctl_info));
+	}
+	if (strncasecmp(attr, "attr", 4) == 0) {
+		if (check_id_changed(space, 1))
+			return NULL;
+		res[0] = '\0';
+		if (snd_ctl_elem_info_is_readable(space->ctl_info))
+			strcat(res, "r");
+		if (snd_ctl_elem_info_is_writable(space->ctl_info))
+			strcat(res, "w");
+		if (snd_ctl_elem_info_is_volatile(space->ctl_info))
+			strcat(res, "v");
+		if (snd_ctl_elem_info_is_inactive(space->ctl_info))
+			strcat(res, "i");
+		if (snd_ctl_elem_info_is_locked(space->ctl_info))
+			strcat(res, "l");
+		if (snd_ctl_elem_info_is_tlv_readable(space->ctl_info))
+			strcat(res, "R");
+		if (snd_ctl_elem_info_is_tlv_writable(space->ctl_info))
+			strcat(res, "W");
+		if (snd_ctl_elem_info_is_tlv_commandable(space->ctl_info))
+			strcat(res, "C");
+		if (snd_ctl_elem_info_is_owner(space->ctl_info))
+			strcat(res, "o");
+		if (snd_ctl_elem_info_is_user(space->ctl_info))
+			strcat(res, "u");
+		return res;
+	}
+	if (strncasecmp(attr, "owner", 5) == 0) {
+		if (check_id_changed(space, 1))
+			return NULL;
+		val = snd_ctl_elem_info_get_owner(space->ctl_info);
+		goto value;
+	}
+	if (strncasecmp(attr, "count", 5) == 0) {
+		if (check_id_changed(space, 1))
+			return NULL;
+		val = snd_ctl_elem_info_get_count(space->ctl_info);
+		goto value;
+	}
+	if (strncasecmp(attr, "min", 3) == 0) {
+		if (check_id_changed(space, 1))
+			return NULL;
+		type = snd_ctl_elem_info_get_type(space->ctl_info);
+		if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+			val = snd_ctl_elem_info_get_min64(space->ctl_info);
+		else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+			val = snd_ctl_elem_info_get_min(space->ctl_info);
+		else
+			goto empty;
+		goto value;
+	}
+	if (strncasecmp(attr, "max", 3) == 0) {
+		if (check_id_changed(space, 1))
+			return NULL;
+		type = snd_ctl_elem_info_get_type(space->ctl_info);
+		if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+			val = snd_ctl_elem_info_get_max64(space->ctl_info);
+		else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+			val = snd_ctl_elem_info_get_max(space->ctl_info);
+		else
+			goto empty;
+		goto value;
+	}
+	if (strncasecmp(attr, "step", 3) == 0) {
+		if (check_id_changed(space, 1))
+			return NULL;
+		type = snd_ctl_elem_info_get_type(space->ctl_info);
+		if (type == SND_CTL_ELEM_TYPE_INTEGER64)
+			val = snd_ctl_elem_info_get_step64(space->ctl_info);
+		else if (type == SND_CTL_ELEM_TYPE_INTEGER)
+			val = snd_ctl_elem_info_get_step(space->ctl_info);
+		else
+			goto empty;
+		goto value;
+	}
+	if (strncasecmp(attr, "items", 5) == 0) {
+		if (check_id_changed(space, 1))
+			return NULL;
+		if (snd_ctl_elem_info_get_type(space->ctl_info) == SND_CTL_ELEM_TYPE_ENUMERATED)
+			val = snd_ctl_elem_info_get_items(space->ctl_info);
+		else {
+		  empty:
+			res[0] = '\0';
+			return res;
+		}
+		goto value;
+	}
+	if (strncasecmp(attr, "value", 5) == 0) {
+		if (check_id_changed(space, 3))
+			return NULL;
+		return get_ctl_value(space);
+	}
+	if (strncasecmp(attr, "dBmin", 5) == 0) {
+		long min, max;
+		if (check_id_changed(space, 1))
+			return NULL;
+		if (snd_ctl_get_dB_range(snd_hctl_ctl(space->ctl_handle), space->ctl_id, &min, &max) < 0)
+			goto empty;
+		val = min;
+dbvalue:
+		sprintf(res, "%li.%02idB", (long)(val / 100), (int)abs(val % 100));
+		return res;
+	}
+	if (strncasecmp(attr, "dBmax", 5) == 0) {
+		long min, max;
+		if (check_id_changed(space, 1))
+			return NULL;
+		if (snd_ctl_get_dB_range(snd_hctl_ctl(space->ctl_handle), space->ctl_id, &min, &max) < 0)
+			goto empty;
+		val = max;
+		goto dbvalue;
+	}
+	if (strncasecmp(attr, "enums", 5) == 0) {
+		unsigned int idx, items;
+		snd_hctl_elem_t *elem;
+		if (check_id_changed(space, 1))
+			return NULL;
+		if (snd_ctl_elem_info_get_type(space->ctl_info) != SND_CTL_ELEM_TYPE_ENUMERATED)
+			goto empty;
+		items = snd_ctl_elem_info_get_items(space->ctl_info);
+		strcpy(res, "|");
+		for (idx = 0; idx < items; idx++) {
+			snd_ctl_elem_info_set_item(space->ctl_info, idx);
+			elem = snd_hctl_find_elem(space->ctl_handle, space->ctl_id);
+			if (elem == NULL)
+				break;
+			if (snd_hctl_elem_info(elem, space->ctl_info) < 0)
+				break;
+			strlcat(res, snd_ctl_elem_info_get_item_name(space->ctl_info), sizeof(res));
+			strlcat(res, "|", sizeof(res));
+		}
+		return res;
+	}
+	if (strncasecmp(attr, "do_search", 9) == 0) {
+		int err, index = 0;
+		snd_hctl_elem_t *elem;
+		snd_ctl_elem_id_t *id;
+		char *pos = strchr(attr, ' ');
+		if (pos)
+			index = strtol(pos, NULL, 0);
+		err = snd_ctl_elem_id_malloc(&id);
+		if (err < 0)
+			return NULL;
+		elem = snd_hctl_first_elem(space->ctl_handle);
+		while (elem) {
+			snd_hctl_elem_get_id(elem, id);
+			if (!ctl_match(space->ctl_id, id))
+				goto next_search;
+			if (index > 0) {
+				index--;
+				goto next_search;
+			}
+			strcpy(res, "1");
+			snd_ctl_elem_id_copy(space->ctl_id, id);
+			snd_ctl_elem_id_free(id);
+			dbg("do_ctl_search found a control");
+			return res;
+		      next_search:
+			elem = snd_hctl_elem_next(elem);
+		}
+		snd_ctl_elem_id_free(id);
+		strcpy(res, "0");
+		return res;
+	}
+	if (strncasecmp(attr, "do_count", 8) == 0) {
+		int err, index = 0;
+		snd_hctl_elem_t *elem;
+		snd_ctl_elem_id_t *id;
+		err = snd_ctl_elem_id_malloc(&id);
+		if (err < 0)
+			return NULL;
+		elem = snd_hctl_first_elem(space->ctl_handle);
+		while (elem) {
+			snd_hctl_elem_get_id(elem, id);
+			if (ctl_match(space->ctl_id, id))
+				index++;
+			elem = snd_hctl_elem_next(elem);
+		}
+		snd_ctl_elem_id_free(id);
+		sprintf(res, "%u", index);
+		dbg("do_ctl_count found %s controls", res);
+		return res;
+	}
+	Perror(space, "unknown ctl{} attribute '%s'", attr);
+	return NULL;
+  value:
+  	sprintf(res, "%lli", val);
+  	return res;
+}
+
+static int elemid_set(struct space *space, const char *attr, const char *value)
+{
+	unsigned int val;
+	void (*fcn)(snd_ctl_elem_id_t *, unsigned int);
+	snd_ctl_elem_iface_t iface;
+	int err;
+
+	if (strncasecmp(attr, "numid", 5) == 0) {
+		fcn = snd_ctl_elem_id_set_numid;
+	    	goto value;
+	}
+	if (strncasecmp(attr, "iface", 5) == 0 ||
+	    strncasecmp(attr, "interface", 9) == 0 ||
+	    strncasecmp(attr, "reset", 5) == 0 ||
+	    strncasecmp(attr, "search", 6) == 0) {
+	    	if (strlen(value) == 0 && strncasecmp(attr, "search", 6) == 0) {
+	    		iface = 0;
+	    		goto search;
+		}
+	    	for (iface = 0; iface <= SND_CTL_ELEM_IFACE_LAST; iface++) {
+	    		if (strcasecmp(value, snd_ctl_elem_iface_name(iface)) == 0) {
+			    	if (strncasecmp(attr, "reset", 5) == 0)
+			    		snd_ctl_elem_id_clear(space->ctl_id);
+			    	if (strncasecmp(attr, "search", 5) == 0) {
+			    	  search:
+			    		snd_ctl_elem_id_clear(space->ctl_id);
+			    		/* -1 means all */
+			    		snd_ctl_elem_id_set_interface(space->ctl_id, -1);
+			    		snd_ctl_elem_id_set_device(space->ctl_id, -1);
+			    		snd_ctl_elem_id_set_subdevice(space->ctl_id, -1);
+			    		snd_ctl_elem_id_set_name(space->ctl_id, "*");
+			    		snd_ctl_elem_id_set_index(space->ctl_id, -1);
+			    		if (strlen(value) == 0)
+			    			return 0;
+				}
+				snd_ctl_elem_id_set_interface(space->ctl_id, iface);
+				space->ctl_id_changed = ~0;
+			    	return 0;
+			}
+		}
+		Perror(space, "unknown control interface name '%s'", value);
+		return -EINVAL;
+	}
+	if (strncasecmp(attr, "device", 6) == 0) {
+		fcn = snd_ctl_elem_id_set_device;
+	    	goto value;
+	}
+	if (strncasecmp(attr, "subdev", 6) == 0) {
+		fcn = snd_ctl_elem_id_set_subdevice;
+	    	goto value;
+	}
+	if (strncasecmp(attr, "name", 4) == 0) {
+		snd_ctl_elem_id_set_name(space->ctl_id, value);
+	  	space->ctl_id_changed = ~0;
+		return 0;
+	}
+	if (strncasecmp(attr, "index", 5) == 0) {
+		fcn = snd_ctl_elem_id_set_index;
+	    	goto value;
+	}
+	if (strncasecmp(attr, "values", 6) == 0 ||
+	    strncasecmp(attr, "value", 5) == 0) {
+		err = check_id_changed(space, 1);
+		if (err < 0) {
+			Perror(space, "control element not found");
+			return err;
+		}
+		err = set_ctl_value(space, value, strncasecmp(attr, "values", 6) == 0);
+		if (err < 0) {
+			space->ctl_id_changed |= 2;
+		} else {
+			space->ctl_id_changed &= ~2;
+			snd_ctl_elem_value_set_id(space->ctl_value, space->ctl_id);
+			err = snd_ctl_elem_write(snd_hctl_ctl(space->ctl_handle), space->ctl_value);
+			if (err < 0) {
+				Perror(space, "value write error: %s", snd_strerror(err));
+				return err;
+			}
+		}
+	    	return err;
+	}
+	Perror(space, "unknown CTL{} attribute '%s'", attr);
+	return -EINVAL;
+  value:
+  	val = (unsigned int)strtol(value, NULL, 0);
+  	fcn(space->ctl_id, val);
+  	space->ctl_id_changed = ~0;
+  	return 0;
+}
+
+static int get_key(char **line, char **key, enum key_op *op, char **value)
+{
+	char *linepos;
+	char *temp;
+
+	linepos = *line;
+	if (linepos == NULL && linepos[0] == '\0')
+		return -EINVAL;
+
+	/* skip whitespace */
+	while (isspace(linepos[0]) || linepos[0] == ',')
+		linepos++;
+
+	/* get the key */
+	if (linepos[0] == '\0')
+		return -EINVAL;
+	*key = linepos;
+
+	while (1) {
+		linepos++;
+		if (linepos[0] == '\0')
+			return -1;
+		if (isspace(linepos[0]))
+			break;
+		if (linepos[0] == '=')
+			break;
+		if (linepos[0] == '+')
+			break;
+		if (linepos[0] == '!')
+			break;
+		if (linepos[0] == ':')
+			break;
+	}
+
+	/* remember end of key */
+	temp = linepos;
+
+	/* skip whitespace after key */
+	while (isspace(linepos[0]))
+		linepos++;
+	if (linepos[0] == '\0')
+		return -EINVAL;
+
+	/* get operation type */
+	if (linepos[0] == '=' && linepos[1] == '=') {
+		*op = KEY_OP_MATCH;
+		linepos += 2;
+		dbg("operator=match");
+	} else if (linepos[0] == '!' && linepos[1] == '=') {
+		*op = KEY_OP_NOMATCH;
+		linepos += 2;
+		dbg("operator=nomatch");
+	} else if (linepos[0] == '+' && linepos[1] == '=') {
+		*op = KEY_OP_ADD;
+		linepos += 2;
+		dbg("operator=add");
+	} else if (linepos[0] == '=') {
+		*op = KEY_OP_ASSIGN;
+		linepos++;
+		dbg("operator=assign");
+	} else if (linepos[0] == ':' && linepos[1] == '=') {
+		*op = KEY_OP_ASSIGN_FINAL;
+		linepos += 2;
+		dbg("operator=assign_final");
+	} else
+		return -EINVAL;
+
+	/* terminate key */
+	temp[0] = '\0';
+	dbg("key='%s'", *key);
+
+	/* skip whitespace after operator */
+	while (isspace(linepos[0]))
+		linepos++;
+	if (linepos[0] == '\0')
+		return -EINVAL;
+
+	/* get the value*/
+	if (linepos[0] != '"')
+		return -EINVAL;
+	linepos++;
+	*value = linepos;
+
+	while (1) {
+		temp = strchr(linepos, '"');
+		if (temp && temp[-1] == '\\') {
+			linepos = temp + 1;
+			continue;
+		}
+		break;
+	}
+	if (!temp)
+		return -EINVAL;
+	temp[0] = '\0';
+	temp++;
+	dbg("value='%s'", *value);
+
+	/* move line to next key */
+	*line = temp;
+
+	return 0;
+}
+
+/* extract possible KEY{attr} */
+static char *get_key_attribute(struct space *space, char *str, char *res, size_t ressize)
+{
+	char *pos;
+	char *attr;
+
+	attr = strchr(str, '{');
+	if (attr != NULL) {
+		attr++;
+		pos = strchr(attr, '}');
+		if (pos == NULL) {
+			Perror(space, "missing closing brace for format");
+			return NULL;
+		}
+		pos[0] = '\0';
+		strlcpy(res, attr, ressize);
+		pos[0] = '}';
+		dbg("attribute='%s'", res);
+		return res;
+	}
+
+	return NULL;
+}
+
+/* extract possible {attr} and move str behind it */
+static char *get_format_attribute(struct space *space, char **str)
+{
+	char *pos;
+	char *attr = NULL;
+
+	if (*str[0] == '{') {
+		pos = strchr(*str, '}');
+		if (pos == NULL) {
+			Perror(space, "missing closing brace for format");
+			return NULL;
+		}
+		pos[0] = '\0';
+		attr = *str+1;
+		*str = pos+1;
+		dbg("attribute='%s', str='%s'", attr, *str);
+	}
+	return attr;
+}
+
+/* extract possible format length and move str behind it*/
+static int get_format_len(struct space *space, char **str)
+{
+	int num;
+	char *tail;
+
+	if (isdigit(*str[0])) {
+		num = (int) strtoul(*str, &tail, 10);
+		if (num > 0) {
+			*str = tail;
+			dbg("format length=%i", num);
+			return num;
+		} else {
+			Perror(space, "format parsing error '%s'", *str);
+		}
+	}
+	return -1;
+}
+
+static void apply_format(struct space *space, char *string, size_t maxsize)
+{
+	char temp[PATH_SIZE];
+	char temp2[PATH_SIZE];
+	char *head, *tail, *pos, *cpos, *attr, *rest;
+	struct pair *pair;
+	int len;
+	int i;
+	int count;
+	enum subst_type {
+		SUBST_UNKNOWN,
+		SUBST_CARDINFO,
+		SUBST_CTL,
+		SUBST_RESULT,
+		SUBST_ATTR,
+		SUBST_SYSFSROOT,
+		SUBST_ENV,
+		SUBST_CONFIG,
+	};
+	static const struct subst_map {
+		char *name;
+		char fmt;
+		enum subst_type type;
+	} map[] = {
+		{ .name = "cardinfo",	.fmt = 'i',	.type = SUBST_CARDINFO },
+		{ .name = "ctl",	.fmt = 'C',	.type = SUBST_CTL },
+		{ .name = "result",	.fmt = 'c',	.type = SUBST_RESULT },
+		{ .name = "attr",	.fmt = 's',	.type = SUBST_ATTR },
+		{ .name = "sysfsroot",	.fmt = 'r',	.type = SUBST_SYSFSROOT },
+		{ .name = "env",	.fmt = 'E',	.type = SUBST_ENV },
+		{ .name = "config",	.fmt = 'g',	.type = SUBST_CONFIG },
+		{ NULL, '\0', 0 }
+	};
+	enum subst_type type;
+	const struct subst_map *subst;
+
+	head = string;
+	while (1) {
+		len = -1;
+		while (head[0] != '\0') {
+			if (head[0] == '$') {
+				/* substitute named variable */
+				if (head[1] == '\0')
+					break;
+				if (head[1] == '$') {
+					strlcpy(temp, head+2, sizeof(temp));
+					strlcpy(head+1, temp, maxsize);
+					head++;
+					continue;
+				}
+				head[0] = '\0';
+				for (subst = map; subst->name; subst++) {
+					if (strncasecmp(&head[1], subst->name, strlen(subst->name)) == 0) {
+						type = subst->type;
+						tail = head + strlen(subst->name)+1;
+						dbg("will substitute format name '%s'", subst->name);
+						goto found;
+					}
+				}
+			} else if (head[0] == '%') {
+				/* substitute format char */
+				if (head[1] == '\0')
+					break;
+				if (head[1] == '%') {
+					strlcpy(temp, head+2, sizeof(temp));
+					strlcpy(head+1, temp, maxsize);
+					head++;
+					continue;
+				}
+				head[0] = '\0';
+				tail = head+1;
+				len = get_format_len(space, &tail);
+				for (subst = map; subst->name; subst++) {
+					if (tail[0] == subst->fmt) {
+						type = subst->type;
+						tail++;
+						dbg("will substitute format char '%c'", subst->fmt);
+						goto found;
+					}
+				}
+			}
+			head++;
+		}
+		break;
+found:
+		attr = get_format_attribute(space, &tail);
+		strlcpy(temp, tail, sizeof(temp));
+		dbg("format=%i, string='%s', tail='%s'", type ,string, tail);
+
+		switch (type) {
+		case SUBST_CARDINFO:
+			if (attr == NULL)
+				Perror(space, "missing identification parametr for cardinfo");
+			else {
+				const char *value = cardinfo_get(space, attr);
+				if (value == NULL)
+					break;
+				strlcat(string, value, maxsize);
+				dbg("substitute cardinfo{%s} '%s'", attr, value);
+			}
+			break;
+		case SUBST_CTL:
+			if (attr == NULL)
+				Perror(space, "missing identification parametr for ctl");
+			else {
+				const char *value = elemid_get(space, attr);
+				if (value == NULL)
+					break;
+				strlcat(string, value, maxsize);
+				dbg("substitute ctl{%s} '%s'", attr, value);
+			}
+			break;
+		case SUBST_RESULT:
+			if (space->program_result == NULL)
+				break;
+			/* get part part of the result string */
+			i = 0;
+			if (attr != NULL)
+				i = strtoul(attr, &rest, 10);
+			if (i > 0) {
+				dbg("request part #%d of result string", i);
+				cpos = space->program_result;
+				while (--i) {
+					while (cpos[0] != '\0' && !isspace(cpos[0]))
+						cpos++;
+					while (isspace(cpos[0]))
+						cpos++;
+				}
+				if (i > 0) {
+					Perror(space, "requested part of result string not found");
+					break;
+				}
+				strlcpy(temp2, cpos, sizeof(temp2));
+				/* %{2+}c copies the whole string from the second part on */
+				if (rest[0] != '+') {
+					cpos = strchr(temp2, ' ');
+					if (cpos)
+						cpos[0] = '\0';
+				}
+				strlcat(string, temp2, maxsize);
+				dbg("substitute part of result string '%s'", temp2);
+			} else {
+				strlcat(string, space->program_result, maxsize);
+				dbg("substitute result string '%s'", space->program_result);
+			}
+			break;
+		case SUBST_ATTR:
+			if (attr == NULL)
+				Perror(space, "missing file parameter for attr");
+			else {
+				const char *value = NULL;
+				size_t size;
+
+				pair = value_find(space, "sysfs_device");
+				if (pair == NULL)
+					break;
+				value = sysfs_attr_get_value(pair->value, attr);
+
+				if (value == NULL)
+					break;
+
+				/* strip trailing whitespace and replace untrusted characters of sysfs value */
+				size = strlcpy(temp2, value, sizeof(temp2));
+				if (size >= sizeof(temp2))
+					size = sizeof(temp2)-1;
+				while (size > 0 && isspace(temp2[size-1]))
+					temp2[--size] = '\0';
+				count = replace_untrusted_chars(temp2);
+				if (count > 0)
+					Perror(space, "%i untrusted character(s) replaced" , count);
+				strlcat(string, temp2, maxsize);
+				dbg("substitute sysfs value '%s'", temp2);
+			}
+			break;
+		case SUBST_SYSFSROOT:
+			strlcat(string, sysfs_path, maxsize);
+			dbg("substitute sysfs_path '%s'", sysfs_path);
+			break;
+		case SUBST_ENV:
+			if (attr == NULL) {
+				dbg("missing attribute");
+				break;
+			}
+			pos = getenv(attr);
+			if (pos == NULL) {
+				dbg("env '%s' not available", attr);
+				break;
+			}
+			dbg("substitute env '%s=%s'", attr, pos);
+			strlcat(string, pos, maxsize);
+			break;
+		case SUBST_CONFIG:
+			if (attr == NULL) {
+				dbg("missing attribute");
+				break;
+			}
+			pair = value_find(space, attr);
+			if (pair == NULL)
+				break;
+			strlcat(string, pair->value, maxsize);
+			break;
+		default:
+			Perror(space, "unknown substitution type=%i", type);
+			break;
+		}
+		/* possibly truncate to format-char specified length */
+		if (len != -1) {
+			head[len] = '\0';
+			dbg("truncate to %i chars, subtitution string becomes '%s'", len, head);
+		}
+		strlcat(string, temp, maxsize);
+	}
+	/* unescape strings */
+	head = tail = string;
+	while (*head != '\0') {
+		if (*head == '\\') {
+			head++;
+			if (*head == '\0')
+				break;
+			switch (*head) {
+			case 'a': *tail++ = '\a'; break;
+			case 'b': *tail++ = '\b'; break;
+			case 'n': *tail++ = '\n'; break;
+			case 'r': *tail++ = '\r'; break;
+			case 't': *tail++ = '\t'; break;
+			case 'v': *tail++ = '\v'; break;
+			case '\\': *tail++ = '\\'; break;
+			default: *tail++ = *head; break;
+			}
+			head++;
+			continue;
+		}
+		if (*head)
+			*tail++ = *head++;
+	}
+	*tail = 0;
+}
+
+static
+int run_program1(struct space *space,
+		 const char *command0, char *result,
+		 size_t ressize, size_t *reslen, int log)
+{
+	if (strncmp(command0, "__ctl_search", 12) == 0) {
+		const char *res = elemid_get(space, "do_search");
+		if (res == NULL || strcmp(res, "1") != 0)
+			return EXIT_FAILURE;
+		return EXIT_SUCCESS;
+	}
+	if (strncmp(command0, "__ctl_count", 11) == 0) {
+		const char *res = elemid_get(space, "do_count");
+		if (res == NULL || strcmp(res, "0") == 0)
+			return EXIT_FAILURE;
+		strlcpy(result, res, ressize);
+		return EXIT_SUCCESS;
+	}
+	Perror(space, "unknown buildin command '%s'", command0);
+	return EXIT_FAILURE;
+}
+
+static int parse(struct space *space, const char *filename);
+
+static char *new_root_dir(const char *filename)
+{
+	char *res, *tmp;
+
+	res = strdup(filename);
+	if (res) {
+		tmp = strrchr(res, '/');
+		if (tmp)
+			*tmp = '\0';
+	}
+	dbg("new_root_dir '%s' '%s'", filename, res);
+	return res;
+}
+
+static int parse_line(struct space *space, char *line, size_t linesize)
+{
+	char *linepos;
+	char *key, *value, *attr, *temp;
+	struct pair *pair;
+	enum key_op op;
+	int err = 0, count;
+	char string[PATH_SIZE];
+	char result[PATH_SIZE];
+
+	linepos = line;
+	while (*linepos != '\0') {
+		op = KEY_OP_UNSET;
+		
+		err = get_key(&linepos, &key, &op, &value);
+		if (err < 0)
+			goto invalid;
+
+		if (strncasecmp(key, "LABEL", 5) == 0) {
+			if (op != KEY_OP_ASSIGN) {
+				Perror(space, "invalid LABEL operation");
+				goto invalid;
+			}
+			if (space->go_to && strcmp(space->go_to, value) == 0) {
+				free(space->go_to);
+				space->go_to = NULL;
+			}
+			continue;
+		}
+		
+		if (space->go_to) {
+			dbg("skip (GOTO '%s')", space->go_to);
+			break;		/* not for us */
+		}
+
+		if (strncasecmp(key, "CTL{", 4) == 0) {
+			attr = get_key_attribute(space, key + 3, string, sizeof(string));
+			if (attr == NULL) {
+				Perror(space, "error parsing CTL attribute");
+				goto invalid;
+			}
+			if (op == KEY_OP_ASSIGN) {
+				strlcpy(result, value, sizeof(result));
+				apply_format(space, result, sizeof(result));
+				dbg("ctl assign: '%s' '%s'", value, attr);
+				err = elemid_set(space, attr, result);
+				if (space->program_result) {
+					free(space->program_result);
+					space->program_result = NULL;
+				}
+				snprintf(string, sizeof(string), "%i", err);
+				space->program_result = strdup(string);
+				err = 0;
+				if (space->program_result == NULL)
+					break;
+			} else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+				if (strncmp(attr, "write", 5) == 0) {
+					strlcpy(result, value, sizeof(result));
+					apply_format(space, result, sizeof(result));
+					dbg("ctl write: '%s' '%s'", value, attr);
+					err = elemid_set(space, "values", result);
+					if (err == 0 && op == KEY_OP_NOMATCH)
+						break;
+					if (err != 0 && op == KEY_OP_MATCH)
+						break;
+				} else {
+					temp = (char *)elemid_get(space, attr);
+					dbg("ctl match: '%s' '%s' '%s'", attr, value, temp);
+					if (!do_match(key, op, value, temp))
+						break;
+				}
+			} else {
+				Perror(space, "invalid CTL{} operation");
+				goto invalid;
+			}
+			continue;
+		}
+		if (strcasecmp(key, "RESULT") == 0) {
+			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+				if (!do_match(key, op, value, space->program_result))
+					break;
+			} else if (op == KEY_OP_ASSIGN) {
+				if (space->program_result) {
+					free(space->program_result);
+					space->program_result = NULL;
+				}
+				strlcpy(string, value, sizeof(string));
+				apply_format(space, string, sizeof(string));
+				space->program_result = strdup(string);
+				if (space->program_result == NULL)
+					break;
+ 			} else {
+				Perror(space, "invalid RESULT operation");
+				goto invalid;
+			}
+			continue;
+		}
+		if (strcasecmp(key, "PROGRAM") == 0) {
+			if (op == KEY_OP_UNSET)
+				continue;
+			strlcpy(string, value, sizeof(string));
+			apply_format(space, string, sizeof(string));
+			if (space->program_result) {
+				free(space->program_result);
+				space->program_result = NULL;
+			}
+			if (run_program(space, string, result, sizeof(result), NULL, space->log_run) != 0) {
+				dbg("PROGRAM '%s' is false", string);
+				if (op != KEY_OP_NOMATCH)
+					break;
+			} else {
+				remove_trailing_chars(result, '\n');
+				count = replace_untrusted_chars(result);
+				if (count)
+					info("%i untrusted character(s) replaced", count);
+				dbg("PROGRAM '%s' result is '%s'", string, result);
+				space->program_result = strdup(result);
+				if (space->program_result == NULL)
+					break;
+				dbg("PROGRAM returned successful");
+				if (op == KEY_OP_NOMATCH)
+					break;
+			}
+			dbg("PROGRAM key is true");
+			continue;
+		}
+		if (strncasecmp(key, "CARDINFO{", 9) == 0) {
+			attr = get_key_attribute(space, key + 8, string, sizeof(string));
+			if (attr == NULL) {
+				Perror(space, "error parsing CARDINFO attribute");
+				goto invalid;
+			}
+			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+				dbg("cardinfo: '%s' '%s'", value, attr);
+				temp = (char *)cardinfo_get(space, attr);
+				if (!do_match(key, op, value, temp))
+					break;
+			} else {
+				Perror(space, "invalid CARDINFO{} operation");
+				goto invalid;
+			}
+			continue;
+		}
+		if (strncasecmp(key, "ATTR{", 5) == 0) {
+			attr = get_key_attribute(space, key + 4, string, sizeof(string));
+			if (attr == NULL) {
+				Perror(space, "error parsing ATTR attribute");
+				goto invalid;
+			}
+			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+				pair = value_find(space, "sysfs_device");
+				if (pair == NULL)
+					break;
+				dbg("sysfs_attr: '%s' '%s'", pair->value, attr);
+				temp = sysfs_attr_get_value(pair->value, attr);
+				if (!do_match(key, op, value, temp))
+					break;
+			} else {
+				Perror(space, "invalid ATTR{} operation");
+				goto invalid;
+			}
+			continue;
+		}
+		if (strncasecmp(key, "ENV{", 4) == 0) {
+			attr = get_key_attribute(space, key + 3, string, sizeof(string));
+			if (attr == NULL) {
+				Perror(space, "error parsing ENV attribute");
+				goto invalid;
+			}
+			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+				temp = getenv(attr);
+				dbg("env: '%s' '%s'", attr, temp);
+				if (!do_match(key, op, value, temp))
+					break;
+			} else if (op == KEY_OP_ASSIGN ||
+				   op == KEY_OP_ASSIGN_FINAL) {
+				strlcpy(result, value, sizeof(result));
+				apply_format(space, result, sizeof(result));
+				dbg("env set: '%s' '%s'", attr, result);
+				if (setenv(attr, result, op == KEY_OP_ASSIGN_FINAL))
+					break;
+			} else {
+				Perror(space, "invalid ENV{} operation");
+				goto invalid;
+			}
+			continue;
+		}
+		if (strcasecmp(key, "GOTO") == 0) {
+			if (op != KEY_OP_ASSIGN) {
+				Perror(space, "invalid GOTO operation");
+				goto invalid;
+			}
+			space->go_to = strdup(value);
+			if (space->go_to == NULL) {
+				err = -ENOMEM;
+				break;
+			}
+			continue;
+		}
+		if (strcasecmp(key, "INCLUDE") == 0) {
+			char *rootdir, *go_to;
+			const char *filename;
+			struct dirent *dirent;
+			DIR *dir;
+			int linenum;
+			if (op != KEY_OP_ASSIGN) {
+				Perror(space, "invalid INCLUDE operation");
+				goto invalid;
+			}
+			if (value[0] == '/')
+				strlcpy(string, value, sizeof(string));
+			else {
+				strlcpy(string, space->rootdir, sizeof(string));
+				strlcat(string, "/", sizeof(string));
+				strlcat(string, value, sizeof(string));
+			}
+			rootdir = space->rootdir;
+			go_to = space->go_to;
+			filename = space->filename;
+			linenum = space->linenum;
+			dir = opendir(string);
+			if (dir) {
+				count = strlen(string);
+				while ((dirent = readdir(dir)) != NULL) {
+					if (strcmp(dirent->d_name, ".") == 0 ||
+					    strcmp(dirent->d_name, "..") == 0)
+						continue;
+					string[count] = '\0';
+					strlcat(string, "/", sizeof(string));
+					strlcat(string, dirent->d_name, sizeof(string));
+					space->go_to = NULL;
+					space->rootdir = new_root_dir(string);
+					if (space->rootdir) {
+						err = parse(space, string);
+						free(space->rootdir);
+					} else
+						err = -ENOMEM;
+					if (space->go_to) {
+						Perror(space, "unterminated GOTO '%s'", space->go_to);
+						free(space->go_to);
+					}
+					if (err)
+						break;
+				}
+				closedir(dir);
+			} else {
+				space->go_to = NULL;
+				space->rootdir = new_root_dir(string);
+				if (space->rootdir) {
+					err = parse(space, string);
+					free(space->rootdir);
+				} else
+					err = -ENOMEM;
+				if (space->go_to) {
+					Perror(space, "unterminated GOTO '%s'", space->go_to);
+					free(space->go_to);
+				}
+			}
+			space->go_to = go_to;
+			space->rootdir = rootdir;
+			space->filename = filename;
+			space->linenum = linenum;
+			if (space->quit)
+				break;
+			if (err)
+				break;
+			continue;
+		}
+		if (strncasecmp(key, "ACCESS", 6) == 0) {
+			if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+				if (value[0] == '$') {
+					strlcpy(string, value, sizeof(string));
+					apply_format(space, string, sizeof(string));
+					if (string[0] == '/')
+						goto __access1;
+				}
+				if (value[0] != '/') {
+					strlcpy(string, space->rootdir, sizeof(string));
+					strlcat(string, "/", sizeof(string));
+					strlcat(string, value, sizeof(string));
+				} else {
+					strlcpy(string, value, sizeof(string));
+				}
+				apply_format(space, string, sizeof(string));
+			      __access1:
+				count = access(string, F_OK);
+				dbg("access(%s) = %i (%s)", string, count, value);
+				if (op == KEY_OP_MATCH && count != 0)
+					break;
+				if (op == KEY_OP_NOMATCH && count == 0)
+					break;
+			} else {
+				Perror(space, "invalid ACCESS operation");
+				goto invalid;
+			}
+			continue;
+		}
+		if (strncasecmp(key, "PRINT", 5) == 0) {
+			if (op != KEY_OP_ASSIGN) {
+				Perror(space, "invalid PRINT operation");
+				goto invalid;
+			}
+			strlcpy(string, value, sizeof(string));
+			apply_format(space, string, sizeof(string));
+			fwrite(string, strlen(string), 1, stdout);
+			continue;
+		}
+		if (strncasecmp(key, "ERROR", 5) == 0) {
+			if (op != KEY_OP_ASSIGN) {
+				Perror(space, "invalid ERROR operation");
+				goto invalid;
+			}
+			strlcpy(string, value, sizeof(string));
+			apply_format(space, string, sizeof(string));
+			fwrite(string, strlen(string), 1, stderr);
+			continue;
+		}
+		if (strncasecmp(key, "EXIT", 4) == 0) {
+			if (op != KEY_OP_ASSIGN) {
+				Perror(space, "invalid EXIT operation");
+				goto invalid;
+			}
+			strlcpy(string, value, sizeof(string));
+			apply_format(space, string, sizeof(string));
+			if (strcmp(string, "return") == 0)
+				return -EJUSTRETURN;
+			space->exit_code = strtol(string, NULL, 0);
+			space->quit = 1;
+			break;
+		}
+		if (strncasecmp(key, "CONFIG{", 7) == 0) {
+			attr = get_key_attribute(space, key + 6, string, sizeof(string));
+			if (attr == NULL) {
+				Perror(space, "error parsing CONFIG attribute");
+				goto invalid;
+			}
+			strlcpy(result, value, sizeof(result));
+			apply_format(space, result, sizeof(result));
+			if (op == KEY_OP_ASSIGN) {
+				err = value_set(space, attr, result);
+				dbg("CONFIG{%s}='%s'", attr, result);
+				break;
+			} else if (op == KEY_OP_MATCH || op == KEY_OP_NOMATCH) {
+				pair = value_find(space, attr);
+				if (pair == NULL)
+					break;
+				if (!do_match(key, op, result, pair->value))
+					break;
+			} else {
+				Perror(space, "invalid CONFIG{} operation");
+				goto invalid;
+			}
+		}
+
+		Perror(space, "unknown key '%s'", key);
+	}
+	return err;
+
+invalid:
+	Perror(space, "invalid rule");
+	return -EINVAL;
+}
+
+static int parse(struct space *space, const char *filename)
+{
+	char *buf, *bufline, *line;
+	size_t bufsize, pos, count, linesize;
+	unsigned int linenum, i, j, linenum_adj;
+	int err;
+
+	dbg("start of file '%s'", filename);
+
+	if (file_map(filename, &buf, &bufsize) != 0) {
+		err = errno;
+		error("Unable to open file '%s': %s", filename, strerror(err));
+		return -err;
+	}
+
+	err = 0;	
+	pos = 0;
+	linenum = 0;
+	linesize = 128;
+	line = malloc(linesize);
+	if (line == NULL)
+		return -ENOMEM;
+	space->filename = filename;
+	while (!err && pos < bufsize && !space->quit) {
+		count = line_width(buf, bufsize, pos);
+		bufline = buf + pos;
+		pos += count + 1;
+		linenum++;
+		
+		/* skip whitespaces */
+		while (count > 0 && isspace(bufline[0])) {
+			bufline++;
+			count--;
+		}
+		if (count == 0)
+			continue;
+			
+		/* comment check */
+		if (bufline[0] == '#')
+			continue;
+		
+		if (count > linesize - 1) {
+			free(line);
+			linesize = (count + 127 + 1) & ~127;
+			if (linesize > 2048) {
+				error("file %s, line %i too long", filename, linenum);
+				err = -EINVAL;
+				break;
+			}
+			line = malloc(linesize);
+			if (line == NULL) {
+				err = -EINVAL;
+				break;
+			}
+		}
+		
+		/* skip backslash and newline from multiline rules */
+		linenum_adj = 0;
+		for (i = j = 0; i < count; i++) {
+			if (bufline[i] == '\\' && bufline[i+1] == '\n') {
+				linenum_adj++;
+				continue;
+			}
+			line[j++] = bufline[i];
+		}
+		line[j] = '\0';
+
+		dbg("read (%i) '%s'", linenum, line);
+		space->linenum = linenum;
+		err = parse_line(space, line, linesize);
+		if (err == -EJUSTRETURN) {
+			err = 0;
+			break;
+		}
+		linenum += linenum_adj;
+	}
+
+	free(line);
+	space->filename = NULL;
+	space->linenum = -1;
+	file_unmap(buf, bufsize);
+	dbg("end of file '%s'", filename);
+	return err ? err : -abs(space->exit_code);
+}
+
+int init(const char *filename, const char *cardname)
+{
+	struct space *space;
+	int err = 0, card, first;
+	
+	sysfs_init();
+	if (!cardname) {
+		first = 1;
+		card = -1;
+		while (1) {
+			if (snd_card_next(&card) < 0)
+				break;
+			if (card < 0) {
+				if (first) {
+					error("No soundcards found...");
+					return -ENODEV;
+				}
+				break;
+			}
+			first = 0;
+			err = init_space(&space, card);
+			if (err == 0) {
+				space->rootdir = new_root_dir(filename);
+				if (space->rootdir != NULL)
+					err = parse(space, filename);
+				free_space(space);
+			}
+			if (err < 0)
+				break;
+		}
+	} else {
+		card = snd_card_get_index(cardname);
+		if (card < 0) {
+			error("Cannot find soundcard '%s'...", cardname);
+			goto error;
+		}
+		memset(&space, 0, sizeof(space));
+		err = init_space(&space, card);
+		if (err == 0) {
+			space->rootdir = new_root_dir(filename);
+			if (space->rootdir  != NULL)
+				err = parse(space, filename);
+			free_space(space);
+		}
+	}
+  error:
+	sysfs_cleanup();
+	return err;
+}
diff --git a/alsactl/init_sysdeps.c b/alsactl/init_sysdeps.c
new file mode 100644
index 0000000..3aca1b4
--- /dev/null
+++ b/alsactl/init_sysdeps.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *	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 version 2 of the License.
+ * 
+ *	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.,
+ *	51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#if defined(__GLIBC__) && !(defined(__UCLIBC__) && defined(__USE_BSD))
+static size_t strlcpy(char *dst, const char *src, size_t size)
+{
+	size_t bytes = 0;
+	char *q = dst;
+	const char *p = src;
+	char ch;
+
+	while ((ch = *p++)) {
+		if (bytes+1 < size)
+			*q++ = ch;
+		bytes++;
+	}
+
+	/* If size == 0 there is no space for a final null... */
+	if (size)
+		*q = '\0';
+	return bytes;
+}
+
+static size_t strlcat(char *dst, const char *src, size_t size)
+{
+	size_t bytes = 0;
+	char *q = dst;
+	const char *p = src;
+	char ch;
+
+	while (bytes < size && *q) {
+		q++;
+		bytes++;
+	}
+	if (bytes == size)
+		return (bytes + strlen(src));
+
+	while ((ch = *p++)) {
+		if (bytes+1 < size)
+		*q++ = ch;
+		bytes++;
+	}
+
+	*q = '\0';
+	return bytes;
+}
+#endif /* __GLIBC__ */
diff --git a/alsactl/init_sysfs.c b/alsactl/init_sysfs.c
new file mode 100644
index 0000000..0cbada2
--- /dev/null
+++ b/alsactl/init_sysfs.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@vrfy.org>
+ *	              2008 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 version 2 of the License.
+ * 
+ *	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.,
+ *	51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+
+static char sysfs_path[PATH_SIZE];
+
+/* attribute value cache */
+static LIST_HEAD(attr_list);
+struct sysfs_attr {
+	struct list_head node;
+	char path[PATH_SIZE];
+	char *value;			/* points to value_local if value is cached */
+	char value_local[NAME_SIZE];
+};
+
+static int sysfs_init(void)
+{
+	const char *env;
+	char sysfs_test[PATH_SIZE];
+
+	env = getenv("SYSFS_PATH");
+	if (env) {
+		strlcpy(sysfs_path, env, sizeof(sysfs_path));
+		remove_trailing_chars(sysfs_path, '/');
+	} else
+		strlcpy(sysfs_path, "/sys", sizeof(sysfs_path));
+	dbg("sysfs_path='%s'", sysfs_path);
+
+	strlcpy(sysfs_test, sysfs_path, sizeof(sysfs_test));
+	strlcat(sysfs_test, "/kernel/uevent_helper", sizeof(sysfs_test));
+	if (access(sysfs_test, F_OK)) {
+		error("sysfs path '%s' is invalid\n", sysfs_path);
+		return -errno;
+	}
+
+	INIT_LIST_HEAD(&attr_list);
+	return 0;
+}
+
+static void sysfs_cleanup(void)
+{
+	struct sysfs_attr *attr_loop;
+	struct sysfs_attr *attr_temp;
+
+	list_for_each_entry_safe(attr_loop, attr_temp, &attr_list, node) {
+		list_del(&attr_loop->node);
+		free(attr_loop);
+	}
+}
+
+static char *sysfs_attr_get_value(const char *devpath, const char *attr_name)
+{
+	char path_full[PATH_SIZE];
+	const char *path;
+	char value[NAME_SIZE];
+	struct sysfs_attr *attr_loop;
+	struct sysfs_attr *attr;
+	struct stat statbuf;
+	int fd;
+	ssize_t size;
+	size_t sysfs_len;
+
+	dbg("open '%s'/'%s'", devpath, attr_name);
+	sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
+	path = &path_full[sysfs_len];
+	strlcat(path_full, devpath, sizeof(path_full));
+	strlcat(path_full, "/", sizeof(path_full));
+	strlcat(path_full, attr_name, sizeof(path_full));
+
+	/* look for attribute in cache */
+	list_for_each_entry(attr_loop, &attr_list, node) {
+		if (strcmp(attr_loop->path, path) == 0) {
+			dbg("found in cache '%s'", attr_loop->path);
+			return attr_loop->value;
+		}
+	}
+
+	/* store attribute in cache (also negatives are kept in cache) */
+	dbg("new uncached attribute '%s'", path_full);
+	attr = malloc(sizeof(struct sysfs_attr));
+	if (attr == NULL)
+		return NULL;
+	memset(attr, 0x00, sizeof(struct sysfs_attr));
+	strlcpy(attr->path, path, sizeof(attr->path));
+	dbg("add to cache '%s'", path_full);
+	list_add(&attr->node, &attr_list);
+
+	if (lstat(path_full, &statbuf) != 0) {
+		dbg("stat '%s' failed: %s", path_full, strerror(errno));
+		goto out;
+	}
+
+	if (S_ISLNK(statbuf.st_mode)) {
+		/* links return the last element of the target path */
+		char link_target[PATH_SIZE];
+		int len;
+		const char *pos;
+
+		len = readlink(path_full, link_target, sizeof(link_target));
+		if (len > 0) {
+			link_target[len] = '\0';
+			pos = strrchr(link_target, '/');
+			if (pos != NULL) {
+				dbg("cache '%s' with link value '%s'", path_full, pos+1);
+				strlcpy(attr->value_local, &pos[1], sizeof(attr->value_local));
+				attr->value = attr->value_local;
+			}
+		}
+		goto out;
+	}
+
+	/* skip directories */
+	if (S_ISDIR(statbuf.st_mode))
+		goto out;
+
+	/* skip non-readable files */
+	if ((statbuf.st_mode & S_IRUSR) == 0)
+		goto out;
+
+	/* read attribute value */
+	fd = open(path_full, O_RDONLY);
+	if (fd < 0) {
+		dbg("attribute '%s' does not exist", path_full);
+		goto out;
+	}
+	size = read(fd, value, sizeof(value));
+	close(fd);
+	if (size < 0)
+		goto out;
+	if (size == sizeof(value))
+		goto out;
+
+	/* got a valid value, store and return it */
+	value[size] = '\0';
+	remove_trailing_chars(value, '\n');
+	dbg("cache '%s' with attribute value '%s'", path_full, value);
+	strlcpy(attr->value_local, value, sizeof(attr->value_local));
+	attr->value = attr->value_local;
+
+out:
+	return attr->value;
+}
diff --git a/alsactl/init_utils_run.c b/alsactl/init_utils_run.c
new file mode 100644
index 0000000..dde490b
--- /dev/null
+++ b/alsactl/init_utils_run.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *	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 version 2 of the License.
+ * 
+ *	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.,
+ *	51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#define MY_MAX(a,b) ((a) > (b) ? (a) : (b))
+
+#define READ_END	0
+#define WRITE_END	1
+
+static
+int run_program1(struct space *space,
+	         const char *command0, char *result,
+		 size_t ressize, size_t *reslen, int log);
+
+static
+int run_program0(struct space *space,
+	         const char *command0, char *result,
+		 size_t ressize, size_t *reslen, int log)
+{
+	int retval = 0;
+	int status;
+	int outpipe[2] = {-1, -1};
+	int errpipe[2] = {-1, -1};
+	pid_t pid;
+	char arg[PATH_SIZE];
+	char program[PATH_SIZE];
+	char *argv[(sizeof(arg) / 2) + 1];
+	int devnull;
+	int i;
+
+	/* build argv from comand */
+	strlcpy(arg, command0, sizeof(arg));
+	i = 0;
+	if (strchr(arg, ' ') != NULL) {
+		char *pos = arg;
+
+		while (pos != NULL) {
+			if (pos[0] == '\'') {
+				/* don't separate if in apostrophes */
+				pos++;
+				argv[i] = strsep(&pos, "\'");
+				while (pos != NULL && pos[0] == ' ')
+					pos++;
+			} else {
+				argv[i] = strsep(&pos, " ");
+			}
+			dbg("arg[%i] '%s'", i, argv[i]);
+			i++;
+		}
+		argv[i] = NULL;
+	} else {
+		argv[0] = arg;
+		argv[1] = NULL;
+	}
+	info("'%s'", command0);
+
+	/* prepare pipes from child to parent */
+	if (result || log) {
+		if (pipe(outpipe) != 0) {
+			Perror(space, "pipe failed: %s", strerror(errno));
+			return -1;
+		}
+	}
+	if (log) {
+		if (pipe(errpipe) != 0) {
+			Perror(space, "pipe failed: %s", strerror(errno));
+			return -1;
+		}
+	}
+
+	/* allow programs in /lib/alsa called without the path */
+	if (strchr(argv[0], '/') == NULL) {
+		strlcpy(program, "/lib/alsa/", sizeof(program));
+		strlcat(program, argv[0], sizeof(program));
+		argv[0] = program;
+	}
+
+	pid = fork();
+	switch(pid) {
+	case 0:
+		/* child closes parent ends of pipes */
+		if (outpipe[READ_END] > 0)
+			close(outpipe[READ_END]);
+		if (errpipe[READ_END] > 0)
+			close(errpipe[READ_END]);
+
+		/* discard child output or connect to pipe */
+		devnull = open("/dev/null", O_RDWR);
+		if (devnull > 0) {
+			dup2(devnull, STDIN_FILENO);
+			if (outpipe[WRITE_END] < 0)
+				dup2(devnull, STDOUT_FILENO);
+			if (errpipe[WRITE_END] < 0)
+				dup2(devnull, STDERR_FILENO);
+			close(devnull);
+		} else
+			Perror(space, "open /dev/null failed: %s", strerror(errno));
+		if (outpipe[WRITE_END] > 0) {
+			dup2(outpipe[WRITE_END], STDOUT_FILENO);
+			close(outpipe[WRITE_END]);
+		}
+		if (errpipe[WRITE_END] > 0) {
+			dup2(errpipe[WRITE_END], STDERR_FILENO);
+			close(errpipe[WRITE_END]);
+		}
+		execv(argv[0], argv);
+
+		/* we should never reach this */
+		Perror(space, "exec of program '%s' failed", argv[0]);
+		_exit(1);
+	case -1:
+		Perror(space, "fork of '%s' failed: %s", argv[0], strerror(errno));
+		return -1;
+	default:
+		/* read from child if requested */
+		if (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
+			ssize_t count;
+			size_t respos = 0;
+
+			/* parent closes child ends of pipes */
+			if (outpipe[WRITE_END] > 0)
+				close(outpipe[WRITE_END]);
+			if (errpipe[WRITE_END] > 0)
+				close(errpipe[WRITE_END]);
+
+			/* read child output */
+			while (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
+				int fdcount;
+				fd_set readfds;
+
+				FD_ZERO(&readfds);
+				if (outpipe[READ_END] > 0)
+					FD_SET(outpipe[READ_END], &readfds);
+				if (errpipe[READ_END] > 0)
+					FD_SET(errpipe[READ_END], &readfds);
+				fdcount = select(MY_MAX(outpipe[READ_END], errpipe[READ_END])+1, &readfds, NULL, NULL, NULL);
+				if (fdcount < 0) {
+					if (errno == EINTR)
+						continue;
+					retval = -1;
+					break;
+				}
+
+				/* get stdout */
+				if (outpipe[READ_END] > 0 && FD_ISSET(outpipe[READ_END], &readfds)) {
+					char inbuf[1024];
+					char *pos;
+					char *line;
+
+					count = read(outpipe[READ_END], inbuf, sizeof(inbuf)-1);
+					if (count <= 0) {
+						close(outpipe[READ_END]);
+						outpipe[READ_END] = -1;
+						if (count < 0) {
+							Perror(space, "stdin read failed: %s", strerror(errno));
+							retval = -1;
+						}
+						continue;
+					}
+					inbuf[count] = '\0';
+
+					/* store result for rule processing */
+					if (result) {
+						if (respos + count < ressize) {
+							memcpy(&result[respos], inbuf, count);
+							respos += count;
+						} else {
+							Perror(space, "ressize %ld too short", (long)ressize);
+							retval = -1;
+						}
+					}
+					pos = inbuf;
+					while ((line = strsep(&pos, "\n")))
+						if (pos || line[0] != '\0')
+							info("'%s' (stdout) '%s'", argv[0], line);
+				}
+
+				/* get stderr */
+				if (errpipe[READ_END] > 0 && FD_ISSET(errpipe[READ_END], &readfds)) {
+					char errbuf[1024];
+					char *pos;
+					char *line;
+
+					count = read(errpipe[READ_END], errbuf, sizeof(errbuf)-1);
+					if (count <= 0) {
+						close(errpipe[READ_END]);
+						errpipe[READ_END] = -1;
+						if (count < 0)
+							Perror(space, "stderr read failed: %s", strerror(errno));
+						continue;
+					}
+					errbuf[count] = '\0';
+					pos = errbuf;
+					while ((line = strsep(&pos, "\n")))
+						if (pos || line[0] != '\0')
+							info("'%s' (stderr) '%s'", argv[0], line);
+				}
+			}
+			if (outpipe[READ_END] > 0)
+				close(outpipe[READ_END]);
+			if (errpipe[READ_END] > 0)
+				close(errpipe[READ_END]);
+
+			/* return the childs stdout string */
+			if (result) {
+				result[respos] = '\0';
+				dbg("result='%s'", result);
+				if (reslen)
+					*reslen = respos;
+			}
+		}
+		waitpid(pid, &status, 0);
+		if (WIFEXITED(status)) {
+			info("'%s' returned with status %i", argv[0], WEXITSTATUS(status));
+			if (WEXITSTATUS(status) != 0)
+				retval = -1;
+		} else {
+			Perror(space, "'%s' abnormal exit", argv[0]);
+			retval = -1;
+		}
+	}
+
+	return retval;
+}
+
+static
+int run_program(struct space *space, const char *command0, char *result,
+		size_t ressize, size_t *reslen, int log)
+{
+	if (command0[0] == '_' && command0[1] == '_')
+		return run_program1(space, command0, result, ressize, reslen, log);
+	return run_program0(space, command0, result, ressize, reslen, log);
+}
diff --git a/alsactl/init_utils_string.c b/alsactl/init_utils_string.c
new file mode 100644
index 0000000..01ea800
--- /dev/null
+++ b/alsactl/init_utils_string.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ *	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 version 2 of the License.
+ * 
+ *	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.,
+ *	51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+
+static void remove_trailing_chars(char *path, char c)
+{
+	size_t len;
+
+	len = strlen(path);
+	while (len > 0 && path[len-1] == c)
+		path[--len] = '\0';
+}
+
+/* count of characters used to encode one unicode char */
+static int utf8_encoded_expected_len(const char *str)
+{
+	unsigned char c = (unsigned char)str[0];
+
+	if (c < 0x80)
+		return 1;
+	if ((c & 0xe0) == 0xc0)
+		return 2;
+	if ((c & 0xf0) == 0xe0)
+		return 3;
+	if ((c & 0xf8) == 0xf0)
+		return 4;
+	if ((c & 0xfc) == 0xf8)
+		return 5;
+	if ((c & 0xfe) == 0xfc)
+		return 6;
+	return 0;
+}
+
+/* decode one unicode char */
+static int utf8_encoded_to_unichar(const char *str)
+{
+	int unichar;
+	int len;
+	int i;
+
+	len = utf8_encoded_expected_len(str);
+	switch (len) {
+	case 1:
+		return (int)str[0];
+	case 2:
+		unichar = str[0] & 0x1f;
+		break;
+	case 3:
+		unichar = (int)str[0] & 0x0f;
+		break;
+	case 4:
+		unichar = (int)str[0] & 0x07;
+		break;
+	case 5:
+		unichar = (int)str[0] & 0x03;
+		break;
+	case 6:
+		unichar = (int)str[0] & 0x01;
+		break;
+	default:
+		return -1;
+	}
+
+	for (i = 1; i < len; i++) {
+		if (((int)str[i] & 0xc0) != 0x80)
+			return -1;
+		unichar <<= 6;
+		unichar |= (int)str[i] & 0x3f;
+	}
+
+	return unichar;
+}
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(int unichar)
+{
+	if (unichar < 0x80)
+		return 1;
+	if (unichar < 0x800)
+		return 2;
+	if (unichar < 0x10000)
+		return 3;
+	if (unichar < 0x200000)
+		return 4;
+	if (unichar < 0x4000000)
+		return 5;
+	return 6;
+}
+
+/* check if unicode char has a valid numeric range */
+static int utf8_unichar_valid_range(int unichar)
+{
+	if (unichar > 0x10ffff)
+		return 0;
+	if ((unichar & 0xfffff800) == 0xd800)
+		return 0;
+	if ((unichar > 0xfdcf) && (unichar < 0xfdf0))
+		return 0;
+	if ((unichar & 0xffff) == 0xffff)
+		return 0;
+	return 1;
+}
+
+/* validate one encoded unicode char and return its length */
+static int utf8_encoded_valid_unichar(const char *str)
+{
+	int len;
+	int unichar;
+	int i;
+
+	len = utf8_encoded_expected_len(str);
+	if (len == 0)
+		return -1;
+
+	/* ascii is valid */
+	if (len == 1)
+		return 1;
+
+	/* check if expected encoded chars are available */
+	for (i = 0; i < len; i++)
+		if ((str[i] & 0x80) != 0x80)
+			return -1;
+
+	unichar = utf8_encoded_to_unichar(str);
+
+	/* check if encoded length matches encoded value */
+	if (utf8_unichar_to_encoded_len(unichar) != len)
+		return -1;
+
+	/* check if value has valid range */
+	if (!utf8_unichar_valid_range(unichar))
+		return -1;
+
+	return len;
+}
+
+/* replace everything but whitelisted plain ascii and valid utf8 */
+static int replace_untrusted_chars(char *str)
+{
+	size_t i = 0;
+	int replaced = 0;
+
+	while (str[i] != '\0') {
+		int len;
+
+		/* valid printable ascii char */
+		if ((str[i] >= '0' && str[i] <= '9') ||
+		    (str[i] >= 'A' && str[i] <= 'Z') ||
+		    (str[i] >= 'a' && str[i] <= 'z') ||
+		    strchr(" #$%+-./:=?@_,", str[i])) {
+			i++;
+			continue;
+		}
+		/* valid utf8 is accepted */
+		len = utf8_encoded_valid_unichar(&str[i]);
+		if (len > 1) {
+			i += len;
+			continue;
+		}
+
+		/* everything else is garbage */
+		str[i] = '_';
+		i++;
+		replaced++;
+	}
+
+	return replaced;
+}
diff --git a/alsactl/list.h b/alsactl/list.h
new file mode 100644
index 0000000..8626630
--- /dev/null
+++ b/alsactl/list.h
@@ -0,0 +1,289 @@
+/*
+ * Copied from the Linux kernel source tree, version 2.6.0-test1.
+ *
+ * Licensed under the GPL v2 as per the whole kernel source tree.
+ *
+ */
+
+#ifndef _LIST_H
+#define _LIST_H
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr:	the pointer to the member.
+ * @type:	the type of the container struct this is embedded in.
+ * @member:	the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({			\
+	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
+	(type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1  ((void *) 0x00100100)
+#define LIST_POISON2  ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+	struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries. 
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+			      struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	entry->next = LIST_POISON1;
+	entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	INIT_LIST_HEAD(entry); 
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+	__list_del(list->prev, list->next);
+	list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+				  struct list_head *head)
+{
+	__list_del(list->prev, list->next);
+	list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(struct list_head *head)
+{
+	return head->next == head;
+}
+
+static inline void __list_splice(struct list_head *list,
+				 struct list_head *head)
+{
+	struct list_head *first = list->next;
+	struct list_head *last = list->prev;
+	struct list_head *at = head->next;
+
+	first->prev = head;
+	head->next = first;
+
+	last->next = at;
+	at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+	if (!list_empty(list))
+		__list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+				    struct list_head *head)
+{
+	if (!list_empty(list)) {
+		__list_splice(list, head);
+		INIT_LIST_HEAD(list);
+	}
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:	the &struct list_head pointer.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+	container_of(ptr, type, member)
+
+/**
+ * list_for_each	-	iterate over a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); \
+		pos = pos->next)
+
+/**
+ * __list_for_each	-	iterate over a list
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+	for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev	-	iterate over a list backwards
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @head:	the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+	for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe	-	iterate over a list safe against removal of list entry
+ * @pos:	the &struct list_head to use as a loop counter.
+ * @n:		another &struct list_head to use as temporary storage
+ * @head:	the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+	for (pos = (head)->next, n = pos->next; pos != (head); \
+		pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry	-	iterate over list of given type
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_entry((head)->next, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos:	the type * to use as a loop counter.
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member)			\
+	for (pos = list_entry((head)->prev, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos:	the type * to use as a loop counter.
+ * @n:		another type * to use as temporary storage
+ * @head:	the head for your list.
+ * @member:	the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)			\
+	for (pos = list_entry((head)->next, typeof(*pos), member),	\
+		n = list_entry(pos->member.next, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+#endif /* _LIST_H */
diff --git a/alsactl/state.c b/alsactl/state.c
new file mode 100644
index 0000000..a8b5bd3
--- /dev/null
+++ b/alsactl/state.c
@@ -0,0 +1,1731 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program
+ *  Copyright (c) by Abramo Bagnara <abramo@alsa-project.org>
+ *                   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"
+#include "version.h"
+#include <getopt.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <alsa/asoundlib.h>
+#include "alsactl.h"
+
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a)[0])
+
+
+static char *id_str(snd_ctl_elem_id_t *id)
+{
+	static char str[128];
+	assert(id);
+	sprintf(str, "%i,%i,%i,%s,%i", 
+		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;
+}
+
+static char *num_str(long n)
+{
+	static char str[32];
+	sprintf(str, "%ld", n);
+	return str;
+}
+
+static int snd_config_integer_add(snd_config_t *father, char *id, long integer)
+{
+	int err;
+	snd_config_t *leaf;
+	err = snd_config_imake_integer(&leaf, id, integer);
+	if (err < 0)
+		return err;
+	err = snd_config_add(father, leaf);
+	if (err < 0) {
+		snd_config_delete(leaf);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_config_integer64_add(snd_config_t *father, char *id, long long integer)
+{
+	int err;
+	snd_config_t *leaf;
+	err = snd_config_imake_integer64(&leaf, id, integer);
+	if (err < 0)
+		return err;
+	err = snd_config_add(father, leaf);
+	if (err < 0) {
+		snd_config_delete(leaf);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_config_string_add(snd_config_t *father, const char *id, const char *string)
+{
+	int err;
+	snd_config_t *leaf;
+	err = snd_config_imake_string(&leaf, id, string);
+	if (err < 0)
+		return err;
+	err = snd_config_add(father, leaf);
+	if (err < 0) {
+		snd_config_delete(leaf);
+		return err;
+	}
+	return 0;
+}
+
+static int snd_config_compound_add(snd_config_t *father, const char *id, int join,
+				   snd_config_t **node)
+{
+	int err;
+	snd_config_t *leaf;
+	err = snd_config_make_compound(&leaf, id, join);
+	if (err < 0)
+		return err;
+	err = snd_config_add(father, leaf);
+	if (err < 0) {
+		snd_config_delete(leaf);
+		return err;
+	}
+	*node = leaf;
+	return 0;
+}
+
+#define MAX_USER_TLV_SIZE	64
+
+static char *tlv_to_str(unsigned int *tlv)
+{
+	int i, len = tlv[1] / 4 + 2;
+	char *s, *p;
+
+	if (len >= MAX_USER_TLV_SIZE)
+		return NULL;
+	s = malloc(len * 8 + 1);
+	if (! s)
+		return NULL;
+	p = s;
+	for (i = 0; i < len; i++) {
+		sprintf(p, "%08x", tlv[i]);
+		p += 8;
+	}
+	return s;
+}
+
+static unsigned int *str_to_tlv(const char *s)
+{
+	int i, j, c, len;
+	unsigned int *tlv;
+			
+	len = strlen(s);
+	if (len % 8) /* aligned to 4 bytes (= 8 letters) */
+		return NULL;
+	len /= 8;
+	if (len > MAX_USER_TLV_SIZE)
+		return NULL;
+	tlv = malloc(sizeof(int) * len);
+	if (! tlv)
+		return NULL;
+	for (i = 0; i < len; i++) {
+		tlv[i] = 0;
+		for (j = 0; j < 8; j++) {
+			if ((c = hextodigit(*s++)) < 0) {
+				free(tlv);
+				return NULL;
+			}
+			tlv[i] = (tlv[i] << 4) | c;
+		}
+	}
+	return tlv;
+}
+
+/*
+ * add the TLV string, dB ranges, and dB values to comment fields
+ */
+static int add_tlv_comments(snd_ctl_t *handle, snd_ctl_elem_id_t *id,
+			    snd_ctl_elem_info_t *info, snd_ctl_elem_value_t *ctl,
+			    snd_config_t *comment)
+{
+	unsigned int tlv[MAX_USER_TLV_SIZE];
+	unsigned int *db;
+	long rangemin, rangemax;
+	long dbmin, dbmax, dbgain;
+	snd_config_t *value;
+	unsigned int i, count;
+	int err;
+
+	if (snd_ctl_elem_tlv_read(handle, id, tlv, sizeof(tlv)) < 0)
+		return 0; /* ignore error */
+
+	if (snd_ctl_elem_info_is_tlv_writable(info)) {
+		char *s = tlv_to_str(tlv);
+		if (s) {
+			err = snd_config_string_add(comment, "tlv", s);
+			if (err < 0) {
+				error("snd_config_string_add: %s", snd_strerror(err));
+				return err;
+			}
+			free(s);
+		}
+	}
+
+	err = snd_tlv_parse_dB_info(tlv, sizeof(tlv), &db);
+	if (err <= 0)
+		return 0;
+
+	rangemin = snd_ctl_elem_info_get_min(info);
+	rangemax = snd_ctl_elem_info_get_max(info);
+	snd_tlv_get_dB_range(db, rangemin, rangemax, &dbmin, &dbmax);
+	if (err < 0)
+		return err;
+	snd_config_integer_add(comment, "dbmin", dbmin);
+	snd_config_integer_add(comment, "dbmax", dbmax);
+
+	if (snd_ctl_elem_info_get_type(info) == SND_CTL_ELEM_TYPE_INTEGER) {
+		err = snd_config_compound_add(comment, "dbvalue", 1, &value);
+		if (err < 0) {
+			error("snd_config_compound_add: %s", snd_strerror(err));
+			return err;
+		}
+		count = snd_ctl_elem_info_get_count(info);
+		for (i = 0; i < count; i++) {
+			err = snd_tlv_convert_to_dB(db, rangemin, rangemax,
+					snd_ctl_elem_value_get_integer(ctl, i), &dbgain);
+			if (err < 0) {
+				error("snd_tlv_convert_to_dB: %s", snd_strerror(err));
+				return err;
+			}
+			err = snd_config_integer_add(value, num_str(i), dbgain);
+			if (err < 0) {
+				error("snd_config_integer_add: %s", snd_strerror(err));
+				return err;
+			}
+		}
+	}
+	return 0;
+}
+
+static int get_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id, snd_config_t *top)
+{
+	snd_ctl_elem_value_t *ctl;
+	snd_ctl_elem_info_t *info;
+	snd_config_t *control, *comment, *item, *value;
+	const char *s;
+	char buf[256];
+	unsigned int idx;
+	int err;
+	unsigned int device, subdevice, index;
+	const char *name;
+	snd_ctl_elem_type_t type;
+	unsigned int count;
+	snd_ctl_elem_value_alloca(&ctl);
+	snd_ctl_elem_info_alloca(&info);
+	snd_ctl_elem_info_set_id(info, id);
+	err = snd_ctl_elem_info(handle, info);
+	if (err < 0) {
+		error("Cannot read control info '%s': %s", id_str(id), snd_strerror(err));
+		return err;
+	}
+
+	if (!snd_ctl_elem_info_is_readable(info))
+		return 0;
+	snd_ctl_elem_value_set_id(ctl, id);
+	err = snd_ctl_elem_read(handle, ctl);
+	if (err < 0) {
+		error("Cannot read control '%s': %s", id_str(id), snd_strerror(err));
+		return err;
+	}
+
+	err = snd_config_compound_add(top, num_str(snd_ctl_elem_info_get_numid(info)), 0, &control);
+	if (err < 0) {
+		error("snd_config_compound_add: %s", snd_strerror(err));
+		return err;
+	}
+	err = snd_config_make_compound(&comment, "comment", 0);
+	if (err < 0) {
+		error("snd_config_make_compound: %s", snd_strerror(err));
+		return err;
+	}
+
+	buf[0] = '\0';
+	buf[1] = '\0';
+	if (snd_ctl_elem_info_is_readable(info))
+		strcat(buf, " read");
+	if (snd_ctl_elem_info_is_writable(info))
+		strcat(buf, " write");
+	if (snd_ctl_elem_info_is_inactive(info))
+		strcat(buf, " inactive");
+	if (snd_ctl_elem_info_is_volatile(info))
+		strcat(buf, " volatile");
+	if (snd_ctl_elem_info_is_locked(info))
+		strcat(buf, " locked");
+	if (snd_ctl_elem_info_is_user(info))
+		strcat(buf, " user");
+	err = snd_config_string_add(comment, "access", buf + 1);
+	if (err < 0) {
+		error("snd_config_string_add: %s", snd_strerror(err));
+		return err;
+	}
+
+	type = snd_ctl_elem_info_get_type(info);
+	device = snd_ctl_elem_info_get_device(info);
+	subdevice = snd_ctl_elem_info_get_subdevice(info);
+	index = snd_ctl_elem_info_get_index(info);
+	name = snd_ctl_elem_info_get_name(info);
+	count = snd_ctl_elem_info_get_count(info);
+	s = snd_ctl_elem_type_name(type);
+	err = snd_config_string_add(comment, "type", s);
+	if (err < 0) {
+		error("snd_config_string_add: %s", snd_strerror(err));
+		return err;
+	}
+	err = snd_config_integer_add(comment, "count", count);
+	if (err < 0) {
+		error("snd_config_integer_add: %s", snd_strerror(err));
+		return err;
+	}
+
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+	{
+		long min = snd_ctl_elem_info_get_min(info);
+		long max = snd_ctl_elem_info_get_max(info);
+		long step = snd_ctl_elem_info_get_step(info);
+		if (step)
+			sprintf(buf, "%li - %li (step %li)", min, max, step);
+		else
+			sprintf(buf, "%li - %li", min, max);
+		err = snd_config_string_add(comment, "range", buf);
+		if (err < 0) {
+			error("snd_config_string_add: %s", snd_strerror(err));
+			return err;
+		}
+		if (snd_ctl_elem_info_is_tlv_readable(info)) {
+			err = add_tlv_comments(handle, id, info, ctl, comment);
+			if (err < 0)
+				return err;
+		}
+		break;
+	}
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+	{
+		long long min = snd_ctl_elem_info_get_min64(info);
+		long long max = snd_ctl_elem_info_get_max64(info);
+		long long step = snd_ctl_elem_info_get_step64(info);
+		if (step)
+			sprintf(buf, "%Li - %Li (step %Li)", min, max, step);
+		else
+			sprintf(buf, "%Li - %Li", min, max);
+		err = snd_config_string_add(comment, "range", buf);
+		if (err < 0) {
+			error("snd_config_string_add: %s", snd_strerror(err));
+			return err;
+		}
+		break;
+	}
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+	{
+		unsigned int items;
+		err = snd_config_compound_add(comment, "item", 1, &item);
+		if (err < 0) {
+			error("snd_config_compound_add: %s", snd_strerror(err));
+			return err;
+		}
+		items = snd_ctl_elem_info_get_items(info);
+		for (idx = 0; idx < items; idx++) {
+			snd_ctl_elem_info_set_item(info, idx);
+			err = snd_ctl_elem_info(handle, info);
+			if (err < 0) {
+				error("snd_ctl_card_info: %s", snd_strerror(err));
+				return err;
+			}
+			err = snd_config_string_add(item, num_str(idx), snd_ctl_elem_info_get_item_name(info));
+			if (err < 0) {
+				error("snd_config_string_add: %s", snd_strerror(err));
+				return err;
+			}
+		}
+		break;
+	}
+	default:
+		break;
+	}
+	s = snd_ctl_elem_iface_name(snd_ctl_elem_info_get_interface(info));
+	err = snd_config_string_add(control, "iface", s);
+	if (err < 0) {
+		error("snd_config_string_add: %s", snd_strerror(err));
+		return err;
+	}
+	if (device != 0) {
+		err = snd_config_integer_add(control, "device", device);
+		if (err < 0) {
+			error("snd_config_integer_add: %s", snd_strerror(err));
+			return err;
+		}
+	}
+	if (subdevice != 0) {
+		err = snd_config_integer_add(control, "subdevice", subdevice);
+		if (err < 0) {
+			error("snd_config_integer_add: %s", snd_strerror(err));
+			return err;
+		}
+	}
+	err = snd_config_string_add(control, "name", name);
+	if (err < 0) {
+		error("snd_config_string_add: %s", snd_strerror(err));
+		return err;
+	}
+	if (index != 0) {
+		err = snd_config_integer_add(control, "index", index);
+		if (err < 0) {
+			error("snd_config_integer_add: %s", snd_strerror(err));
+			return err;
+		}
+	}
+
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BYTES:
+	case SND_CTL_ELEM_TYPE_IEC958:
+	{
+		size_t size = type == SND_CTL_ELEM_TYPE_BYTES ?
+			count : sizeof(snd_aes_iec958_t);
+		char buf[size * 2 + 1];
+		char *p = buf;
+		char *hex = "0123456789abcdef";
+		const unsigned char *bytes = 
+		  (const unsigned char *)snd_ctl_elem_value_get_bytes(ctl);
+		for (idx = 0; idx < size; idx++) {
+			int v = bytes[idx];
+			*p++ = hex[v >> 4];
+			*p++ = hex[v & 0x0f];
+		}
+		*p = '\0';
+		err = snd_config_string_add(control, "value", buf);
+		if (err < 0) {
+			error("snd_config_string_add: %s", snd_strerror(err));
+			return err;
+		}
+		goto finish;
+	}
+	default:
+		break;
+	}
+
+	if (count == 1) {
+		switch (type) {
+		case SND_CTL_ELEM_TYPE_BOOLEAN:
+			err = snd_config_string_add(control, "value", snd_ctl_elem_value_get_boolean(ctl, 0) ? "true" : "false");
+			if (err < 0) {
+				error("snd_config_string_add: %s", snd_strerror(err));
+				return err;
+			}
+			goto finish;
+		case SND_CTL_ELEM_TYPE_INTEGER:
+			err = snd_config_integer_add(control, "value", snd_ctl_elem_value_get_integer(ctl, 0));
+			if (err < 0) {
+				error("snd_config_integer_add: %s", snd_strerror(err));
+				return err;
+			}
+			goto finish;
+		case SND_CTL_ELEM_TYPE_INTEGER64:
+			err = snd_config_integer64_add(control, "value", snd_ctl_elem_value_get_integer64(ctl, 0));
+			if (err < 0) {
+				error("snd_config_integer64_add: %s", snd_strerror(err));
+				return err;
+			}
+			goto finish;
+		case SND_CTL_ELEM_TYPE_ENUMERATED:
+		{
+			unsigned int v = snd_ctl_elem_value_get_enumerated(ctl, 0);
+			snd_config_t *c;
+			err = snd_config_search(item, num_str(v), &c);
+			if (err == 0) {
+				err = snd_config_get_string(c, &s);
+				assert(err == 0);
+				err = snd_config_string_add(control, "value", s);
+			} else {
+				err = snd_config_integer_add(control, "value", v);
+			}
+			if (err < 0)
+				error("snd_config add: %s", snd_strerror(err));
+			goto finish;
+		}
+		default:
+			error("Unknown control type: %d\n", type);
+			return -EINVAL;
+		}
+	}
+
+	err = snd_config_compound_add(control, "value", 1, &value);
+	if (err < 0) {
+		error("snd_config_compound_add: %s", snd_strerror(err));
+		return err;
+	}
+
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		for (idx = 0; idx < count; idx++) {
+			err = snd_config_string_add(value, num_str(idx), snd_ctl_elem_value_get_boolean(ctl, idx) ? "true" : "false");
+			if (err < 0) {
+				error("snd_config_string_add: %s", snd_strerror(err));
+				return err;
+			}
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		for (idx = 0; idx < count; idx++) {
+			err = snd_config_integer_add(value, num_str(idx), snd_ctl_elem_value_get_integer(ctl, idx));
+			if (err < 0) {
+				error("snd_config_integer_add: %s", snd_strerror(err));
+				return err;
+			}
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		for (idx = 0; idx < count; idx++) {
+			err = snd_config_integer64_add(value, num_str(idx), snd_ctl_elem_value_get_integer64(ctl, idx));
+			if (err < 0) {
+				error("snd_config_integer64_add: %s", snd_strerror(err));
+				return err;
+			}
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		for (idx = 0; idx < count; idx++) {
+			unsigned int v = snd_ctl_elem_value_get_enumerated(ctl, idx);
+			snd_config_t *c;
+			err = snd_config_search(item, num_str(v), &c);
+			if (err == 0) {
+				err = snd_config_get_string(c, &s);
+				assert(err == 0);
+				err = snd_config_string_add(value, num_str(idx), s);
+			} else {
+				err = snd_config_integer_add(value, num_str(idx), v);
+			}
+			if (err < 0) {
+				error("snd_config add: %s", snd_strerror(err));
+				return err;
+			}
+		}
+		break;
+	default:
+		error("Unknown control type: %d\n", type);
+		return -EINVAL;
+	}
+	
+finish:
+	err = snd_config_add(control, comment);
+	if (err < 0) {
+		error("snd_config_add: %s", snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+	
+static int get_controls(int cardno, snd_config_t *top)
+{
+	snd_ctl_t *handle;
+	snd_ctl_card_info_t *info;
+	snd_config_t *state, *card, *control;
+	snd_ctl_elem_list_t *list;
+	snd_ctl_elem_id_t *elem_id;
+	unsigned int idx;
+	int err;
+	char name[32];
+	unsigned int count;
+	const char *id;
+	snd_ctl_card_info_alloca(&info);
+	snd_ctl_elem_list_alloca(&list);
+	snd_ctl_elem_id_alloca(&elem_id);
+
+	sprintf(name, "hw:%d", cardno);
+	err = snd_ctl_open(&handle, name, SND_CTL_READONLY);
+	if (err < 0) {
+		error("snd_ctl_open error: %s", snd_strerror(err));
+		return err;
+	}
+	err = snd_ctl_card_info(handle, info);
+	if (err < 0) {
+		error("snd_ctl_card_info error: %s", snd_strerror(err));
+		goto _close;
+	}
+	id = snd_ctl_card_info_get_id(info);
+	err = snd_config_search(top, "state", &state);
+	if (err == 0 &&
+	    snd_config_get_type(state) != SND_CONFIG_TYPE_COMPOUND) {
+		error("config state node is not a compound");
+		err = -EINVAL;
+		goto _close;
+	}
+	if (err < 0) {
+		err = snd_config_compound_add(top, "state", 1, &state);
+		if (err < 0) {
+			error("snd_config_compound_add: %s", snd_strerror(err));
+			goto _close;
+		}
+	}
+	err = snd_config_search(state, id, &card);
+	if (err == 0 &&
+	    snd_config_get_type(card) != SND_CONFIG_TYPE_COMPOUND) {
+		error("config state.%s node is not a compound", id);
+		err = -EINVAL;
+		goto _close;
+	}
+	if (err < 0) {
+		err = snd_config_compound_add(state, id, 0, &card);
+		if (err < 0) {
+			error("snd_config_compound_add: %s", snd_strerror(err));
+			goto _close;
+		}
+	}
+	err = snd_config_search(card, "control", &control);
+	if (err == 0) {
+		err = snd_config_delete(control);
+		if (err < 0) {
+			error("snd_config_delete: %s", snd_strerror(err));
+			goto _close;
+		}
+	}
+	err = snd_ctl_elem_list(handle, list);
+	if (err < 0) {
+		error("Cannot determine controls: %s", snd_strerror(err));
+		goto _close;
+	}
+	count = snd_ctl_elem_list_get_count(list);
+	err = snd_config_compound_add(card, "control", count > 0, &control);
+	if (err < 0) {
+		error("snd_config_compound_add: %s", snd_strerror(err));
+		goto _close;
+	}
+	if (count == 0) {
+		err = 0;
+		goto _close;
+	}
+	snd_ctl_elem_list_set_offset(list, 0);
+	if (snd_ctl_elem_list_alloc_space(list, count) < 0) {
+		error("No enough memory...");
+		goto _close;
+	}
+	if ((err = snd_ctl_elem_list(handle, list)) < 0) {
+		error("Cannot determine controls (2): %s", snd_strerror(err));
+		goto _free;
+	}
+	for (idx = 0; idx < count; ++idx) {
+		snd_ctl_elem_list_get_id(list, idx, elem_id);
+		err = get_control(handle, elem_id, control);
+		if (err < 0)
+			goto _free;
+	}		
+		
+	err = 0;
+ _free:
+	snd_ctl_elem_list_free_space(list);
+ _close:
+	snd_ctl_close(handle);
+	return err;
+}
+
+static long config_iface(snd_config_t *n)
+{
+	long i;
+	long long li;
+	snd_ctl_elem_iface_t idx; 
+	const char *str;
+	switch (snd_config_get_type(n)) {
+	case SND_CONFIG_TYPE_INTEGER:
+		snd_config_get_integer(n, &i);
+		return i;
+	case SND_CONFIG_TYPE_INTEGER64:
+		snd_config_get_integer64(n, &li);
+		return li;
+	case SND_CONFIG_TYPE_STRING:
+		snd_config_get_string(n, &str);
+		break;
+	default:
+		return -1;
+	}
+	for (idx = 0; idx <= SND_CTL_ELEM_IFACE_LAST; idx++) {
+		if (strcasecmp(snd_ctl_elem_iface_name(idx), str) == 0)
+			return idx;
+	}
+	return -1;
+}
+
+static int config_bool(snd_config_t *n, int doit)
+{
+	const char *str;
+	long val;
+	long long lval;
+
+	switch (snd_config_get_type(n)) {
+	case SND_CONFIG_TYPE_INTEGER:
+		snd_config_get_integer(n, &val);
+		if (val < 0 || val > 1)
+			return -1;
+		return val;
+	case SND_CONFIG_TYPE_INTEGER64:
+		snd_config_get_integer64(n, &lval);
+		if (lval < 0 || lval > 1)
+			return -1;
+		return (int) lval;
+	case SND_CONFIG_TYPE_STRING:
+		snd_config_get_string(n, &str);
+		break;
+	case SND_CONFIG_TYPE_COMPOUND:
+		if (!force_restore || !doit)
+			return -1;
+		n = snd_config_iterator_entry(snd_config_iterator_first(n));
+		return config_bool(n, doit);
+	default:
+		return -1;
+	}
+	if (strcmp(str, "on") == 0 || strcmp(str, "true") == 0)
+		return 1;
+	if (strcmp(str, "off") == 0 || strcmp(str, "false") == 0)
+		return 0;
+	return -1;
+}
+
+static int config_enumerated(snd_config_t *n, snd_ctl_t *handle,
+			     snd_ctl_elem_info_t *info, int doit)
+{
+	const char *str;
+	long val;
+	long long lval;
+	unsigned int idx, items;
+
+	switch (snd_config_get_type(n)) {
+	case SND_CONFIG_TYPE_INTEGER:
+		snd_config_get_integer(n, &val);
+		return val;
+	case SND_CONFIG_TYPE_INTEGER64:
+		snd_config_get_integer64(n, &lval);
+		return (int) lval;
+	case SND_CONFIG_TYPE_STRING:
+		snd_config_get_string(n, &str);
+		break;
+	case SND_CONFIG_TYPE_COMPOUND:
+		if (!force_restore || !doit)
+			return -1;
+		n = snd_config_iterator_entry(snd_config_iterator_first(n));
+		return config_enumerated(n, handle, info, doit);
+	default:
+		return -1;
+	}
+	items = snd_ctl_elem_info_get_items(info);
+	for (idx = 0; idx < items; idx++) {
+		int err;
+		snd_ctl_elem_info_set_item(info, idx);
+		err = snd_ctl_elem_info(handle, info);
+		if (err < 0) {
+			error("snd_ctl_elem_info: %s", snd_strerror(err));
+			return err;
+		}
+		if (strcmp(str, snd_ctl_elem_info_get_item_name(info)) == 0)
+			return idx;
+	}
+	return -1;
+}
+
+static int config_integer(snd_config_t *n, long *val, int doit)
+{
+	int err = snd_config_get_integer(n, val);
+	if (err < 0 && force_restore && doit) {
+		if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND)
+			return err;
+		n = snd_config_iterator_entry(snd_config_iterator_first(n));
+		return config_integer(n, val, doit);
+	}
+	return err;
+}
+
+static int config_integer64(snd_config_t *n, long long *val, int doit)
+{
+	int err = snd_config_get_integer64(n, val);
+	if (err < 0 && force_restore && doit) {
+		if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND)
+			return err;
+		n = snd_config_iterator_entry(snd_config_iterator_first(n));
+		return config_integer64(n, val, doit);
+	}
+	return err;
+}
+
+static int check_comment_access(snd_config_t *conf, const char *str)
+{
+	snd_config_iterator_t i, next;
+
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id, *s;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "access") == 0) {
+			if (snd_config_get_string(n, &s) < 0)
+				return 0;
+			if (strstr(s, str))
+				return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * get the item type from the given comment config
+ */
+static int get_comment_type(snd_config_t *n)
+{
+	static const snd_ctl_elem_type_t types[] = {
+		SND_CTL_ELEM_TYPE_BOOLEAN,
+		SND_CTL_ELEM_TYPE_INTEGER,
+		SND_CTL_ELEM_TYPE_ENUMERATED,
+		SND_CTL_ELEM_TYPE_BYTES,
+		SND_CTL_ELEM_TYPE_IEC958,
+		SND_CTL_ELEM_TYPE_INTEGER64,
+	};
+	const char *type;
+	unsigned int i;
+
+	if (snd_config_get_string(n, &type) < 0)
+		return -EINVAL;
+	for (i = 0; i < ARRAY_SIZE(types); ++i)
+		if (strcmp(type, snd_ctl_elem_type_name(types[i])) == 0)
+			return types[i];
+	return -EINVAL;
+}
+
+/*
+ * get the value range from the given comment config
+ */
+static int get_comment_range(snd_config_t *n, int ctype,
+			     long *imin, long *imax, long *istep)
+{
+	const char *s;
+	int err;
+
+	if (snd_config_get_string(n, &s) < 0)
+		return -EINVAL;
+	switch (ctype) {
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		err = sscanf(s, "%li - %li (step %li)", imin, imax, istep);
+		if (err != 3) {
+			istep = 0;
+			err = sscanf(s, "%li - %li", imin, imax);
+			if (err != 2)
+				return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+struct string_array {
+	unsigned int count;
+	const char **strings;
+};
+
+static int get_comment_items(snd_config_t *n, struct string_array *items)
+{
+	snd_config_iterator_t it, next;
+	unsigned int i;
+	int err;
+
+	snd_config_for_each(it, next, n) {
+		snd_config_t *item = snd_config_iterator_entry(it);
+		const char *id;
+		unsigned int numid;
+
+		if (snd_config_get_id(item, &id) < 0)
+			return -EINVAL;
+		numid = atoi(id);
+		if (numid > 999999)
+			return -EINVAL;
+
+		if (numid >= items->count) {
+			const char **strings = realloc(items->strings, (numid + 1) * sizeof(char *));
+			if (!strings)
+				return -ENOMEM;
+			for (i = items->count; i < numid + 1; ++i)
+				strings[i] = NULL;
+			items->count = numid + 1;
+			items->strings = strings;
+		}
+		err = snd_config_get_string(item, &items->strings[numid]);
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < items->count; ++i)
+		if (!items->strings[i])
+			return -EINVAL;
+
+	return 0;
+}
+
+static int add_user_control(snd_ctl_t *handle, snd_ctl_elem_info_t *info, snd_config_t *conf)
+{
+	snd_ctl_elem_id_t *id;
+	snd_config_iterator_t i, next;
+	long imin, imax, istep;
+	snd_ctl_elem_type_t ctype;
+	unsigned int count;
+	struct string_array enum_items;
+	int err;
+	unsigned int *tlv;
+
+	imin = imax = istep = 0;
+	count = 0;
+	ctype = SND_CTL_ELEM_TYPE_NONE;
+	enum_items.count = 0;
+	enum_items.strings = NULL;
+	tlv = NULL;
+	snd_config_for_each(i, next, conf) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		if (strcmp(id, "type") == 0) {
+			err = get_comment_type(n);
+			if (err < 0)
+				goto error;
+			ctype = err;
+			continue;
+		}
+		if (strcmp(id, "range") == 0) {
+			err = get_comment_range(n, ctype, &imin, &imax, &istep);
+			if (err < 0)
+				goto error;
+			continue;
+		}
+		if (strcmp(id, "count") == 0) {
+			long v;
+			if ((err = snd_config_get_integer(n, &v)) < 0)
+				goto error;
+			count = v;
+			continue;
+		}
+		if (strcmp(id, "item") == 0) {
+			err = get_comment_items(n, &enum_items);
+			if (err < 0)
+				goto error;
+			continue;
+		}
+		if (strcmp(id, "tlv") == 0) {
+			const char *s;
+			if ((err = snd_config_get_string(n, &s)) < 0)
+				goto error;
+			if (tlv)
+				free(tlv);
+			if ((tlv = str_to_tlv(s)) == NULL) {
+				err = -EINVAL;
+				goto error;
+			}
+			continue;
+		}
+	}
+
+	snd_ctl_elem_id_alloca(&id);
+	snd_ctl_elem_info_get_id(info, id);
+	if (count <= 0)
+		count = 1;
+	switch (ctype) {
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		if (imin > imax || istep > imax - imin) {
+			err = -EINVAL;
+			goto error;
+		}
+		err = snd_ctl_elem_add_integer(handle, id, count, imin, imax, istep);
+		if (err < 0)
+			goto error;
+		if (tlv)
+			snd_ctl_elem_tlv_write(handle, id, tlv);
+		break;
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		err = snd_ctl_elem_add_boolean(handle, id, count);
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		err = snd_ctl_elem_add_enumerated(handle, id, count,
+						  enum_items.count, enum_items.strings);
+		break;
+	case SND_CTL_ELEM_TYPE_IEC958:
+		err = snd_ctl_elem_add_iec958(handle, id);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+
+ error:
+	free(tlv);
+	free(enum_items.strings);
+	if (err < 0)
+		return err;
+	return snd_ctl_elem_info(handle, info);
+}
+
+/*
+ * check whether the config item has the same of compatible type
+ */
+static int check_comment_type(snd_config_t *conf, int type)
+{
+	snd_config_t *n;
+	int ctype;
+
+	if (snd_config_search(conf, "type", &n) < 0)
+		return 0; /* not defined */
+	ctype = get_comment_type(n);
+	if (ctype == type)
+		return 0;
+	if ((ctype == SND_CTL_ELEM_TYPE_BOOLEAN ||
+	     ctype == SND_CTL_ELEM_TYPE_INTEGER ||
+	     ctype == SND_CTL_ELEM_TYPE_INTEGER64 ||
+	     ctype == SND_CTL_ELEM_TYPE_ENUMERATED) &&
+	    (type == SND_CTL_ELEM_TYPE_BOOLEAN ||
+	     type == SND_CTL_ELEM_TYPE_INTEGER ||
+	     type == SND_CTL_ELEM_TYPE_INTEGER64 ||
+	     type == SND_CTL_ELEM_TYPE_ENUMERATED))
+		return 0; /* OK, compatible */
+	return -EINVAL;
+}
+
+/*
+ * convert from an old value to a new value with the same dB level
+ */
+static int convert_to_new_db(snd_config_t *value, long omin, long omax,
+			     long nmin, long nmax,
+			     long odbmin, long odbmax,
+			     snd_config_t *comment, const char *index,
+			     snd_ctl_t *device, snd_ctl_elem_id_t *id,
+			     int doit)
+{
+	snd_config_t *db_node;
+	long db, val;
+	int err;
+
+	if (snd_config_searchv(comment, &db_node, "dbvalue", index, NULL) < 0 ||
+	    snd_config_get_integer(db_node, &db) < 0) {
+		err = config_integer(value, &val, doit);
+		if (err < 0)
+			return err;
+		if (val < omin || val > omax)
+			return -EINVAL;
+		db = ((val - omin) * (odbmax - odbmin)) / (omax - omin) + odbmin;
+	}
+
+	err = snd_ctl_convert_from_dB(device, id, db, &val, db > 0);
+	if (err < 0)
+		return err;
+	if (val < nmin)
+		val = nmin;
+	else if (val > nmax)
+		val = nmax;
+	return snd_config_set_integer(value, val);
+}
+
+/*
+ * compare the current value range with the old range in comments.
+ * also, if dB information is available, try to compare them.
+ * if any change occurs, try to keep the same dB level.
+ */
+static int check_comment_range(snd_ctl_t *handle, snd_config_t *conf,
+			       snd_ctl_elem_info_t *info, snd_config_t *value,
+			       int doit)
+{
+	snd_config_t *n;
+	long omin, omax, ostep;
+	long nmin, nmax;
+	long odbmin, odbmax;
+	long ndbmin, ndbmax;
+	snd_ctl_elem_id_t *id;
+
+	if (snd_config_search(conf, "range", &n) < 0)
+		return 0;
+	if (get_comment_range(n, SND_CTL_ELEM_TYPE_INTEGER,
+			      &omin, &omax, &ostep) < 0)
+		return 0;
+	nmin = snd_ctl_elem_info_get_min(info);
+	nmax = snd_ctl_elem_info_get_max(info);
+	if (omin != nmin && omax != nmax) {
+		/* Hey, the range mismatches */
+		if (!force_restore || !doit)
+			return -EINVAL;
+	}
+	if (omin >= omax || nmin >= nmax)
+		return 0; /* invalid values */
+
+	if (snd_config_search(conf, "dbmin", &n) < 0)
+		return 0;
+	if (config_integer(n, &odbmin, doit) < 0)
+		return 0;
+	if (snd_config_search(conf, "dbmax", &n) < 0)
+		return 0;
+	if (config_integer(n, &odbmax, doit) < 0)
+		return 0;
+	if (odbmin >= odbmax)
+		return 0; /* invalid values */
+	snd_ctl_elem_id_alloca(&id);
+	snd_ctl_elem_info_get_id(info, id);
+	if (snd_ctl_get_dB_range(handle, id, &ndbmin, &ndbmax) < 0)
+		return 0;
+	if (ndbmin >= ndbmax)
+		return 0; /* invalid values */
+	if (omin == nmin && omax == nmax &&
+	    odbmin == ndbmin && odbmax == ndbmax)
+		return 0; /* OK, identical one */
+
+	/* Let's guess the current value from dB range */
+	if (snd_config_get_type(value) == SND_CONFIG_TYPE_COMPOUND) {
+		snd_config_iterator_t i, next;
+		snd_config_for_each(i, next, value) {
+			snd_config_t *n = snd_config_iterator_entry(i);
+			const char *idxstr;
+			if (snd_config_get_id(n, &idxstr) < 0)
+				continue;
+			convert_to_new_db(n, omin, omax, nmin, nmax,
+					  odbmin, odbmax, conf, idxstr,
+					  handle, id, doit);
+		}
+	} else
+		convert_to_new_db(value, omin, omax, nmin, nmax,
+				  odbmin, odbmax, conf, "0",
+				  handle, id, doit);
+	return 0;
+}
+
+static int restore_config_value(snd_ctl_t *handle, snd_ctl_elem_info_t *info,
+				snd_ctl_elem_iface_t type,
+				snd_config_t *value,
+				snd_ctl_elem_value_t *ctl, int idx,
+				int doit)
+{
+	long val;
+	long long lval;
+	int err;
+
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BOOLEAN:
+		val = config_bool(value, doit);
+		if (val >= 0) {
+			snd_ctl_elem_value_set_boolean(ctl, idx, val);
+			return 1;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		err = config_integer(value, &val, doit);
+		if (err == 0) {
+			snd_ctl_elem_value_set_integer(ctl, idx, val);
+			return 1;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		err = config_integer64(value, &lval, doit);
+		if (err == 0) {
+			snd_ctl_elem_value_set_integer64(ctl, idx, lval);
+			return 1;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+		val = config_enumerated(value, handle, info, doit);
+		if (val >= 0) {
+			snd_ctl_elem_value_set_enumerated(ctl, idx, val);
+			return 1;
+		}
+		break;
+	case SND_CTL_ELEM_TYPE_BYTES:
+	case SND_CTL_ELEM_TYPE_IEC958:
+		break;
+	default:
+		cerror(doit, "Unknow control type: %d", type);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int restore_config_value2(snd_ctl_t *handle, snd_ctl_elem_info_t *info,
+				 snd_ctl_elem_iface_t type,
+				 snd_config_t *value,
+				 snd_ctl_elem_value_t *ctl, int idx,
+				 unsigned int numid, int doit)
+{
+	int err = restore_config_value(handle, info, type, value, ctl, idx, doit);
+	long val;
+
+	if (err != 0)
+		return err;
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BYTES:
+	case SND_CTL_ELEM_TYPE_IEC958:
+		err = snd_config_get_integer(value, &val);
+		if (err < 0 || val < 0 || val > 255) {
+			cerror(doit, "bad control.%d.value.%d content", numid, idx);
+			return force_restore && doit ? 0 : -EINVAL;
+		}
+		snd_ctl_elem_value_set_byte(ctl, idx, val);
+		return 1;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int set_control(snd_ctl_t *handle, snd_config_t *control,
+		       int *maxnumid, int doit)
+{
+	snd_ctl_elem_value_t *ctl;
+	snd_ctl_elem_info_t *info;
+	snd_config_iterator_t i, next;
+	unsigned int numid1;
+	snd_ctl_elem_iface_t iface = -1;
+	int iface1;
+	const char *name1;
+	unsigned int numid;
+	snd_ctl_elem_type_t type;
+	unsigned int count;
+	long device = -1;
+	long device1;
+	long subdevice = -1;
+	long subdevice1;
+	const char *name = NULL;
+	long index1;
+	long index = -1;
+	snd_config_t *value = NULL;
+	snd_config_t *comment = NULL;
+	unsigned int idx;
+	int err;
+	char *set;
+	const char *id;
+	snd_ctl_elem_value_alloca(&ctl);
+	snd_ctl_elem_info_alloca(&info);
+	if (snd_config_get_type(control) != SND_CONFIG_TYPE_COMPOUND) {
+		cerror(doit, "control is not a compound");
+		return -EINVAL;
+	}
+	err = snd_config_get_id(control, &id);
+	if (err < 0) {
+		cerror(doit, "unable to get id");
+		return -EINVAL;
+	}
+	numid = atoi(id);
+	if ((int)numid > *maxnumid)
+		*maxnumid = numid;
+	snd_config_for_each(i, next, control) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *fld;
+		if (snd_config_get_id(n, &fld) < 0)
+			continue;
+		if (strcmp(fld, "comment") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_COMPOUND) {
+				cerror(doit, "control.%d.%s is invalid", numid, fld);
+				return -EINVAL;
+			}
+			comment = n;
+			continue;
+		}
+		if (strcmp(fld, "iface") == 0) {
+			iface = (snd_ctl_elem_iface_t)config_iface(n);
+			continue;
+		}
+		if (strcmp(fld, "device") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
+				cerror(doit, "control.%d.%s is invalid", numid, fld);
+				return -EINVAL;
+			}
+			snd_config_get_integer(n, &device);
+			continue;
+		}
+		if (strcmp(fld, "subdevice") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
+				cerror(doit, "control.%d.%s is invalid", numid, fld);
+				return -EINVAL;
+			}
+			snd_config_get_integer(n, &subdevice);
+			continue;
+		}
+		if (strcmp(fld, "name") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_STRING) {
+				cerror(doit, "control.%d.%s is invalid", numid, fld);
+				return -EINVAL;
+			}
+			snd_config_get_string(n, &name);
+			continue;
+		}
+		if (strcmp(fld, "index") == 0) {
+			if (snd_config_get_type(n) != SND_CONFIG_TYPE_INTEGER) {
+				cerror(doit, "control.%d.%s is invalid", numid, fld);
+				return -EINVAL;
+			}
+			snd_config_get_integer(n, &index);
+			continue;
+		}
+		if (strcmp(fld, "value") == 0) {
+			value = n;
+			continue;
+		}
+		cerror(doit, "unknown control.%d.%s field", numid, fld);
+	}
+	if (!value) {
+		cerror(doit, "missing control.%d.value", numid);
+		return -EINVAL;
+	}
+	if (device < 0)
+		device = 0;
+	if (subdevice < 0)
+		subdevice = 0;
+	if (index < 0)
+		index = 0;
+
+	err = -EINVAL;
+	if (!force_restore) {
+		snd_ctl_elem_info_set_numid(info, numid);
+		err = snd_ctl_elem_info(handle, info);
+	}
+	if (err < 0 && name) {
+		snd_ctl_elem_info_set_numid(info, 0);
+		snd_ctl_elem_info_set_interface(info, iface);
+		snd_ctl_elem_info_set_device(info, device);
+		snd_ctl_elem_info_set_subdevice(info, subdevice);
+		snd_ctl_elem_info_set_name(info, name);
+		snd_ctl_elem_info_set_index(info, index);
+		err = snd_ctl_elem_info(handle, info);
+		if (err < 0 && comment && check_comment_access(comment, "user")) {
+			err = add_user_control(handle, info, comment);
+			if (err < 0) {
+				cerror(doit, "failed to add user control #%d (%s)",
+				       numid, snd_strerror(err));
+				return err;
+			}
+		}
+	}
+	if (err < 0) {
+		cerror(doit, "failed to obtain info for control #%d (%s)", numid, snd_strerror(err));
+		return -ENOENT;
+	}
+	numid1 = snd_ctl_elem_info_get_numid(info);
+	iface1 = snd_ctl_elem_info_get_interface(info);
+	device1 = snd_ctl_elem_info_get_device(info);
+	subdevice1 = snd_ctl_elem_info_get_subdevice(info);
+	name1 = snd_ctl_elem_info_get_name(info);
+	index1 = snd_ctl_elem_info_get_index(info);
+	count = snd_ctl_elem_info_get_count(info);
+	type = snd_ctl_elem_info_get_type(info);
+	if (err |= numid != numid1 && !force_restore)
+		cerror(doit, "warning: numid mismatch (%d/%d) for control #%d", 
+		      numid, numid1, numid);
+	if (err |= iface != iface1)
+		cerror(doit, "warning: iface mismatch (%d/%d) for control #%d", iface, iface1, numid);
+	if (err |= device != device1)
+		cerror(doit, "warning: device mismatch (%ld/%ld) for control #%d", device, device1, numid);
+	if (err |= subdevice != subdevice1)
+		cerror(doit, "warning: subdevice mismatch (%ld/%ld) for control #%d", subdevice, subdevice1, numid);
+	if (err |= strcmp(name, name1))
+		cerror(doit, "warning: name mismatch (%s/%s) for control #%d", name, name1, numid);
+	if (err |= index != index1)
+		cerror(doit, "warning: index mismatch (%ld/%ld) for control #%d", index, index1, numid);
+	if (err < 0) {
+		cerror(doit, "failed to obtain info for control #%d (%s)", numid, snd_strerror(err));
+		return -ENOENT;
+	}
+
+	if (comment) {
+		if (check_comment_type(comment, type) < 0)
+			cerror(doit, "incompatible field type for control #%d", numid);
+		if (type == SND_CTL_ELEM_TYPE_INTEGER) {
+			if (check_comment_range(handle, comment, info, value, doit) < 0) {
+				cerror(doit, "value range mismatch for control #%d",
+				      numid);
+				return -EINVAL;
+			}
+		}
+		/* inactive controls are not restored */
+		if (comment && check_comment_access(comment, "inactive"))
+			return 0;
+	}
+
+	if (snd_ctl_elem_info_is_inactive(info) ||
+				!snd_ctl_elem_info_is_writable(info))
+		return 0;
+	snd_ctl_elem_value_set_numid(ctl, numid1);
+
+	if (count == 1) {
+		err = restore_config_value(handle, info, type, value, ctl, 0, doit);
+		if (err < 0)
+			return err;
+		if (err > 0)
+			goto _ok;
+	}
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_BYTES:
+	case SND_CTL_ELEM_TYPE_IEC958:
+	{
+		const char *buf;
+		err = snd_config_get_string(value, &buf);
+		if (err >= 0) {
+			int c1 = 0;
+			int len = strlen(buf);
+			unsigned int idx = 0;
+			int size = type == SND_CTL_ELEM_TYPE_BYTES ?
+				count : sizeof(snd_aes_iec958_t);
+			if (size * 2 != len) {
+				cerror(doit, "bad control.%d.value contents\n", numid);
+				return -EINVAL;
+			}
+			while (*buf) {
+				int c = *buf++;
+				if ((c = hextodigit(c)) < 0) {
+					cerror(doit, "bad control.%d.value contents\n", numid);
+					return -EINVAL;
+				}
+				if (idx % 2 == 1)
+					snd_ctl_elem_value_set_byte(ctl, idx / 2, c1 << 4 | c);
+				else
+					c1 = c;
+				idx++;
+			}
+			goto _ok;
+		}
+	}
+	default:
+		break;
+	}
+	if (snd_config_get_type(value) != SND_CONFIG_TYPE_COMPOUND) {
+		if (!force_restore || !doit) {
+			cerror(doit, "bad control.%d.value type", numid);
+			return -EINVAL;
+		}
+		for (idx = 0; idx < count; ++idx) {
+			err = restore_config_value2(handle, info, type, value,
+						    ctl, idx, numid, doit);
+			if (err < 0)
+				return err;
+		}
+		goto _ok;
+	}
+
+	set = (char*) alloca(count);
+	memset(set, 0, count);
+	snd_config_for_each(i, next, value) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		const char *id;
+		if (snd_config_get_id(n, &id) < 0)
+			continue;
+		idx = atoi(id);
+		if (idx >= count || set[idx]) {
+			cerror(doit, "bad control.%d.value index", numid);
+			if (!force_restore || !doit)
+				return -EINVAL;
+			continue;
+		}
+		err = restore_config_value2(handle, info, type, n,
+					    ctl, idx, numid, doit);
+		if (err < 0)
+			return err;
+		if (err > 0)
+			set[idx] = 1;
+	}
+	for (idx = 0; idx < count; ++idx) {
+		if (!set[idx]) {
+			cerror(doit, "control.%d.value.%d is not specified", numid, idx);
+			if (!force_restore || !doit)
+				return -EINVAL;
+		}
+	}
+
+ _ok:
+	err = doit ? snd_ctl_elem_write(handle, ctl) : 0;
+	if (err < 0) {
+		error("Cannot write control '%d:%ld:%ld:%s:%ld' : %s", (int)iface, device, subdevice, name, index, snd_strerror(err));
+		return err;
+	}
+	return 0;
+}
+
+static int set_controls(int card, snd_config_t *top, int doit)
+{
+	snd_ctl_t *handle;
+	snd_ctl_card_info_t *info;
+	snd_config_t *control;
+	snd_config_iterator_t i, next;
+	int err, maxnumid = -1;
+	char name[32], tmpid[16];
+	const char *id;
+	snd_ctl_card_info_alloca(&info);
+
+	sprintf(name, "hw:%d", card);
+	dbg("device='%s', doit=%i", name, doit);
+	err = snd_ctl_open(&handle, name, 0);
+	if (err < 0) {
+		error("snd_ctl_open error: %s", snd_strerror(err));
+		return err;
+	}
+	err = snd_ctl_card_info(handle, info);
+	if (err < 0) {
+		error("snd_ctl_card_info error: %s", snd_strerror(err));
+		goto _close;
+	}
+	id = snd_ctl_card_info_get_id(info);
+	dbg("card-info-id: '%s'", id);
+	err = snd_config_searchv(top, &control, "state", id, "control", 0);
+	if (err < 0) {
+		if (force_restore) {
+			sprintf(tmpid, "card%d", card);
+			err = snd_config_searchv(top, &control, "state", tmpid, "control", 0);
+			if (! err)
+				id = tmpid;
+		}
+		if (err < 0) {
+			fprintf(stderr, "No state is present for card %s\n", id);
+			goto _close;
+		}
+		id = tmpid;
+	}
+	if (snd_config_get_type(control) != SND_CONFIG_TYPE_COMPOUND) {
+		cerror(doit, "state.%s.control is not a compound\n", id);
+		return -EINVAL;
+	}
+	snd_config_for_each(i, next, control) {
+		snd_config_t *n = snd_config_iterator_entry(i);
+		err = set_control(handle, n, &maxnumid, doit);
+		if (err < 0 && (!force_restore || !doit))
+			goto _close;
+	}
+
+	dbg("maxnumid=%i", maxnumid);
+	/* check if we have additional controls in driver */
+	/* in this case we should go through init procedure */
+	if (!doit && maxnumid >= 0) {
+		snd_ctl_elem_info_t *info;
+		snd_ctl_elem_info_alloca(&info);
+		snd_ctl_elem_info_set_numid(info, maxnumid+1);
+		if (snd_ctl_elem_info(handle, info) == 0) {
+			/* not very informative */
+			/* but value is used for check only */
+			err = -EAGAIN;
+			dbg("more controls than maxnumid?");
+			goto _close;
+		}
+	}
+
+ _close:
+	snd_ctl_close(handle);
+	dbg("result code: %i", err);
+	return err;
+}
+
+int save_state(const char *file, const char *cardname)
+{
+	int err;
+	snd_config_t *config;
+	snd_input_t *in;
+	snd_output_t *out;
+	int stdio;
+
+	err = snd_config_top(&config);
+	if (err < 0) {
+		error("snd_config_top error: %s", snd_strerror(err));
+		return err;
+	}
+	stdio = !strcmp(file, "-");
+	if (!stdio && (err = snd_input_stdio_open(&in, file, "r")) >= 0) {
+		err = snd_config_load(config, in);
+		snd_input_close(in);
+#if 0
+		if (err < 0) {
+			error("snd_config_load error: %s", snd_strerror(err));
+			return err;
+		}
+#endif
+	}
+
+	if (!cardname) {
+		int card, first = 1;
+
+		card = -1;
+		/* find each installed soundcards */
+		while (1) {
+			if (snd_card_next(&card) < 0)
+				break;
+			if (card < 0) {
+				if (first) {
+					if (ignore_nocards) {
+						return 0;
+					} else {
+						error("No soundcards found...");
+						return -ENODEV;
+					}
+				}
+				break;
+			}
+			first = 0;
+			if ((err = get_controls(card, config)))
+				return err;
+		}
+	} else {
+		int cardno;
+
+		cardno = snd_card_get_index(cardname);
+		if (cardno < 0) {
+			error("Cannot find soundcard '%s'...", cardname);
+			return cardno;
+		}
+		if ((err = get_controls(cardno, config))) {
+			return err;
+		}
+	}
+	
+	if (stdio)
+		err = snd_output_stdio_attach(&out, stdout, 0);
+	else
+		err = snd_output_stdio_open(&out, file, "w");
+	if (err < 0) {
+		error("Cannot open %s for writing: %s", file, snd_strerror(err));
+		return -errno;
+	}
+	err = snd_config_save(config, out);
+	snd_output_close(out);
+	if (err < 0)
+		error("snd_config_save: %s", snd_strerror(err));
+	return 0;
+}
+
+int load_state(const char *file, const char *initfile, const char *cardname,
+	       int do_init)
+{
+	int err, finalerr = 0;
+	snd_config_t *config;
+	snd_input_t *in;
+	int stdio;
+
+	err = snd_config_top(&config);
+	if (err < 0) {
+		error("snd_config_top error: %s", snd_strerror(err));
+		return err;
+	}
+	stdio = !strcmp(file, "-");
+	if (stdio)
+		err = snd_input_stdio_attach(&in, stdin, 0);
+	else
+		err = snd_input_stdio_open(&in, file, "r");
+	if (err >= 0) {
+		err = snd_config_load(config, in);
+		snd_input_close(in);
+		if (err < 0) {
+			error("snd_config_load error: %s", snd_strerror(err));
+			return err;
+		}
+	} else {
+		int card, first = 1;
+		char cardname1[16];
+
+		error("Cannot open %s for reading: %s", file, snd_strerror(err));
+		finalerr = err;
+		card = -1;
+		/* find each installed soundcards */
+		while (1) {
+			if (snd_card_next(&card) < 0)
+				break;
+			if (card < 0)
+				break;
+			first = 0;
+			if (!do_init)
+				break;
+			sprintf(cardname1, "%i", card);
+			err = init(initfile, cardname1);
+			if (err < 0) {
+				finalerr = err;
+				initfailed(card, "init", err);
+			}
+			initfailed(card, "restore", -ENOENT);
+		}
+		if (first)
+			finalerr = 0;	/* no cards, no error code */
+		return finalerr;
+	}
+
+	if (!cardname) {
+		int card, first = 1;
+		char cardname1[16];
+
+		card = -1;
+		/* find each installed soundcards */
+		while (1) {
+			if (snd_card_next(&card) < 0)
+				break;
+			if (card < 0) {
+				if (first) {
+					if (ignore_nocards) {
+						return 0;
+					} else {
+						error("No soundcards found...");
+						return -ENODEV;
+					}
+				}
+				break;
+			}
+			first = 0;
+			/* do a check if controls matches state file */
+ 			if (do_init && set_controls(card, config, 0)) {
+				sprintf(cardname1, "%i", card);
+				err = init(initfile, cardname1);
+				if (err < 0) {
+					initfailed(card, "init", err);
+					finalerr = err;
+				}
+			}
+			if ((err = set_controls(card, config, 1))) {
+				if (!force_restore)
+					finalerr = err;
+				initfailed(card, "restore", err);
+			}
+		}
+	} else {
+		int cardno;
+
+		cardno = snd_card_get_index(cardname);
+		if (cardno < 0) {
+			error("Cannot find soundcard '%s'...", cardname);
+			return -ENODEV;
+		}
+		/* do a check if controls matches state file */
+		if (do_init && set_controls(cardno, config, 0)) {
+			err = init(initfile, cardname);
+			if (err < 0) {
+				initfailed(cardno, "init", err);
+				finalerr = err;
+			}
+		}
+		if ((err = set_controls(cardno, config, 1))) {
+			initfailed(cardno, "restore", err);
+			if (!force_restore)
+				return err;
+		}
+	}
+	return finalerr;
+}
diff --git a/alsactl/utils.c b/alsactl/utils.c
new file mode 100644
index 0000000..a27eb6e
--- /dev/null
+++ b/alsactl/utils.c
@@ -0,0 +1,102 @@
+/*
+ *  Advanced Linux Sound Architecture Control Program - Support routines
+ *  Copyright (c) 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 <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <alsa/asoundlib.h>
+#include "alsactl.h"
+
+int file_map(const char *filename, char **buf, size_t *bufsize)
+{
+	struct stat stats;
+	int fd;
+
+	fd = open(filename, O_RDONLY);
+	if (fd < 0) {
+		return -1;
+	}
+
+	if (fstat(fd, &stats) < 0) {
+		close(fd);
+		return -1;
+	}
+
+	*buf = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
+	if (*buf == MAP_FAILED) {
+		close(fd);
+		return -1;
+	}
+	*bufsize = stats.st_size;
+
+	close(fd);
+
+	return 0;
+}
+
+void file_unmap(void *buf, size_t bufsize)
+{
+	munmap(buf, bufsize);
+}
+
+size_t line_width(const char *buf, size_t bufsize, size_t pos)
+{
+	int esc = 0;
+	size_t count;
+	
+	for (count = pos; count < bufsize; count++) {
+		if (!esc && buf[count] == '\n')
+			break;
+		esc = buf[count] == '\\';
+	}
+
+	return count - pos;
+}
+
+void initfailed(int cardnumber, const char *reason, int exitcode)
+{
+	int fp;
+	char *str;
+	char sexitcode[16];
+
+	if (statefile == NULL)
+		return;
+	if (snd_card_get_name(cardnumber, &str) < 0)
+		return;
+	sprintf(sexitcode, "%i", exitcode);
+	fp = open(statefile, O_WRONLY|O_CREAT|O_APPEND, 0644);
+	write(fp, str, strlen(str));
+	write(fp, ":", 1);
+	write(fp, reason, strlen(reason));
+	write(fp, ":", 1);
+	write(fp, sexitcode, strlen(sexitcode));
+	write(fp, "\n", 1);
+	close(fp);
+	free(str);
+}
diff --git a/alsaloop/Makefile.am b/alsaloop/Makefile.am
new file mode 100644
index 0000000..a694911
--- /dev/null
+++ b/alsaloop/Makefile.am
@@ -0,0 +1,14 @@
+INCLUDES = -I$(top_srcdir)/include
+LDADD = -lm
+AM_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..bfac2d3
--- /dev/null
+++ b/alsaloop/pcmjob.c
@@ -0,0 +1,1953 @@
+/*
+ *  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_physical_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;
+}
+
+static void fix_format(struct loopback *loop, int force)
+{
+	snd_pcm_format_t format = loop->capt->format;
+
+	if (!force && loop->sync != SYNC_TYPE_SAMPLERATE)
+		return;
+	if (format == SND_PCM_FORMAT_S16 ||
+	    format == SND_PCM_FORMAT_S32)
+		return;
+	if (snd_pcm_format_width(format) > 16)
+		format = SND_PCM_FORMAT_S32;
+	else
+		format = SND_PCM_FORMAT_S16;
+	loop->capt->format = format;
+	loop->play->format = format;
+}
+
+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;
+		fix_format(loop, 0);
+		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;
+__again:
+	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->capt->rate_req != loop->capt->rate) {
+                        snd_pcm_format_t format1, format2;
+			loop->use_samplerate = 1;
+                        format1 = loop->play->format;
+                        format2 = loop->capt->format;
+                        fix_format(loop, 1);
+                        if (loop->play->format != format1 ||
+                            loop->capt->format != format2) {
+                                pcmjob_stop(loop);
+                                goto __again;
+                        }
+                }
+	}
+#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
new file mode 100644
index 0000000..8a82323
--- /dev/null
+++ b/alsamixer/Makefile.am
@@ -0,0 +1,25 @@
+AM_CFLAGS = @CURSES_CFLAGS@ -DCURSESINC="@CURSESINC@"
+LDADD = @CURSESLIB@
+
+bin_PROGRAMS = alsamixer
+alsamixer_SOURCES = card_select.c card_select.h \
+		cli.c \
+		colors.c colors.h \
+		device_name.c device_name.h \
+		die.c die.h \
+		mainloop.c mainloop.h \
+		mem.c mem.h \
+		mixer_controls.c mixer_controls.h \
+		mixer_display.c mixer_display.h \
+		mixer_widget.c mixer_widget.h \
+		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
+alsamixer_INCLUDES = -I$(top_srcdir)/include
+
+#LDFLAGS = -static
+#CFLAGS += -g -Wall
diff --git a/alsamixer/alsamixer.1 b/alsamixer/alsamixer.1
new file mode 100644
index 0000000..cd88404
--- /dev/null
+++ b/alsamixer/alsamixer.1
@@ -0,0 +1,178 @@
+.TH ALSAMIXER 1 "22 May 2009"
+.SH NAME
+alsamixer \- soundcard mixer for ALSA soundcard driver, with ncurses interface
+.SH SYNOPSIS
+
+\fBalsamixer\fP [\fIoptions\fP]
+
+.SH DESCRIPTION
+\fBalsamixer\fP is an ncurses mixer program for use with the ALSA
+soundcard drivers. It supports multiple soundcards with multiple devices.
+
+.SH OPTIONS
+
+.TP
+\fI\-h, \-\-help\fP
+Help: show available flags.
+
+.TP
+\fI\-c, \-\-card\fP <card number or identification>
+Select the soundcard to use, if you have more than one. Cards are
+numbered from 0 (the default).
+
+.TP
+\fI\-D, \-\-device\fP <device identification>
+Select the mixer device to control.
+
+.TP
+\fI\-V, \-\-view\fP <mode>
+Select the starting view mode, either \fIplayback\fP, \fIcapture\fP or \fIall\fP.
+
+.TP
+\fI\-g, \-\-no\-color\fP
+Toggle the using of colors.
+
+.SH MIXER VIEWS
+
+The top-left corner of \fBalsamixer\fP is the are to show some basic
+information: the card name, the mixer chip name, the current view
+mode and the currently selected mixer item.
+When the mixer item is switched off, \fI[Off]\fP is displayed in its
+name.
+
+Volume bars are located below the basic information area.  You can
+scroll left/right when all controls can't be put in a single screen.
+The name of each control is shown in the bottom below the volume bars.
+The currently selected item is drawn in red and/of emphasized.
+
+Each mixer control with volume capability shows a box and the current
+volume filled in that box.  The volume percentages are displayed below
+the volume bar for left and right channels.  For a mono control, only
+one value is shown there.
+
+When a mixer control is turned off, \fIM\fP (mute) appears below the
+volume bar.  When it's turned on, \fIO\fP in green appears instead.
+You can toggle the switch via \fIm\fP key.
+
+When a mixer control has capture capability, the capture flag appears
+below the volume bar, too.  When the capture is turned off,
+\-\-\-\-\-\-\- is shown.  \fICAPTURE\fP in red appears when the
+capture switch is turned on.  In addition, \fIL\fP and \fIR\fP letters
+appear in left and right side to indicate that left and the right
+channels are turned on.
+
+Some controls have the enumeration list, and don't show boxes but only
+texts which indicate the currently active item.  You can change the
+item via up/down keys.
+
+.SH VIEW MODES
+\fBalsamixer\fP has three view modes: playback, capture and all.
+In the playback view, only the controls related with playback are shown.
+Similarly, only the controls for capture (recording) are shown in the capture
+view.  The all view mode shows all controls.  The current view mode is displayed
+in the top-left position together with the mixer name, etc.
+
+The default view mode is the playback view.  You can change it via 
+\fI-V\fP option.
+
+Each view mode can be switched via keyboard commands, too.
+See the next section.
+
+.SH KEYBOARD COMMANDS
+\fBalsamixer\fP recognizes the following keyboard commands to control the soundcard. 
+Commands shown here in upper case can also be given in lower case.
+To be reminded of these keystrokes, hit the \fIh\fP key.
+
+.SS
+General Controls
+
+The \fILeft\fP and \fIright arrow\fP keys are used to select the
+channel (or device, depending on your preferred terminology). You can
+also use \fIn\fP ("next") and \fIp\fP ("previous").
+
+The \fIUp\fP and \fIDown Arrows\fP control the volume for the
+currently selected device. You can also use \fI+\fP or \fI\-\fP for the
+same purpose. Both the left and right signals are affected. For
+independent left and right control, see below.
+ 
+The \fIB\fP or \fI=\fP key adjusts the balance of volumes on left and
+right channels.
+
+\fIM\fP toggles muting for the current channel (both left and right).
+If the hardware supports it, you can
+mute left and right independently by using \fI,\fP (or \fI<\fP) and
+\fI.\fP (or \fI>\fP) respectively.
+
+\fISPACE\fP enables recording for the current channel. If any other
+channels have recording enabled, they will have their recording function
+disabled first. This only works for valid input channels, of course.
+
+\fIL\fP re-draws the screen.
+
+.SS
+View Mode Controls
+Function keys are used to change view modes.
+You can switch to the help mode and the proc info mode via \fIF1\fP and
+\fIF2\fP keys, respectively.
+On terminals that can't use function keys like gnome\-terminal, \fI?\fP and
+\fI/\fP keys can be used alternatively for help and proc modes.
+
+\fIF3\fP, \fIF4\fP and \fIF5\fP keys are used to switch to playback, capture
+and all view mode, respectively.  \fITAB\fP key toggles the
+current view mode circularly.
+
+.SS
+Quick Volume Changes
+
+\fIPageUp\fP increases volume by 5.
+
+\fIPageDown\fP decreases volume by 5.
+
+\fIEnd\fP sets volume to 0.
+
+You can also control left & right levels for the current channel
+independently, as follows:
+
+[\fIQ\fP | \fIW\fP | \fIE\fP ]  -- turn UP [ left | both | right ]
+
+[\fIZ\fP | \fIX\fP | \fIC\fP ] -- turn DOWN [ left | both | right ]   
+
+If the currently selected mixer channel is not a stereo channel, then
+all UP keys will work like \fIW\fP, and all DOWN keys will work like \fIX\fP.
+
+The number keys from \fI0\fP to \fI9\fP are to change the absolute volume
+quickly.  They correspond to 0 to 90% volume.
+
+.SS
+Selecting the Sound Card
+
+You can select another sound card by pressing the \fIF6\fP or \fIS\fP keys.
+This will show a list of available sound cards to choose from,
+and an entry to enter the mixer device name by hand.
+
+.SS
+Exiting
+
+Quit the program with \fIALT Q\fP, or by hitting \fIESC\fP.
+Please note that you might need to hit \fIESC\fP twice on some terminals
+since it's regarded as a prefix key.
+
+.SH SEE ALSO
+\fB
+amixer(1),
+aplay(1),
+arecord(1)
+\fP
+
+.SH BUGS 
+Some terminal emulators (e.g. \fBnxterm\fP) may not
+work quite right with ncurses, but that's their own damn
+fault. Plain old \fBxterm\fP seems to be fine.
+
+.SH AUTHOR
+.B alsamixer
+has been written by Tim Janik and
+been further improved by Jaroslav Kysela <perex@perex.cz>
+and Clemens Ladisch <clemens@ladisch.de>.
+
+This manual page was provided by Paul Winkler <zarmzarm@erols.com>.
diff --git a/alsamixer/card_select.c b/alsamixer/card_select.c
new file mode 100644
index 0000000..b473dcf
--- /dev/null
+++ b/alsamixer/card_select.c
@@ -0,0 +1,268 @@
+/*
+ * card_select.c - select a card by list or device name
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <alsa/asoundlib.h>
+#include <menu.h>
+#include "gettext_curses.h"
+#include "die.h"
+#include "mem.h"
+#include "utils.h"
+#include "colors.h"
+#include "widget.h"
+#include "mixer_widget.h"
+#include "device_name.h"
+#include "card_select.h"
+
+struct card {
+	struct card *next;
+	char *indexstr;
+	char *name;
+	char *device_name;
+};
+
+static struct widget list_widget;
+static struct card first_card;
+static ITEM **items;
+static MENU *menu;
+static ITEM *initial_item;
+
+static void on_key_enter(void)
+{
+	ITEM *item = current_item(menu);
+	if (item) {
+		struct card *card = item_userptr(item);
+		if (card->device_name) {
+			if (select_card_by_name(card->device_name))
+				list_widget.close();
+		} else {
+			create_device_name_form();
+		}
+	}
+}
+
+static void on_menu_key(int key)
+{
+	static const struct {
+		int key;
+		int request;
+	} key_map[] = {
+		{ KEY_DOWN, REQ_DOWN_ITEM },
+		{ KEY_UP, REQ_UP_ITEM },
+		{ KEY_HOME, REQ_FIRST_ITEM },
+		{ KEY_NPAGE, REQ_SCR_DPAGE },
+		{ KEY_PPAGE, REQ_SCR_UPAGE },
+		{ KEY_BEG, REQ_FIRST_ITEM },
+		{ KEY_END, REQ_LAST_ITEM },
+	};
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(key_map); ++i)
+		if (key_map[i].key == key) {
+			menu_driver(menu, key_map[i].request);
+			break;
+		}
+}
+
+static void on_handle_key(int key)
+{
+	switch (key) {
+	case 27:
+	case KEY_CANCEL:
+	case 'q':
+	case 'Q':
+		list_widget.close();
+		break;
+	case 10:
+	case 13:
+	case KEY_ENTER:
+		on_key_enter();
+		break;
+	default:
+		on_menu_key(key);
+		break;
+	}
+}
+
+static bool create(void)
+{
+	int rows, columns;
+	const char *title;
+
+	if (screen_lines < 3 || screen_cols < 10) {
+		beep();
+		list_widget.close();
+		return FALSE;
+	}
+	scale_menu(menu, &rows, &columns);
+	rows += 2;
+	columns += 2;
+	if (rows > screen_lines)
+		rows = screen_lines;
+	if (columns > screen_cols)
+		columns = screen_cols;
+
+	widget_init(&list_widget, rows, columns, SCREEN_CENTER, SCREEN_CENTER,
+		    attr_menu, WIDGET_BORDER | WIDGET_SUBWINDOW);
+
+	title = _("Sound Card");
+	mvwprintw(list_widget.window, 0, (columns - 2 - get_mbs_width(title)) / 2, " %s ", title);
+	set_menu_win(menu, list_widget.window);
+	set_menu_sub(menu, list_widget.subwindow);
+	return TRUE;
+}
+
+static void on_window_size_changed(void)
+{
+	unpost_menu(menu);
+	if (!create())
+		return;
+	post_menu(menu);
+}
+
+static void on_close(void)
+{
+	unsigned int i;
+	struct card *card, *next_card;
+
+	unpost_menu(menu);
+	free_menu(menu);
+	for (i = 0; items[i]; ++i)
+		free_item(items[i]);
+	free(items);
+	for (card = first_card.next; card; card = next_card) {
+		next_card = card->next;
+		free(card->indexstr);
+		free(card->name);
+		free(card->device_name);
+		free(card);
+	}
+	widget_free(&list_widget);
+}
+
+void close_card_select_list(void)
+{
+	on_close();
+}
+
+static struct widget list_widget = {
+	.handle_key = on_handle_key,
+	.window_size_changed = on_window_size_changed,
+	.close = on_close,
+};
+
+static int get_cards(void)
+{
+	int count, number, err;
+	snd_ctl_t *ctl;
+	snd_ctl_card_info_t *info;
+	char buf[16];
+	struct card *card, *prev_card;
+
+	first_card.indexstr = "-";
+	first_card.name = _("(default)");
+	first_card.device_name = "default";
+	count = 1;
+
+	snd_ctl_card_info_alloca(&info);
+	prev_card = &first_card;
+	number = -1;
+	for (;;) {
+		err = snd_card_next(&number);
+		if (err < 0)
+			fatal_alsa_error(_("cannot enumerate sound cards"), err);
+		if (number < 0)
+			break;
+		sprintf(buf, "hw:%d", number);
+		err = snd_ctl_open(&ctl, buf, 0);
+		if (err < 0)
+			continue;
+		err = snd_ctl_card_info(ctl, info);
+		snd_ctl_close(ctl);
+		if (err < 0)
+			continue;
+		card = ccalloc(1, sizeof *card);
+		sprintf(buf, "%d", number);
+		card->indexstr = cstrdup(buf);
+		card->name = cstrdup(snd_ctl_card_info_get_name(info));
+		sprintf(buf, "hw:%d", number);
+		card->device_name = cstrdup(buf);
+		prev_card->next = card;
+		prev_card = card;
+		++count;
+	}
+
+	card = ccalloc(1, sizeof *card);
+	card->indexstr = cstrdup(" ");
+	card->name = cstrdup(_("enter device name..."));
+	prev_card->next = card;
+	++count;
+
+	return count;
+}
+
+static void create_list_items(int cards)
+{
+	int i;
+	struct card *card;
+	ITEM *item;
+
+	initial_item = NULL;
+	items = ccalloc(cards + 1, sizeof(ITEM*));
+	i = 0;
+	for (card = &first_card; card; card = card->next) {
+		item = new_item(card->indexstr, card->name);
+		if (!item)
+			fatal_error("cannot create menu item");
+		set_item_userptr(item, card);
+		items[i++] = item;
+		if (!initial_item &&
+		    mixer_device_name &&
+		    (!card->device_name ||
+		     !strcmp(card->device_name, mixer_device_name)))
+			initial_item = item;
+	}
+	assert(i == cards);
+}
+
+void create_card_select_list(void)
+{
+	int cards;
+
+	cards = get_cards();
+	create_list_items(cards);
+
+	menu = new_menu(items);
+	if (!menu)
+		fatal_error("cannot create menu");
+	set_menu_fore(menu, attr_menu_selected);
+	set_menu_back(menu, attr_menu);
+	set_menu_mark(menu, NULL);
+	if (initial_item)
+		set_current_item(menu, initial_item);
+	set_menu_spacing(menu, 2, 1, 1);
+	menu_opts_on(menu, O_SHOWDESC);
+
+	if (!create())
+		return;
+
+	post_menu(menu);
+}
diff --git a/alsamixer/card_select.h b/alsamixer/card_select.h
new file mode 100644
index 0000000..4ba15fc
--- /dev/null
+++ b/alsamixer/card_select.h
@@ -0,0 +1,7 @@
+#ifndef CARD_SELECT_H_INCLUDED
+#define CARD_SELECT_H_INCLUDED
+
+void create_card_select_list(void);
+void close_card_select_list(void);
+
+#endif
diff --git a/alsamixer/cli.c b/alsamixer/cli.c
new file mode 100644
index 0000000..bb4f030
--- /dev/null
+++ b/alsamixer/cli.c
@@ -0,0 +1,135 @@
+/*
+ * alsamixer - curses mixer for the ALSA project
+ * Copyright (c) 1998,1999 Tim Janik
+ *                         Jaroslav Kysela <perex@perex.cz>
+ * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+#include "gettext_curses.h"
+#include "mixer_widget.h"
+#include "mainloop.h"
+
+static int use_color = 1;
+static struct snd_mixer_selem_regopt selem_regopt = {
+	.ver = 1,
+	.abstract = SND_MIXER_SABSTRACT_NONE,
+	.device = "default",
+};
+
+static void show_help(void)
+{
+	puts(_("Usage: alsamixer [options]"));
+	puts(_("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"));
+	puts(_("Debugging options:\n"
+	       "  -g, --no-color          toggle using of colors\n"
+	       "  -a, --abstraction=NAME  mixer abstraction level: none/basic"));
+}
+
+static void parse_options(int argc, char *argv[])
+{
+	static const char short_options[] = "hc:D:V:gsa:";
+	static const struct option long_options[] = {
+		{ .name = "help", .val = 'h' },
+		{ .name = "card", .has_arg = 1, .val = 'c' },
+		{ .name = "device", .has_arg = 1, .val = 'D' },
+		{ .name = "view", .has_arg = 1, .val = 'V' },
+		{ .name = "no-color", .val = 'g' },
+		{ .name = "abstraction", .has_arg = 1, .val = 'a' },
+		{ }
+	};
+	int option;
+	int card_index;
+	static char name_buf[16];
+
+	while ((option = getopt_long(argc, argv, short_options,
+				     long_options, NULL)) != -1) {
+		switch (option) {
+		case '?':
+		case 'h':
+			show_help();
+			exit(EXIT_SUCCESS);
+		case 'c':
+			card_index = snd_card_get_index(optarg);
+			if (card_index < 0) {
+				fprintf(stderr, _("invalid card index: %s\n"), optarg);
+				goto fail;
+			}
+			sprintf(name_buf, "hw:%d", card_index);
+			selem_regopt.device = name_buf;
+			break;
+		case 'D':
+			selem_regopt.device = optarg;
+			break;
+		case 'V':
+			if (*optarg == 'p' || *optarg == 'P')
+				view_mode = VIEW_MODE_PLAYBACK;
+			else if (*optarg == 'c' || *optarg == 'C')
+				view_mode = VIEW_MODE_CAPTURE;
+			else
+				view_mode = VIEW_MODE_ALL;
+			break;
+		case 'g':
+			use_color = !use_color;
+			break;
+		case 'a':
+			if (!strcmp(optarg, "none"))
+				selem_regopt.abstract = SND_MIXER_SABSTRACT_NONE;
+			else if (!strcmp(optarg, "basic"))
+				selem_regopt.abstract = SND_MIXER_SABSTRACT_BASIC;
+			else {
+				fprintf(stderr, _("unknown abstraction level: %s\n"), optarg);
+				goto fail;
+			}
+			break;
+		default:
+			fprintf(stderr, _("unknown option: %c\n"), option);
+fail:
+			fputs(_("try `alsamixer --help' for more information\n"), stderr);
+			exit(EXIT_FAILURE);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	setlocale(LC_ALL, "");
+#ifdef ENABLE_NLS_IN_CURSES
+	textdomain(PACKAGE);
+#endif
+
+	parse_options(argc, argv);
+
+	create_mixer_object(&selem_regopt);
+
+	initialize_curses(use_color);
+
+	create_mixer_widget();
+
+	mainloop();
+
+	app_shutdown();
+	return 0;
+}
diff --git a/alsamixer/colors.c b/alsamixer/colors.c
new file mode 100644
index 0000000..b4b98e5
--- /dev/null
+++ b/alsamixer/colors.c
@@ -0,0 +1,119 @@
+/*
+ * colors.c - color and attribute definitions
+ * Copyright (c) 1998,1999 Tim Janik
+ *                         Jaroslav Kysela <perex@perex.cz>
+ * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include CURSESINC
+#include "colors.h"
+
+int attr_mixer_frame;
+int attr_mixer_text;
+int attr_mixer_active;
+int attr_ctl_frame;
+int attr_ctl_mute;
+int attr_ctl_nomute;
+int attr_ctl_capture;
+int attr_ctl_nocapture;
+int attr_ctl_label;
+int attr_ctl_label_focus;
+int attr_ctl_mark_focus;
+int attr_ctl_bar_lo;
+#ifdef TRICOLOR_VOLUME_BAR
+int attr_ctl_bar_mi;
+int attr_ctl_bar_hi;
+#endif
+int attr_ctl_inactive;
+int attr_ctl_label_inactive;
+int attr_errormsg;
+int attr_infomsg;
+int attr_textbox;
+int attr_textfield;
+int attr_menu;
+int attr_menu_selected;
+
+void init_colors(int use_color)
+{
+	if (!!has_colors() == !!use_color) {
+		start_color();
+
+		init_pair(1, COLOR_CYAN, COLOR_BLACK);
+		init_pair(2, COLOR_YELLOW, COLOR_BLACK);
+		init_pair(3, COLOR_WHITE, COLOR_GREEN);
+		init_pair(4, COLOR_RED, COLOR_BLACK);
+		init_pair(5, COLOR_WHITE, COLOR_BLACK);
+		init_pair(6, COLOR_WHITE, COLOR_BLUE);
+		init_pair(7, COLOR_RED, COLOR_BLUE);
+		init_pair(8, COLOR_GREEN, COLOR_GREEN);
+		init_pair(9, COLOR_WHITE, COLOR_RED);
+#ifdef TRICOLOR_VOLUME_BAR
+		init_pair(10, COLOR_WHITE, COLOR_WHITE);
+		init_pair(11, COLOR_RED, COLOR_RED);
+#endif
+
+		attr_mixer_frame = COLOR_PAIR(1);
+		attr_mixer_text = COLOR_PAIR(1);
+		attr_mixer_active = A_BOLD | COLOR_PAIR(2);
+		attr_ctl_frame = A_BOLD | COLOR_PAIR(1);
+		attr_ctl_mute = COLOR_PAIR(1);
+		attr_ctl_nomute = A_BOLD | COLOR_PAIR(3);
+		attr_ctl_capture = A_BOLD | COLOR_PAIR(4);
+		attr_ctl_nocapture = COLOR_PAIR(5);
+		attr_ctl_label = A_BOLD | COLOR_PAIR(6);
+		attr_ctl_label_focus = A_BOLD | COLOR_PAIR(7);
+		attr_ctl_mark_focus = A_BOLD | COLOR_PAIR(4);
+		attr_ctl_bar_lo = A_BOLD | COLOR_PAIR(8);
+#ifdef TRICOLOR_VOLUME_BAR
+		attr_ctl_bar_mi = A_BOLD | COLOR_PAIR(10);
+		attr_ctl_bar_hi = A_BOLD | COLOR_PAIR(11);
+#endif
+		attr_ctl_inactive = COLOR_PAIR(5);
+		attr_ctl_label_inactive = A_REVERSE | COLOR_PAIR(5);
+		attr_errormsg = A_BOLD | COLOR_PAIR(9);
+		attr_infomsg = A_BOLD | COLOR_PAIR(6);
+		attr_textbox = A_BOLD | COLOR_PAIR(6);
+		attr_textfield = A_REVERSE | COLOR_PAIR(5);
+		attr_menu = A_BOLD | COLOR_PAIR(6);
+		attr_menu_selected = A_REVERSE | COLOR_PAIR(6);
+	} else {
+		attr_mixer_frame = A_NORMAL;
+		attr_mixer_text = A_NORMAL;
+		attr_mixer_active = A_BOLD;
+		attr_ctl_frame = A_BOLD;
+		attr_ctl_mute = A_NORMAL;
+		attr_ctl_nomute = A_BOLD;
+		attr_ctl_capture = A_BOLD;
+		attr_ctl_nocapture = A_NORMAL;
+		attr_ctl_label = A_REVERSE;
+		attr_ctl_label_focus = A_REVERSE | A_BOLD;
+		attr_ctl_mark_focus = A_BOLD;
+		attr_ctl_bar_lo = A_BOLD;
+#ifdef TRICOLOR_VOLUME_BAR
+		attr_ctl_bar_mi = A_BOLD;
+		attr_ctl_bar_hi = A_BOLD;
+#endif
+		attr_ctl_inactive = A_NORMAL;
+		attr_ctl_label_inactive = A_REVERSE;
+		attr_errormsg = A_STANDOUT;
+		attr_infomsg = A_NORMAL;
+		attr_textbox = A_NORMAL;
+		attr_textfield = A_REVERSE;
+		attr_menu = A_NORMAL;
+		attr_menu_selected = A_REVERSE;
+	}
+}
diff --git a/alsamixer/colors.h b/alsamixer/colors.h
new file mode 100644
index 0000000..9396004
--- /dev/null
+++ b/alsamixer/colors.h
@@ -0,0 +1,33 @@
+#ifndef COLORS_H_INCLUDED
+#define COLORS_H_INCLUDED
+
+#define TRICOLOR_VOLUME_BAR
+
+extern int attr_mixer_frame;
+extern int attr_mixer_text;
+extern int attr_mixer_active;
+extern int attr_ctl_frame;
+extern int attr_ctl_mute;
+extern int attr_ctl_nomute;
+extern int attr_ctl_capture;
+extern int attr_ctl_nocapture;
+extern int attr_ctl_label;
+extern int attr_ctl_label_focus;
+extern int attr_ctl_mark_focus;
+extern int attr_ctl_bar_lo;
+#ifdef TRICOLOR_VOLUME_BAR
+extern int attr_ctl_bar_mi;
+extern int attr_ctl_bar_hi;
+#endif
+extern int attr_ctl_inactive;
+extern int attr_ctl_label_inactive;
+extern int attr_errormsg;
+extern int attr_infomsg;
+extern int attr_textbox;
+extern int attr_textfield;
+extern int attr_menu;
+extern int attr_menu_selected;
+
+void init_colors(int use_color);
+
+#endif
diff --git a/alsamixer/device_name.c b/alsamixer/device_name.c
new file mode 100644
index 0000000..c58e652
--- /dev/null
+++ b/alsamixer/device_name.c
@@ -0,0 +1,197 @@
+/*
+ * device_name_form.c - ask for sound control device name
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdlib.h>
+#include <string.h>
+#include CURSESINC
+#include <form.h>
+#include "gettext_curses.h"
+#include "die.h"
+#include "mem.h"
+#include "utils.h"
+#include "colors.h"
+#include "widget.h"
+#include "mixer_widget.h"
+#include "card_select.h"
+#include "device_name.h"
+
+static struct widget form_widget;
+static FIELD *fields[3];
+static FORM *form;
+
+static char *dup_current_name(void)
+{
+	int rows, cols, max, i;
+	char *s;
+
+	if (form_driver(form, REQ_VALIDATION) == E_OK) {
+		dynamic_field_info(fields[1], &rows, &cols, &max);
+		s = ccalloc(1, cols + 1);
+		memcpy(s, field_buffer(fields[1], 0), cols);
+		for (i = strlen(s) - 1; i >= 0 && s[i] == ' '; --i)
+			s[i] = '\0';
+		return s;
+	} else {
+		return cstrdup("");
+	}
+}
+
+static void on_key_enter(void)
+{
+	char *s;
+	bool ok;
+
+	s = dup_current_name();
+	ok = select_card_by_name(s);
+	free(s);
+	if (ok) {
+		form_widget.close();
+		close_card_select_list();
+	}
+}
+
+static void on_form_key(int key)
+{
+	static const struct {
+		int key;
+		int request;
+	} key_map[] = {
+		{ KEY_LEFT, REQ_PREV_CHAR },
+		{ KEY_RIGHT, REQ_NEXT_CHAR },
+		{ KEY_HOME, REQ_BEG_FIELD },
+		{ KEY_BACKSPACE, REQ_DEL_PREV },
+		{ KEY_DC, REQ_DEL_CHAR },
+		{ KEY_BEG, REQ_BEG_FIELD },
+		{ KEY_END, REQ_END_FIELD },
+	};
+	unsigned int i;
+
+	if (key >= 32 && key < 256) {
+		form_driver(form, key);
+		return;
+	}
+	for (i = 0; i < ARRAY_SIZE(key_map); ++i)
+		if (key_map[i].key == key) {
+			form_driver(form, key_map[i].request);
+			break;
+		}
+}
+
+static void on_handle_key(int key)
+{
+	switch (key) {
+	case 27:
+	case KEY_CANCEL:
+		form_widget.close();
+		break;
+	case 10:
+	case 13:
+	case KEY_ENTER:
+		on_key_enter();
+		break;
+	default:
+		on_form_key(key);
+		break;
+	}
+}
+
+static bool create(void)
+{
+	const char *title;
+
+	if (screen_lines < 6 || screen_cols < 36) {
+		form_widget.close();
+		beep();
+		return FALSE;
+	}
+	widget_init(&form_widget,
+		    6, 36, SCREEN_CENTER, SCREEN_CENTER,
+		    attr_textbox, WIDGET_BORDER | WIDGET_SUBWINDOW | WIDGET_CURSOR_VISIBLE);
+	title = _("Sound Card");
+	mvwprintw(form_widget.window, 0, (36 - 2 - get_mbs_width(title)) / 2, " %s ", title);
+
+	set_form_win(form, form_widget.window);
+	set_form_sub(form, form_widget.subwindow);
+	return TRUE;
+}
+
+static void on_window_size_changed(void)
+{
+	form_driver(form, REQ_VALIDATION); /* save field value */
+	unpost_form(form);
+
+	if (!create())
+		return;
+
+	/*
+	 * This call fails because ncurses does not allow changing options of
+	 * the current field, and we cannot change the current field because
+	 * there is only one.  The only way to make this work would be to throw
+	 * away and recreate all fields.
+	 */
+	field_opts_off(fields[1], O_BLANK);
+
+	post_form(form);
+}
+
+static void on_close(void)
+{
+	unpost_form(form);
+	free_form(form);
+	free_field(fields[0]);
+	free_field(fields[1]);
+	widget_free(&form_widget);
+}
+
+static struct widget form_widget = {
+	.handle_key = on_handle_key,
+	.window_size_changed = on_window_size_changed,
+	.close = on_close,
+};
+
+void create_device_name_form(void)
+{
+	fields[0] = new_field(1, 32, 1, 1, 0, 0);
+	if (!fields[0])
+		fatal_error("cannot create field");
+	field_opts_off(fields[0], O_ACTIVE);
+	field_opts_off(fields[0], O_EDIT);
+	set_field_fore(fields[0], attr_textbox);
+	set_field_back(fields[0], attr_textbox);
+	set_field_buffer(fields[0], 0, _("Device name:"));
+
+	fields[1] = new_field(1, 32, 2, 1, 0, 0);
+	if (!fields[1])
+		fatal_error("cannot create field");
+	field_opts_off(fields[1], O_AUTOSKIP);
+	field_opts_off(fields[1], O_NULLOK);
+	field_opts_off(fields[1], O_STATIC);
+	set_field_fore(fields[1], attr_textfield);
+	set_field_back(fields[1], attr_textfield);
+	set_field_buffer(fields[1], 0, mixer_device_name);
+
+	form = new_form(fields);
+	if (!form)
+		fatal_error("cannot create form");
+
+	if (!create())
+		return;
+
+	post_form(form);
+}
diff --git a/alsamixer/device_name.h b/alsamixer/device_name.h
new file mode 100644
index 0000000..f4a1f3f
--- /dev/null
+++ b/alsamixer/device_name.h
@@ -0,0 +1,6 @@
+#ifndef DEVICE_NAME_FORM_H_INCLUDED
+#define DEVICE_NAME_FORM_H_INCLUDED
+
+void create_device_name_form(void);
+
+#endif
diff --git a/alsamixer/die.c b/alsamixer/die.c
new file mode 100644
index 0000000..899a501
--- /dev/null
+++ b/alsamixer/die.c
@@ -0,0 +1,39 @@
+/*
+ * die.c - error handlers
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <alsa/asoundlib.h>
+#include "gettext_curses.h"
+#include "mainloop.h"
+#include "die.h"
+
+void fatal_error(const char *msg)
+{
+	app_shutdown();
+	fprintf(stderr, "%s\n", msg);
+	exit(EXIT_FAILURE);
+}
+
+void fatal_alsa_error(const char *msg, int err)
+{
+	app_shutdown();
+	fprintf(stderr, _("%s: %s\n"), msg, snd_strerror(err));
+	exit(EXIT_FAILURE);
+}
diff --git a/alsamixer/die.h b/alsamixer/die.h
new file mode 100644
index 0000000..39ef1c0
--- /dev/null
+++ b/alsamixer/die.h
@@ -0,0 +1,7 @@
+#ifndef DIE_H_INCLUDED
+#define DIE_H_INCLUDED
+
+void fatal_error(const char *msg) __attribute__((__noreturn__));
+void fatal_alsa_error(const char *msg, int err) __attribute__((__noreturn__));
+
+#endif
diff --git a/alsamixer/go b/alsamixer/go
new file mode 100755
index 0000000..c33c298
--- /dev/null
+++ b/alsamixer/go
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+#GDB="gdb --args"
+GDB=""
+
+ALSA_MIXER_SIMPLE_MODULES="$HOME/hg/alsa-lib/modules/mixer/simple/.libs" \
+ALSA_MIXER_SIMPLE="$HOME/hg/alsa-lib/src/conf/smixer.conf" \
+ALSA_MIXER_SIMPLE_MPYTHON="$HOME/hg/alsa-lib/modules/mixer/simple/python/main.py" \
+LD_PRELOAD="$HOME/hg/alsa-lib/src/.libs/libasound.so" \
+$GDB ./alsamixer "$@"
diff --git a/alsamixer/mainloop.c b/alsamixer/mainloop.c
new file mode 100644
index 0000000..dbfef9b
--- /dev/null
+++ b/alsamixer/mainloop.c
@@ -0,0 +1,135 @@
+/*
+ * mainloop.c - main loop
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <poll.h>
+#include <panel.h>
+#include <alsa/asoundlib.h>
+#include "mem.h"
+#include "die.h"
+#include "colors.h"
+#include "widget.h"
+#include "mixer_widget.h"
+#include "mixer_display.h"
+#include "mainloop.h"
+
+static WINDOW *curses_initialized;
+
+static void black_hole_error_handler(const char *file, int line,
+				     const char *function, int err,
+				     const char *fmt, ...)
+{
+}
+
+void initialize_curses(bool use_color)
+{
+	curses_initialized = initscr();
+	cbreak();
+	noecho();
+#ifdef HAVE_CURSES_ESCDELAY
+	set_escdelay(100);
+#endif
+	window_size_changed(); /* update screen_lines/cols */
+	init_colors(use_color);
+	snd_lib_error_set_handler(black_hole_error_handler);
+}
+
+void app_shutdown(void)
+{
+	if (curses_initialized) {
+		clear();
+		refresh();
+		curs_set(1);
+		endwin();
+	}
+	mixer_shutdown();
+}
+
+void mainloop(void)
+{
+	struct pollfd *pollfds = NULL;
+	int nfds = 0, n;
+	struct widget *active_widget;
+	unsigned short revents;
+	int key;
+	int err;
+
+	for (;;) {
+		update_panels();
+		doupdate();
+
+		active_widget = get_active_widget();
+		if (!active_widget)
+			break;
+
+		n = 1 + snd_mixer_poll_descriptors_count(mixer);
+		if (n != nfds) {
+			free(pollfds);
+			nfds = n;
+			pollfds = ccalloc(nfds, sizeof *pollfds);
+			pollfds[0].fd = fileno(stdin);
+			pollfds[0].events = POLLIN;
+		}
+		err = snd_mixer_poll_descriptors(mixer, &pollfds[1], nfds - 1);
+		if (err < 0)
+			fatal_alsa_error("cannot get poll descriptors", err);
+		n = poll(pollfds, nfds, -1);
+		if (n < 0) {
+			if (errno == EINTR) {
+				pollfds[0].revents = 0;
+				doupdate(); /* handle SIGWINCH */
+			} else {
+				fatal_error("poll error");
+			}
+		}
+		if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
+			break;
+		if (pollfds[0].revents & POLLIN)
+			--n;
+		if (n > 0) {
+			err = snd_mixer_poll_descriptors_revents(mixer, &pollfds[1], nfds - 1, &revents);
+			if (err < 0)
+				fatal_alsa_error("cannot get poll events", err);
+			if (revents & (POLLERR | POLLNVAL))
+				close_mixer_device();
+			else if (revents & POLLIN)
+				snd_mixer_handle_events(mixer);
+		}
+		key = wgetch(active_widget->window);
+		while (key != ERR) {
+#ifdef KEY_RESIZE
+			if (key == KEY_RESIZE)
+				window_size_changed();
+			else
+#endif
+				active_widget->handle_key(key);
+			active_widget = get_active_widget();
+			if (!active_widget)
+				break;
+			key = wgetch(active_widget->window);
+		}
+		if (!active_widget)
+			break;
+		if (controls_changed)
+			display_controls();
+	}
+	free(pollfds);
+}
diff --git a/alsamixer/mainloop.h b/alsamixer/mainloop.h
new file mode 100644
index 0000000..22317be
--- /dev/null
+++ b/alsamixer/mainloop.h
@@ -0,0 +1,10 @@
+#ifndef MAINLOOP_H_INCLUDED
+#define MAINLOOP_H_INCLUDED
+
+#include CURSESINC
+
+void initialize_curses(bool use_color);
+void mainloop(void);
+void app_shutdown(void);
+
+#endif
diff --git a/alsamixer/mem.c b/alsamixer/mem.c
new file mode 100644
index 0000000..fa03a89
--- /dev/null
+++ b/alsamixer/mem.c
@@ -0,0 +1,68 @@
+/*
+ * mem.c - memory allocation checkers
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+#include "aconfig.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include "die.h"
+#include "mem.h"
+
+static void check(void *p)
+{
+	if (!p)
+		fatal_error("out of memory");
+}
+
+void *ccalloc(size_t n, size_t size)
+{
+	void *mem = calloc(n, size);
+	if (n && size)
+		check(mem);
+	return mem;
+}
+
+void *crealloc(void *ptr, size_t new_size)
+{
+	ptr = realloc(ptr, new_size);
+	if (new_size)
+		check(ptr);
+	return ptr;
+}
+
+char *cstrdup(const char *s)
+{
+	char *str = strdup(s);
+	check(str);
+	return str;
+}
+
+char *casprintf(const char *fmt, ...)
+{
+	va_list ap;
+	char *str;
+
+	va_start(ap, fmt);
+	if (vasprintf(&str, fmt, ap) < 0)
+		check(NULL);
+	va_end(ap);
+	return str;
+}
diff --git a/alsamixer/mem.h b/alsamixer/mem.h
new file mode 100644
index 0000000..d0e5f54
--- /dev/null
+++ b/alsamixer/mem.h
@@ -0,0 +1,11 @@
+#ifndef MEM_H_INCLUDED
+#define MEM_H_INCLUDED
+
+#include <stddef.h>
+
+void *ccalloc(size_t n, size_t size);
+void *crealloc(void *ptr, size_t new_size);
+char *cstrdup(const char *s);
+char *casprintf(const char *fmt, ...) __attribute__((__format__(printf, 1, 2)));
+
+#endif
diff --git a/alsamixer/mixer_controls.c b/alsamixer/mixer_controls.c
new file mode 100644
index 0000000..cc98b64
--- /dev/null
+++ b/alsamixer/mixer_controls.c
@@ -0,0 +1,521 @@
+/*
+ * mixer_controls.c - handles mixer controls and mapping from selems
+ * Copyright (c) 1998,1999 Tim Janik
+ *                         Jaroslav Kysela <perex@perex.cz>
+ * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include CURSESINC
+#include <alsa/asoundlib.h>
+#include "utils.h"
+#include "mem.h"
+#include "mixer_display.h"
+#include "mixer_widget.h"
+#include "mixer_controls.h"
+
+struct control *controls;
+unsigned int controls_count;
+
+static const snd_mixer_selem_channel_id_t supported_channels[] = {
+	SND_MIXER_SCHN_FRONT_LEFT,
+	SND_MIXER_SCHN_FRONT_RIGHT,
+	SND_MIXER_SCHN_REAR_LEFT,
+	SND_MIXER_SCHN_REAR_RIGHT,
+	SND_MIXER_SCHN_FRONT_CENTER,
+	SND_MIXER_SCHN_WOOFER,
+	SND_MIXER_SCHN_SIDE_LEFT,
+	SND_MIXER_SCHN_SIDE_RIGHT,
+};
+#define LAST_SUPPORTED_CHANNEL SND_MIXER_SCHN_SIDE_RIGHT
+
+static const snd_mixer_selem_channel_id_t control_channels[][2] = {
+	{ SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT },
+	{ SND_MIXER_SCHN_REAR_LEFT, SND_MIXER_SCHN_REAR_RIGHT },
+	{ SND_MIXER_SCHN_FRONT_CENTER, SND_MIXER_SCHN_UNKNOWN },
+	{ SND_MIXER_SCHN_WOOFER, SND_MIXER_SCHN_UNKNOWN },
+	{ SND_MIXER_SCHN_SIDE_LEFT, SND_MIXER_SCHN_SIDE_RIGHT },
+};
+
+bool are_there_any_controls(void)
+{
+	snd_mixer_elem_t *elem;
+	unsigned int i;
+
+	for (elem = snd_mixer_first_elem(mixer);
+	     elem;
+	     elem = snd_mixer_elem_next(elem)) {
+		if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
+			continue;
+		if (snd_mixer_selem_is_enumerated(elem))
+			return TRUE;
+		if (snd_mixer_selem_has_playback_volume_joined(elem) ||
+		    snd_mixer_selem_has_capture_volume_joined(elem) ||
+		    snd_mixer_selem_has_playback_switch_joined(elem) ||
+		    snd_mixer_selem_has_capture_switch_joined(elem))
+			return TRUE;
+		for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
+			if (snd_mixer_selem_has_playback_channel(elem, supported_channels[i]) ||
+			    snd_mixer_selem_has_capture_channel(elem, supported_channels[i]))
+				return TRUE;
+	}
+	return FALSE;
+}
+
+static bool has_more_than_front_capture_channels(snd_mixer_elem_t *elem)
+{
+	unsigned int i;
+
+	for (i = 2; i < ARRAY_SIZE(supported_channels); ++i)
+		if (snd_mixer_selem_has_capture_channel(elem, supported_channels[i]))
+			return TRUE;
+	return FALSE;
+}
+
+static bool has_any_control_channel(snd_mixer_elem_t *elem,
+				    const snd_mixer_selem_channel_id_t channels[2],
+				    int (*has_channel)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t))
+{
+	return has_channel(elem, channels[0]) ||
+	       (channels[1] != SND_MIXER_SCHN_UNKNOWN && has_channel(elem, channels[1]));
+}
+
+static bool has_merged_cswitch(snd_mixer_elem_t *elem)
+{
+	bool pvol, psw;
+	unsigned int i;
+
+	pvol = snd_mixer_selem_has_playback_volume(elem);
+	psw = snd_mixer_selem_has_playback_switch(elem);
+	if ((pvol || psw) &&
+	    snd_mixer_selem_has_capture_switch(elem) &&
+	    !snd_mixer_selem_has_capture_volume(elem)) {
+		if (snd_mixer_selem_has_capture_switch_joined(elem))
+			return TRUE;
+		else if (((pvol && snd_mixer_selem_has_playback_volume_joined(elem)) ||
+			  (psw && snd_mixer_selem_has_playback_switch_joined(elem))) &&
+			 has_more_than_front_capture_channels(elem))
+			return FALSE;
+		for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
+			if (has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_capture_channel) &&
+			    !has_any_control_channel(elem, control_channels[i], snd_mixer_selem_has_playback_channel))
+				return FALSE;
+		}
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static unsigned int get_playback_controls_count(snd_mixer_elem_t *elem)
+{
+	unsigned int count = 0;
+	unsigned int i;
+	int has_vol, has_sw;
+
+	has_vol = snd_mixer_selem_has_playback_volume(elem);
+	has_sw = snd_mixer_selem_has_playback_switch(elem);
+	if (!has_vol && !has_sw)
+		return 0;
+	if ((!has_vol || snd_mixer_selem_has_playback_volume_joined(elem)) &&
+	    (!has_sw || snd_mixer_selem_has_playback_switch_joined(elem)))
+		return 1;
+	for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
+		if (snd_mixer_selem_has_playback_channel(elem, control_channels[i][0]) ||
+		    (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
+		     snd_mixer_selem_has_playback_channel(elem, control_channels[i][1])))
+			++count;
+	}
+	return count;
+}
+
+static unsigned int get_capture_controls_count(snd_mixer_elem_t *elem)
+{
+	unsigned int count = 0;
+	unsigned int i;
+	int has_vol, has_sw;
+
+	has_vol = snd_mixer_selem_has_capture_volume(elem);
+	has_sw = snd_mixer_selem_has_capture_switch(elem);
+	if ((!has_vol && !has_sw) ||
+	    (view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem)))
+		return 0;
+	if ((!has_vol || snd_mixer_selem_has_capture_volume_joined(elem)) &&
+	    (!has_sw || snd_mixer_selem_has_capture_switch_joined(elem)))
+		return 1;
+	for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
+		if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0]) ||
+		    (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
+		     snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])))
+			++count;
+	}
+	return count;
+}
+
+static unsigned int get_controls_count_for_elem(snd_mixer_elem_t *elem)
+{
+	unsigned int p, c;
+
+	if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
+		return 0;
+	if (snd_mixer_selem_is_enumerated(elem)) {
+		switch (view_mode) {
+		case VIEW_MODE_PLAYBACK:
+			return snd_mixer_selem_is_enum_capture(elem) ? 0 : 1;
+		case VIEW_MODE_CAPTURE:
+			return snd_mixer_selem_is_enum_capture(elem) ? 1 : 0;
+		case VIEW_MODE_ALL:
+		default:
+			return 1;
+		}
+	}
+	switch (view_mode) {
+	case VIEW_MODE_PLAYBACK:
+		return get_playback_controls_count(elem);
+	case VIEW_MODE_CAPTURE:
+		return get_capture_controls_count(elem);
+	case VIEW_MODE_ALL:
+	default:
+		p = get_playback_controls_count(elem);
+		c = get_capture_controls_count(elem);
+		return has_merged_cswitch(elem) ? p : p + c;
+	}
+}
+
+static void create_name(struct control *control)
+{
+	unsigned int index;
+	char *s;
+
+	index = snd_mixer_selem_get_index(control->elem);
+	if (index > 0)
+		control->name = casprintf("%s %u", snd_mixer_selem_get_name(control->elem), index);
+	else
+		control->name = cstrdup(snd_mixer_selem_get_name(control->elem));
+
+	while ((s = strstr(control->name, "IEC958")) != NULL)
+		memcpy(s, "S/PDIF", 6);
+}
+
+static unsigned int create_controls_for_elem(snd_mixer_elem_t *elem, struct control *control)
+{
+	unsigned int count = 0;
+	unsigned int i;
+	unsigned int multich_flag;
+	unsigned int enum_index;
+	struct control *front_control = NULL;
+	bool has_pvol, has_psw;
+	bool has_cvol, has_csw;
+	bool has_channel[LAST_SUPPORTED_CHANNEL + 1];
+	bool merged_cswitch;
+	bool has_ch0, has_ch1;
+
+	if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
+		return 0;
+	if (snd_mixer_selem_is_enumerated(elem)) {
+		if ((view_mode == VIEW_MODE_PLAYBACK && snd_mixer_selem_is_enum_capture(elem)) ||
+		    (view_mode == VIEW_MODE_CAPTURE && !snd_mixer_selem_is_enum_capture(elem)))
+			return 0;
+		control->elem = elem;
+		control->flags = TYPE_ENUM;
+		control->enum_channel_bits = 0;
+		for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
+			if (snd_mixer_selem_get_enum_item(control->elem, (snd_mixer_selem_channel_id_t)i, &enum_index) >= 0)
+				control->enum_channel_bits |= 1 << i;
+		if (snd_mixer_selem_is_active(control->elem))
+			control->flags |= IS_ACTIVE;
+		create_name(control);
+		return 1;
+	}
+	has_pvol = snd_mixer_selem_has_playback_volume(elem);
+	has_psw = snd_mixer_selem_has_playback_switch(elem);
+	has_cvol = snd_mixer_selem_has_capture_volume(elem);
+	has_csw = snd_mixer_selem_has_capture_switch(elem);
+	merged_cswitch = view_mode == VIEW_MODE_ALL && has_merged_cswitch(elem);
+	if (view_mode != VIEW_MODE_CAPTURE && (has_pvol || has_psw)) {
+		if ((!has_pvol || snd_mixer_selem_has_playback_volume_joined(elem)) &&
+		    (!has_psw || snd_mixer_selem_has_playback_switch_joined(elem))) {
+			control->elem = elem;
+			if (has_pvol) {
+				control->flags |= TYPE_PVOLUME | HAS_VOLUME_0;
+				control->volume_channels[0] = 0;
+			}
+			if (has_psw) {
+				control->flags |= TYPE_PSWITCH | HAS_PSWITCH_0;
+				control->pswitch_channels[0] = 0;
+			}
+			if (merged_cswitch) {
+				control->flags |= TYPE_CSWITCH;
+				if (snd_mixer_selem_has_capture_switch_joined(elem)) {
+					control->flags |= HAS_CSWITCH_0;
+					control->cswitch_channels[0] = 0;
+				} else {
+					if (snd_mixer_selem_has_capture_channel(elem, control_channels[0][0])) {
+						control->flags |= HAS_CSWITCH_0;
+						control->cswitch_channels[0] = control_channels[0][0];
+					}
+					if (control_channels[0][1] != SND_MIXER_SCHN_UNKNOWN &&
+					    snd_mixer_selem_has_capture_channel(elem, control_channels[0][1])) {
+						control->flags |= HAS_CSWITCH_1;
+						control->cswitch_channels[1] = control_channels[0][1];
+					}
+				}
+				if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
+					control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
+					control->cswitch_channels[0] = control->cswitch_channels[1];
+				}
+			}
+			if (snd_mixer_selem_is_active(control->elem))
+				control->flags |= IS_ACTIVE;
+			create_name(control);
+			++control;
+			++count;
+		} else {
+			multich_flag = 0;
+			for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
+				has_channel[supported_channels[i]] =
+					snd_mixer_selem_has_playback_channel(elem, supported_channels[i]);
+			for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
+				has_ch0 = has_channel[control_channels[i][0]];
+				has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
+					has_channel[control_channels[i][1]];
+				if (!has_ch0 && !has_ch1)
+					continue;
+				control->elem = elem;
+				if (has_pvol) {
+					control->flags |= TYPE_PVOLUME;
+					if (snd_mixer_selem_has_playback_volume_joined(elem)) {
+						control->flags |= HAS_VOLUME_0;
+						control->volume_channels[0] = 0;
+					} else {
+						if (has_ch0) {
+							control->flags |= HAS_VOLUME_0;
+							control->volume_channels[0] = control_channels[i][0];
+						}
+						if (has_ch1) {
+							control->flags |= HAS_VOLUME_1;
+							control->volume_channels[1] = control_channels[i][1];
+						}
+					}
+				}
+				if (has_psw) {
+					control->flags |= TYPE_PSWITCH;
+					if (snd_mixer_selem_has_playback_switch_joined(elem)) {
+						control->flags |= HAS_PSWITCH_0;
+						control->pswitch_channels[0] = 0;
+					} else {
+						if (has_ch0) {
+							control->flags |= HAS_PSWITCH_0;
+							control->pswitch_channels[0] = control_channels[i][0];
+						}
+						if (has_ch1) {
+							control->flags |= HAS_PSWITCH_1;
+							control->pswitch_channels[1] = control_channels[i][1];
+						}
+					}
+				}
+				if (merged_cswitch) {
+					control->flags |= TYPE_CSWITCH;
+					if (snd_mixer_selem_has_capture_switch_joined(elem)) {
+						control->flags |= HAS_CSWITCH_0;
+						control->cswitch_channels[0] = 0;
+					} else {
+						if (snd_mixer_selem_has_capture_channel(elem, control_channels[i][0])) {
+							control->flags |= HAS_CSWITCH_0;
+							control->cswitch_channels[0] = control_channels[i][0];
+						}
+						if (control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
+						    snd_mixer_selem_has_capture_channel(elem, control_channels[i][1])) {
+							control->flags |= HAS_CSWITCH_1;
+							control->cswitch_channels[1] = control_channels[i][1];
+						}
+					}
+				}
+				if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) {
+					control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1;
+					control->volume_channels[0] = control->volume_channels[1];
+				}
+				if ((control->flags & (HAS_PSWITCH_0 | HAS_PSWITCH_1)) == HAS_PSWITCH_1) {
+					control->flags ^= HAS_PSWITCH_0 | HAS_PSWITCH_1;
+					control->pswitch_channels[0] = control->pswitch_channels[1];
+				}
+				if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
+					control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
+					control->cswitch_channels[0] = control->cswitch_channels[1];
+				}
+				if (snd_mixer_selem_is_active(control->elem))
+					control->flags |= IS_ACTIVE;
+				create_name(control);
+				if (i == 0)
+					front_control = control;
+				else {
+					front_control->flags |= IS_MULTICH | 0;
+					control->flags |= IS_MULTICH | i;
+				}
+				++control;
+				++count;
+			}
+		}
+	}
+	if (view_mode != VIEW_MODE_PLAYBACK && (has_cvol || has_csw) && !merged_cswitch) {
+		if ((!has_cvol || snd_mixer_selem_has_capture_volume_joined(elem)) &&
+		    (!has_csw || snd_mixer_selem_has_capture_switch_joined(elem))) {
+			control->elem = elem;
+			if (has_cvol) {
+				control->flags |= TYPE_CVOLUME | HAS_VOLUME_0;
+				control->volume_channels[0] = 0;
+			}
+			if (has_csw) {
+				control->flags |= TYPE_CSWITCH | HAS_CSWITCH_0;
+				control->cswitch_channels[0] = 0;
+			}
+			if (snd_mixer_selem_is_active(control->elem))
+				control->flags |= IS_ACTIVE;
+			create_name(control);
+			++control;
+			++count;
+		} else {
+			for (i = 0; i < ARRAY_SIZE(supported_channels); ++i)
+				has_channel[supported_channels[i]] =
+					snd_mixer_selem_has_capture_channel(elem, supported_channels[i]);
+			for (i = 0; i < ARRAY_SIZE(control_channels); ++i) {
+				has_ch0 = has_channel[control_channels[i][0]];
+				has_ch1 = control_channels[i][1] != SND_MIXER_SCHN_UNKNOWN &&
+					has_channel[control_channels[i][1]];
+				if (!has_ch0 && !has_ch1)
+					continue;
+				control->elem = elem;
+				if (has_cvol) {
+					control->flags |= TYPE_CVOLUME;
+					if (snd_mixer_selem_has_capture_volume_joined(elem)) {
+						control->flags |= HAS_VOLUME_0;
+						control->volume_channels[0] = 0;
+					} else {
+						if (has_ch0) {
+							control->flags |= HAS_VOLUME_0;
+							control->volume_channels[0] = control_channels[i][0];
+						}
+						if (has_ch1) {
+							control->flags |= HAS_VOLUME_1;
+							control->volume_channels[1] = control_channels[i][1];
+						}
+					}
+				}
+				if (has_csw) {
+					control->flags |= TYPE_CSWITCH;
+					if (snd_mixer_selem_has_capture_switch_joined(elem)) {
+						control->flags |= HAS_CSWITCH_0;
+						control->cswitch_channels[0] = 0;
+					} else {
+						if (has_ch0) {
+							control->flags |= HAS_CSWITCH_0;
+							control->cswitch_channels[0] = control_channels[i][0];
+						}
+						if (has_ch1) {
+							control->flags |= HAS_CSWITCH_1;
+							control->cswitch_channels[1] = control_channels[i][1];
+						}
+					}
+				}
+				if ((control->flags & (HAS_VOLUME_0 | HAS_VOLUME_1)) == HAS_VOLUME_1) {
+					control->flags ^= HAS_VOLUME_0 | HAS_VOLUME_1;
+					control->volume_channels[0] = control->volume_channels[1];
+				}
+				if ((control->flags & (HAS_CSWITCH_0 | HAS_CSWITCH_1)) == HAS_CSWITCH_1) {
+					control->flags ^= HAS_CSWITCH_0 | HAS_CSWITCH_1;
+					control->cswitch_channels[0] = control->cswitch_channels[1];
+				}
+				if (snd_mixer_selem_is_active(control->elem))
+					control->flags |= IS_ACTIVE;
+				create_name(control);
+				if (i == 0)
+					front_control = control;
+				else {
+					front_control->flags |= IS_MULTICH | 0;
+					control->flags |= IS_MULTICH | i;
+				}
+				++control;
+				++count;
+			}
+		}
+	}
+	return count;
+}
+
+static void search_for_focus_control(void)
+{
+	snd_mixer_elem_t *elem;
+	unsigned int i;
+
+	elem = snd_mixer_find_selem(mixer, current_selem_id);
+	if (elem)
+		for (i = 0; i < controls_count; ++i)
+			if (controls[i].elem == elem) {
+				focus_control_index = i;
+				for (;;) {
+					++i;
+					if (i >= controls_count || controls[i].elem != elem)
+						return;
+					if (controls[i].flags == current_control_flags) {
+						focus_control_index = i;
+						return;
+					}
+				}
+			}
+	focus_control_index = 0;
+}
+
+void free_controls(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < controls_count; ++i)
+		free(controls[i].name);
+	free(controls);
+	controls = NULL;
+	controls_count = 0;
+}
+
+void create_controls(void)
+{
+	snd_mixer_elem_t *elem;
+	struct control *control;
+
+	free_controls();
+
+	for (elem = snd_mixer_first_elem(mixer);
+	     elem;
+	     elem = snd_mixer_elem_next(elem))
+		controls_count += get_controls_count_for_elem(elem);
+
+	if (controls_count > 0) {
+		controls = ccalloc(controls_count, sizeof *controls);
+		control = controls;
+		for (elem = snd_mixer_first_elem(mixer);
+		     elem;
+		     elem = snd_mixer_elem_next(elem))
+			control += create_controls_for_elem(elem, control);
+		assert(control == controls + controls_count);
+	}
+
+	compute_controls_layout();
+	display_view_mode();
+
+	search_for_focus_control();
+	refocus_control();
+}
diff --git a/alsamixer/mixer_controls.h b/alsamixer/mixer_controls.h
new file mode 100644
index 0000000..dbb3a9d
--- /dev/null
+++ b/alsamixer/mixer_controls.h
@@ -0,0 +1,37 @@
+#ifndef MIXER_CONTROLS_H_INCLUDED
+#define MIXER_CONTROLS_H_INCLUDED
+
+#include <alsa/asoundlib.h>
+
+struct control {
+	snd_mixer_elem_t *elem;
+	char *name;
+	unsigned int flags;
+#define TYPE_PVOLUME	(1u << 4)
+#define TYPE_CVOLUME	(1u << 5)
+#define TYPE_PSWITCH	(1u << 6)
+#define TYPE_CSWITCH	(1u << 7)
+#define TYPE_ENUM	(1u << 8)
+#define HAS_VOLUME_0	(1u << 9)
+#define HAS_VOLUME_1	(1u << 10)
+#define HAS_PSWITCH_0	(1u << 11)
+#define HAS_PSWITCH_1	(1u << 12)
+#define HAS_CSWITCH_0	(1u << 13)
+#define HAS_CSWITCH_1	(1u << 14)
+#define IS_MULTICH	(1u << 15)
+#define IS_ACTIVE	(1u << 16)
+#define MULTICH_MASK	(0x0000f)
+	snd_mixer_selem_channel_id_t volume_channels[2];
+	snd_mixer_selem_channel_id_t pswitch_channels[2];
+	snd_mixer_selem_channel_id_t cswitch_channels[2];
+	unsigned int enum_channel_bits;
+};
+
+extern struct control *controls;
+extern unsigned int controls_count;
+
+bool are_there_any_controls(void);
+void create_controls(void);
+void free_controls(void);
+
+#endif
diff --git a/alsamixer/mixer_display.c b/alsamixer/mixer_display.c
new file mode 100644
index 0000000..8ba396a
--- /dev/null
+++ b/alsamixer/mixer_display.c
@@ -0,0 +1,743 @@
+/*
+ * mixer_display.c - handles displaying of mixer widget and controls
+ * Copyright (c) 1874 Lewis Carroll
+ * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, 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"
+#include "utils.h"
+#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"
+
+enum align {
+	ALIGN_LEFT,
+	ALIGN_RIGHT,
+	ALIGN_CENTER,
+};
+
+static bool screen_too_small;
+static bool has_info_items;
+
+static int info_items_left;
+static int info_items_width;
+
+static int visible_controls;
+static int first_visible_control_index;
+static int first_control_x;
+static int control_width;
+static int control_name_width;
+
+static int base_y;
+static int volume_height;
+static int cswitch_y;
+static int values_y;
+static int name_y;
+static int channel_name_y;
+
+static void display_string_in_field(int y, int x, const char *s, int width, enum align align)
+{
+	int string_width;
+	const char *s_end;
+	int spaces;
+	int cur_y, cur_x;
+
+	wmove(mixer_widget.window, y, x);
+	string_width = width;
+	s_end = mbs_at_width(s, &string_width, -1);
+	if (string_width >= width) {
+		waddnstr(mixer_widget.window, s, s_end - s);
+	} else {
+		if (align != ALIGN_LEFT) {
+			spaces = width - string_width;
+			if (align == ALIGN_CENTER)
+				spaces /= 2;
+			if (spaces > 0)
+				wprintw(mixer_widget.window, "%*s", spaces, "");
+		}
+		waddstr(mixer_widget.window, s);
+		if (align != ALIGN_RIGHT) {
+			getyx(mixer_widget.window, cur_y, cur_x);
+			if (cur_y == y) {
+				spaces = x + width - cur_x;
+				if (spaces > 0)
+					wprintw(mixer_widget.window, "%*s", spaces, "");
+			}
+		}
+	}
+}
+
+void init_mixer_layout(void)
+{
+	const char *labels_left[4] = {
+		_("Card:"),
+		_("Chip:"),
+		_("View:"),
+		_("Item:"),
+	};
+	const char *labels_right[4] = {
+		_("F1:  Help"),
+		_("F2:  System information"),
+		_("F6:  Select sound card"),
+		_("Esc: Exit"),
+	};
+	unsigned int label_width_left, label_width_right;
+	unsigned int right_x, i;
+
+	screen_too_small = screen_lines < 14 || screen_cols < 12;
+	has_info_items = screen_lines >= 6;
+	if (!has_info_items)
+		return;
+
+	label_width_left = get_max_mbs_width(labels_left, 4);
+	label_width_right = get_max_mbs_width(labels_right, 4);
+	if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
+		label_width_right = 0;
+	if (2 + label_width_left + 1 + 28 + label_width_right + 2 > screen_cols)
+		label_width_left = 0;
+
+	info_items_left = label_width_left ? 3 + label_width_left : 2;
+	right_x = screen_cols - label_width_right - 2;
+	info_items_width = right_x - info_items_left;
+	if (info_items_width < 1) {
+		has_info_items = FALSE;
+		return;
+	}
+
+	wattrset(mixer_widget.window, attr_mixer_text);
+	if (label_width_left)
+		for (i = 0; i < 4; ++i)
+			display_string_in_field(1 + i, 2, labels_left[i],
+						label_width_left, ALIGN_RIGHT);
+	if (label_width_right)
+		for (i = 0; i < 4; ++i)
+			display_string_in_field(1 + i, right_x, labels_right[i],
+						label_width_right, ALIGN_LEFT);
+}
+
+void display_card_info(void)
+{
+	snd_hctl_t *hctl;
+	snd_ctl_t *ctl;
+	snd_ctl_card_info_t *card_info;
+	const char *card_name = NULL;
+	const char *mixer_name = NULL;
+	int err;
+
+	if (!has_info_items)
+		return;
+
+	snd_ctl_card_info_alloca(&card_info);
+	if (mixer_device_name)
+		err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
+	else
+		err = -1;
+	if (err >= 0) {
+		ctl = snd_hctl_ctl(hctl);
+		err = snd_ctl_card_info(ctl, card_info);
+		if (err >= 0) {
+			card_name = snd_ctl_card_info_get_name(card_info);
+			mixer_name = snd_ctl_card_info_get_mixername(card_info);
+		}
+	}
+
+	if (card_name)
+		wattrset(mixer_widget.window, attr_mixer_active);
+	else {
+		wattrset(mixer_widget.window, attr_mixer_text);
+		if (unplugged)
+			card_name = _("(unplugged)");
+		else
+			card_name = "-";
+	}
+	display_string_in_field(1, info_items_left, card_name, info_items_width, ALIGN_LEFT);
+
+	if (mixer_name)
+		wattrset(mixer_widget.window, attr_mixer_active);
+	else {
+		wattrset(mixer_widget.window, attr_mixer_text);
+		mixer_name = "-";
+	}
+	display_string_in_field(2, info_items_left, mixer_name, info_items_width, ALIGN_LEFT);
+}
+
+void display_view_mode(void)
+{
+	const char *modes[3] = {
+		_("Playback"),
+		_("Capture"),
+		_("All"),
+	};
+	unsigned int widths[3];
+	bool has_view_mode;
+	int i;
+
+	if (!has_info_items)
+		return;
+
+	has_view_mode = controls_count > 0 || are_there_any_controls();
+	for (i = 0; i < 3; ++i)
+		widths[i] = get_mbs_width(modes[i]);
+	if (4 + widths[0] + 6 + widths[1] + 6 + widths[2] + 1 <= info_items_width) {
+		wmove(mixer_widget.window, 3, info_items_left);
+		wattrset(mixer_widget.window, attr_mixer_text);
+		for (i = 0; i < 3; ++i) {
+			wprintw(mixer_widget.window, "F%c:", '3' + i);
+			if (has_view_mode && (int)view_mode == i) {
+				wattrset(mixer_widget.window, attr_mixer_active);
+				wprintw(mixer_widget.window, "[%s]", modes[i]);
+				wattrset(mixer_widget.window, attr_mixer_text);
+			} else {
+				wprintw(mixer_widget.window, " %s ", modes[i]);
+			}
+			if (i < 2)
+				waddch(mixer_widget.window, ' ');
+		}
+	} else {
+		wattrset(mixer_widget.window, attr_mixer_active);
+		display_string_in_field(3, info_items_left,
+					has_view_mode ? modes[view_mode] : "",
+					info_items_width, ALIGN_LEFT);
+	}
+}
+
+static char *format_gain(long db)
+{
+	if (db != SND_CTL_TLV_DB_GAIN_MUTE)
+		return casprintf("%.2f", db / 100.0);
+	else
+		return cstrdup(_("mute"));
+}
+
+static void display_focus_item_info(void)
+{
+	struct control *control;
+	unsigned int index;
+	char buf[64];
+	long db, db2;
+	int sw, sw2;
+	char *dbs, *dbs2;
+	char *value_info;
+	char *item_info;
+	int err;
+
+	if (!has_info_items)
+		return;
+	wattrset(mixer_widget.window, attr_mixer_active);
+	if (!controls_count || screen_too_small) {
+		display_string_in_field(4, info_items_left, "", info_items_width, ALIGN_LEFT);
+		return;
+	}
+	control = &controls[focus_control_index];
+	value_info = NULL;
+	if (control->flags & TYPE_ENUM) {
+		err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
+		if (err >= 0)
+			err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
+		if (err >= 0)
+			value_info = casprintf(" [%s]", buf);
+	} else if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
+		int (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *);
+
+		if (control->flags & TYPE_PVOLUME)
+			get_vol_func = snd_mixer_selem_get_playback_dB;
+		else
+			get_vol_func = snd_mixer_selem_get_capture_dB;
+		if (!(control->flags & HAS_VOLUME_1)) {
+			err = get_vol_func(control->elem, control->volume_channels[0], &db);
+			if (err >= 0) {
+				dbs = format_gain(db);
+				value_info = casprintf(" [%s %s]", _("dB gain:"), dbs);
+				free(dbs);
+			}
+		} else {
+			err = get_vol_func(control->elem, control->volume_channels[0], &db);
+			if (err >= 0)
+				err = get_vol_func(control->elem, control->volume_channels[1], &db2);
+			if (err >= 0) {
+				dbs = format_gain(db);
+				dbs2 = format_gain(db2);
+				value_info = casprintf(_(" [%s %s, %s]"), _("dB gain:"), dbs, dbs2);
+				free(dbs);
+				free(dbs2);
+			}
+		}
+	} else if (control->flags & TYPE_PSWITCH) {
+		if (!(control->flags & HAS_PSWITCH_1)) {
+			err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
+			if (err >= 0 && !sw)
+				value_info = casprintf(" [%s]", _("Off"));
+		} else {
+			err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &sw);
+			if (err >= 0)
+				err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &sw2);
+			if (err >= 0 && (!sw || !sw2))
+				value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
+		}
+	} else if (control->flags & TYPE_CSWITCH) {
+		if (!(control->flags & HAS_CSWITCH_1)) {
+			err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
+			if (err >= 0 && !sw)
+				value_info = casprintf(" [%s]", _("Off"));
+		} else {
+			err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &sw);
+			if (err >= 0)
+				err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &sw2);
+			if (err >= 0 && (!sw || !sw2))
+				value_info = casprintf(" [%s, %s]", sw ? _("On") : _("Off"), sw2 ? _("On") : _("Off"));
+		}
+	}
+	item_info = casprintf("%s%s", control->name, value_info ? value_info : "");
+	free(value_info);
+	display_string_in_field(4, info_items_left, item_info, info_items_width, ALIGN_LEFT);
+	free(item_info);
+}
+
+static void clear_controls_display(void)
+{
+	int i;
+
+	wattrset(mixer_widget.window, attr_mixer_frame);
+	for (i = 5; i < screen_lines - 1; ++i)
+		mvwprintw(mixer_widget.window, i, 1, "%*s", screen_cols - 2, "");
+}
+
+static void center_string(int line, const char *s)
+{
+	int width = get_mbs_width(s);
+	if (width <= screen_cols - 2)
+		mvwaddstr(mixer_widget.window, line, (screen_cols - width) / 2, s);
+}
+
+static void display_unplugged(void)
+{
+	int lines, top, left;
+	bool boojum;
+
+	lines = screen_lines - 6;
+	if (lines < 2)
+		return;
+	top = lines / 2;
+	boojum = lines >= 10 && screen_cols >= 48;
+	top -= boojum ? 5 : 1;
+	if (top < 5)
+		top = 5;
+	if (boojum) {
+		left = (screen_cols - 46) / 2;
+		wattrset(mixer_widget.window, attr_mixer_text);
+		mvwaddstr(mixer_widget.window, top + 0, left,    "In the midst of the word he was trying to say,");
+		mvwaddstr(mixer_widget.window, top + 1, left + 2,  "In the midst of his laughter and glee,");
+		mvwaddstr(mixer_widget.window, top + 2, left,    "He had softly and suddenly vanished away---");
+		mvwaddstr(mixer_widget.window, top + 3, left + 2,  "For the Snark was a Boojum, you see.");
+		mvwchgat(mixer_widget.window,  top + 3, left + 16, 3,          /* ^^^ */
+			 attr_mixer_text | A_BOLD, PAIR_NUMBER(attr_mixer_text), NULL);
+		mvwaddstr(mixer_widget.window, top + 5, left,    "(Lewis Carroll, \"The Hunting of the Snark\")");
+		top += 8;
+	}
+	wattrset(mixer_widget.window, attr_errormsg);
+	center_string(top, _("The sound device was unplugged."));
+	center_string(top + 1, _("Press F6 to select another sound card."));
+}
+
+static void display_no_controls(void)
+{
+	int y;
+	const char *msg;
+
+	y = (screen_lines - 6) / 2 - 1;
+	if (y < 5)
+		y = 5;
+	if (y >= screen_lines - 1)
+		return;
+	wattrset(mixer_widget.window, attr_infomsg);
+	if (view_mode == VIEW_MODE_PLAYBACK && are_there_any_controls())
+		msg = _("This sound device does not have any playback controls.");
+	else if (view_mode == VIEW_MODE_CAPTURE && are_there_any_controls())
+		msg = _("This sound device does not have any capture controls.");
+	else
+		msg = _("This sound device does not have any controls.");
+	center_string(y, msg);
+}
+
+static void display_string_centered_in_control(int y, int col, const char *s, int width)
+{
+	int left, x;
+
+	left = first_control_x + col * (control_width + 1);
+	x = left + (control_width - width) / 2;
+	display_string_in_field(y, x, s, width, ALIGN_CENTER);
+}
+
+static void display_control(unsigned int control_index)
+{
+	struct control *control;
+	int col;
+	int i, c;
+	int left, frame_left;
+	int bar_height;
+	double volumes[2];
+	int switches[2];
+	unsigned int index;
+	const char *s;
+	char buf[64];
+	int err;
+
+	control = &controls[control_index];
+	col = control_index - first_visible_control_index;
+	left = first_control_x + col * (control_width + 1);
+	frame_left = left + (control_width - 4) / 2;
+	if (control->flags & IS_ACTIVE)
+		wattrset(mixer_widget.window, attr_ctl_frame);
+	else
+		wattrset(mixer_widget.window, attr_ctl_inactive);
+	if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
+		mvwaddch(mixer_widget.window, base_y - volume_height - 1, frame_left, ACS_ULCORNER);
+		waddch(mixer_widget.window, ACS_HLINE);
+		waddch(mixer_widget.window, ACS_HLINE);
+		waddch(mixer_widget.window, ACS_URCORNER);
+		for (i = 0; i < volume_height; ++i) {
+			mvwaddch(mixer_widget.window, base_y - i - 1, frame_left, ACS_VLINE);
+			mvwaddch(mixer_widget.window, base_y - i - 1, frame_left + 3, ACS_VLINE);
+		}
+		mvwaddch(mixer_widget.window, base_y, frame_left,
+			 control->flags & TYPE_PSWITCH ? ACS_LTEE : ACS_LLCORNER);
+		waddch(mixer_widget.window, ACS_HLINE);
+		waddch(mixer_widget.window, ACS_HLINE);
+		waddch(mixer_widget.window,
+		       control->flags & TYPE_PSWITCH ? ACS_RTEE : ACS_LRCORNER);
+	} else if (control->flags & TYPE_PSWITCH) {
+		mvwaddch(mixer_widget.window, base_y, frame_left, ACS_ULCORNER);
+		waddch(mixer_widget.window, ACS_HLINE);
+		waddch(mixer_widget.window, ACS_HLINE);
+		waddch(mixer_widget.window, ACS_URCORNER);
+	}
+	if (control->flags & TYPE_PSWITCH) {
+		mvwaddch(mixer_widget.window, base_y + 1, frame_left, ACS_VLINE);
+		mvwaddch(mixer_widget.window, base_y + 1, frame_left + 3, ACS_VLINE);
+		mvwaddch(mixer_widget.window, base_y + 2, frame_left, ACS_LLCORNER);
+		waddch(mixer_widget.window, ACS_HLINE);
+		waddch(mixer_widget.window, ACS_HLINE);
+		waddch(mixer_widget.window, ACS_LRCORNER);
+	}
+	if (control->flags & (TYPE_PVOLUME | TYPE_CVOLUME)) {
+		double (*get_vol_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t);
+
+		if (control->flags & TYPE_PVOLUME)
+			get_vol_func = get_normalized_playback_volume;
+		else
+			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 (control->flags & IS_ACTIVE)
+			wattrset(mixer_widget.window, 0);
+		for (c = 0; c < 2; c++) {
+			bar_height = lrint(volumes[c] * volume_height);
+			for (i = 0; i < volume_height; ++i) {
+				chtype ch;
+				if (i + 1 > bar_height)
+					ch = ' ' | (control->flags & IS_ACTIVE ?
+						    attr_ctl_frame : 0);
+				else {
+					ch = ACS_CKBOARD;
+					if (!(control->flags & IS_ACTIVE))
+						;
+#ifdef TRICOLOR_VOLUME_BAR
+					else if (i > volume_height * 8 / 10)
+						ch |= attr_ctl_bar_hi;
+					else if (i > volume_height * 4 / 10)
+						ch |= attr_ctl_bar_mi;
+#endif
+					else
+						ch |= attr_ctl_bar_lo;
+				}
+				mvwaddch(mixer_widget.window, base_y - i - 1,
+					 frame_left + c + 1, ch);
+			}
+		}
+		if (control->flags & IS_ACTIVE)
+			wattrset(mixer_widget.window, attr_mixer_active);
+		if (!(control->flags & HAS_VOLUME_1)) {
+			sprintf(buf, "%d", (int)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", (int)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);
+			wprintw(mixer_widget.window, "%-3d", (int)lrint(volumes[1] * 100));
+		}
+	}
+
+	if (control->flags & TYPE_PSWITCH) {
+		err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[0], &switches[0]);
+		if (err >= 0 && (control->flags & HAS_PSWITCH_1))
+			err = snd_mixer_selem_get_playback_switch(control->elem, control->pswitch_channels[1], &switches[1]);
+		else
+			switches[1] = switches[0];
+		if (err < 0)
+			return;
+		if (control->flags & IS_ACTIVE)
+			wattrset(mixer_widget.window, 0);
+		mvwaddch(mixer_widget.window, base_y + 1, frame_left + 1,
+			 switches[0]
+			 /* TRANSLATORS: playback on; one character */
+			 ? _("O")[0] | (control->flags & IS_ACTIVE ? attr_ctl_nomute : 0)
+			 /* TRANSLATORS: playback muted; one character */
+			 : _("M")[0] | (control->flags & IS_ACTIVE ? attr_ctl_mute : 0));
+		waddch(mixer_widget.window,
+		       switches[1]
+		       ? _("O")[0] | (control->flags & IS_ACTIVE ? attr_ctl_nomute : 0)
+		       : _("M")[0] | (control->flags & IS_ACTIVE ? attr_ctl_mute : 0));
+	}
+
+	if (control->flags & TYPE_CSWITCH) {
+		err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[0], &switches[0]);
+		if (err >= 0 && (control->flags & HAS_CSWITCH_1))
+			err = snd_mixer_selem_get_capture_switch(control->elem, control->cswitch_channels[1], &switches[1]);
+		else
+			switches[1] = switches[0];
+		if (err < 0)
+			return;
+		if (control->flags & IS_ACTIVE)
+			wattrset(mixer_widget.window, switches[0] ? attr_ctl_capture : attr_ctl_nocapture);
+		/* TRANSLATORS: "left"; no more than two characters */
+		display_string_in_field(cswitch_y - 1, frame_left - 2, switches[0] ? _("L") : "", 2, ALIGN_RIGHT);
+		if (control->flags & IS_ACTIVE)
+			wattrset(mixer_widget.window, switches[1] ? attr_ctl_capture : attr_ctl_nocapture);
+		/* TRANSLATORS: "right"; no more than two characters */
+		display_string_in_field(cswitch_y - 1, frame_left + 4, switches[1] ? _("R") : "", 2, ALIGN_LEFT);
+		/* TRANSLATORS: no more than eight characters */
+		s = _("CAPTURE");
+		if (switches[0] || switches[1]) {
+			if (control->flags & IS_ACTIVE)
+				wattrset(mixer_widget.window, attr_ctl_capture);
+			display_string_in_field(cswitch_y, frame_left - 2, s, 8, ALIGN_CENTER);
+		} else {
+			i = get_mbs_width(s);
+			if (i > 8)
+				i = 8;
+			memset(buf, '-', i);
+			buf[i] = '\0';
+			if (control->flags & IS_ACTIVE)
+				wattrset(mixer_widget.window, attr_ctl_nocapture);
+			display_string_in_field(cswitch_y, frame_left - 2, buf, 8, ALIGN_CENTER);
+		}
+	}
+
+	if (control->flags & TYPE_ENUM) {
+		err = snd_mixer_selem_get_enum_item(control->elem, ffs(control->enum_channel_bits) - 1, &index);
+		if (err < 0)
+			return;
+		err = snd_mixer_selem_get_enum_item_name(control->elem, index, sizeof buf - 1, buf);
+		if (err < 0)
+			return;
+		if (control->flags & IS_ACTIVE)
+			wattrset(mixer_widget.window, attr_mixer_active);
+		display_string_centered_in_control(base_y, col, buf, control_width);
+	}
+
+	if (control_index == focus_control_index) {
+		i = first_control_x + col * (control_width + 1) + (control_width - control_name_width) / 2;
+		wattrset(mixer_widget.window, attr_ctl_mark_focus);
+		mvwaddch(mixer_widget.window, name_y, i - 1, '<');
+		mvwaddch(mixer_widget.window, name_y, i + control_name_width, '>');
+		if (control->flags & IS_ACTIVE)
+			wattrset(mixer_widget.window, attr_ctl_label_focus);
+		else
+			wattrset(mixer_widget.window, attr_ctl_label_inactive);
+	} else {
+		if (control->flags & IS_ACTIVE)
+			wattrset(mixer_widget.window, attr_ctl_label);
+		else
+			wattrset(mixer_widget.window, attr_ctl_label_inactive);
+	}
+	display_string_centered_in_control(name_y, col, control->name, control_name_width);
+	if (channel_name_y > name_y) {
+		if (control->flags & IS_MULTICH) {
+			switch (control->flags & MULTICH_MASK) {
+			case 0:
+			default:
+				s = _("Front");
+				break;
+			case 1:
+				s = _("Rear");
+				break;
+			case 2:
+				s = _("Center");
+				break;
+			case 3:
+				s = _("Woofer");
+				break;
+			case 4:
+				s = _("Side");
+				break;
+			}
+		} else {
+			s = "";
+			wattrset(mixer_widget.window, attr_mixer_frame);
+		}
+		display_string_centered_in_control(channel_name_y, col, s,
+						   control_name_width);
+	}
+}
+
+static void display_scroll_indicators(void)
+{
+	int y0, y1, y;
+	chtype left, right;
+
+	if (screen_too_small)
+		return;
+	y0 = screen_lines * 3 / 8;
+	y1 = screen_lines * 5 / 8;
+	left = first_visible_control_index > 0 ? ACS_LARROW : ACS_VLINE;
+	right = first_visible_control_index + visible_controls < controls_count
+		? ACS_RARROW : ACS_VLINE;
+	wattrset(mixer_widget.window, attr_mixer_frame);
+	for (y = y0; y <= y1; ++y) {
+		mvwaddch(mixer_widget.window, y, 0, left);
+		mvwaddch(mixer_widget.window, y, screen_cols - 1, right);
+	}
+}
+
+void display_controls(void)
+{
+	unsigned int i;
+
+	if (first_visible_control_index > controls_count - visible_controls)
+		first_visible_control_index = controls_count - visible_controls;
+	if (first_visible_control_index > focus_control_index)
+		first_visible_control_index = focus_control_index;
+	else if (first_visible_control_index < focus_control_index - visible_controls + 1 && visible_controls)
+		first_visible_control_index = focus_control_index - visible_controls + 1;
+
+	clear_controls_display();
+
+	display_focus_item_info();
+
+	if (controls_count > 0) {
+		if (!screen_too_small)
+			for (i = 0; i < visible_controls; ++i)
+				display_control(first_visible_control_index + i);
+	} else if (unplugged) {
+		display_unplugged();
+	} else if (mixer_device_name) {
+		display_no_controls();
+	}
+	display_scroll_indicators();
+	controls_changed = FALSE;
+}
+
+void compute_controls_layout(void)
+{
+	bool any_volume, any_pswitch, any_cswitch, any_multich;
+	int max_width, name_len;
+	int height, space;
+	unsigned int i;
+
+	if (controls_count == 0 || screen_too_small) {
+		visible_controls = 0;
+		return;
+	}
+
+	any_volume = FALSE;
+	any_pswitch = FALSE;
+	any_cswitch = FALSE;
+	any_multich = FALSE;
+	for (i = 0; i < controls_count; ++i) {
+		if (controls[i].flags & (TYPE_PVOLUME | TYPE_CVOLUME))
+			any_volume = 1;
+		if (controls[i].flags & TYPE_PSWITCH)
+			any_pswitch = 1;
+		if (controls[i].flags & TYPE_CSWITCH)
+			any_cswitch = 1;
+		if (controls[i].flags & IS_MULTICH)
+			any_multich = 1;
+	}
+
+	max_width = 8;
+	for (i = 0; i < controls_count; ++i) {
+		name_len = strlen(controls[i].name);
+		if (name_len > max_width)
+			max_width = name_len;
+	}
+	max_width = (max_width + 1) & ~1;
+
+	control_width = (screen_cols - 3 - (int)controls_count) / controls_count;
+	if (control_width < 8)
+		control_width = 8;
+	if (control_width > max_width)
+		control_width = max_width;
+	if (control_width > screen_cols - 4)
+		control_width = screen_cols - 4;
+
+	visible_controls = (screen_cols - 3) / (control_width + 1);
+	if (visible_controls > controls_count)
+		visible_controls = controls_count;
+
+	first_control_x = 2 + (screen_cols - 3 - visible_controls * (control_width + 1)) / 2;
+
+	if (control_width < max_width)
+		control_name_width = control_width;
+	else
+		control_name_width = max_width;
+
+	height = 2;
+	if (any_volume)
+		height += 2;
+	if (any_pswitch)
+		height += 2;
+	if (any_cswitch)
+		height += 1;
+	if (any_multich)
+		height += 1;
+	if (any_volume) {
+		space = screen_lines - 6 - height;
+		if (space <= 1)
+			volume_height = 1;
+		else if (space <= 10)
+			volume_height = space;
+		else
+			volume_height = 10 + (space - 10) / 2;
+		height += volume_height;
+	}
+
+	space = screen_lines - 6 - height;
+	channel_name_y = screen_lines - 2 - space / 2;
+	name_y = channel_name_y - any_multich;
+	values_y = name_y - any_volume;
+	cswitch_y = values_y - any_cswitch;
+	base_y = cswitch_y - 1 - 2 * any_pswitch;
+}
diff --git a/alsamixer/mixer_display.h b/alsamixer/mixer_display.h
new file mode 100644
index 0000000..3d65670
--- /dev/null
+++ b/alsamixer/mixer_display.h
@@ -0,0 +1,10 @@
+#ifndef MIXER_DISPLAY_H_INCLUDED
+#define MIXER_DISPLAY_H_INCLUDED
+
+void init_mixer_layout(void);
+void display_card_info(void);
+void display_view_mode(void);
+void display_controls(void);
+void compute_controls_layout(void);
+
+#endif
diff --git a/alsamixer/mixer_widget.c b/alsamixer/mixer_widget.c
new file mode 100644
index 0000000..caaf777
--- /dev/null
+++ b/alsamixer/mixer_widget.c
@@ -0,0 +1,661 @@
+/*
+ * mixer_widget.c - mixer widget and keys handling
+ * Copyright (c) 1998,1999 Tim Janik
+ *                         Jaroslav Kysela <perex@perex.cz>
+ * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <alsa/asoundlib.h>
+#include "gettext_curses.h"
+#include "version.h"
+#include "utils.h"
+#include "die.h"
+#include "mem.h"
+#include "colors.h"
+#include "widget.h"
+#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"
+
+snd_mixer_t *mixer;
+char *mixer_device_name;
+bool unplugged;
+
+struct widget mixer_widget;
+
+enum view_mode view_mode;
+
+int focus_control_index;
+snd_mixer_selem_id_t *current_selem_id;
+unsigned int current_control_flags;
+
+bool controls_changed;
+
+enum channel_mask {
+	LEFT = 1,
+	RIGHT = 2,
+};
+
+static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask)
+{
+	unsigned int i;
+
+	if (mask & (SND_CTL_EVENT_MASK_REMOVE |
+		    SND_CTL_EVENT_MASK_INFO |
+		    SND_CTL_EVENT_MASK_VALUE))
+		controls_changed = TRUE;
+
+	if (mask & SND_CTL_EVENT_MASK_INFO)
+		for (i = 0; i < controls_count; ++i)
+			if (controls[i].elem == elem) {
+				controls[i].flags &= ~IS_ACTIVE;
+				if (snd_mixer_selem_is_active(controls[i].elem))
+					controls[i].flags |= IS_ACTIVE;
+			}
+
+	return 0;
+}
+
+static int mixer_callback(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem)
+{
+	if (mask & SND_CTL_EVENT_MASK_ADD) {
+		snd_mixer_elem_set_callback(elem, elem_callback);
+		controls_changed = TRUE;
+	}
+	return 0;
+}
+
+void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt)
+{
+	int err;
+
+	err = snd_mixer_open(&mixer, 0);
+	if (err < 0)
+		fatal_alsa_error(_("cannot open mixer"), err);
+
+	mixer_device_name = cstrdup(selem_regopt->device);
+	err = snd_mixer_selem_register(mixer, selem_regopt, NULL);
+	if (err < 0)
+		fatal_alsa_error(_("cannot open mixer"), err);
+
+	snd_mixer_set_callback(mixer, mixer_callback);
+
+	err = snd_mixer_load(mixer);
+	if (err < 0)
+		fatal_alsa_error(_("cannot load mixer controls"), err);
+
+	err = snd_mixer_selem_id_malloc(&current_selem_id);
+	if (err < 0)
+		fatal_error("out of memory");
+}
+
+static void set_view_mode(enum view_mode m)
+{
+	view_mode = m;
+	create_controls();
+}
+
+static void close_hctl(void)
+{
+	free_controls();
+	if (mixer_device_name) {
+		snd_mixer_detach(mixer, mixer_device_name);
+		free(mixer_device_name);
+		mixer_device_name = NULL;
+	}
+}
+
+static void check_unplugged(void)
+{
+	snd_hctl_t *hctl;
+	snd_ctl_t *ctl;
+	unsigned int state;
+	int err;
+
+	unplugged = FALSE;
+	if (mixer_device_name) {
+		err = snd_mixer_get_hctl(mixer, mixer_device_name, &hctl);
+		if (err >= 0) {
+			ctl = snd_hctl_ctl(hctl);
+			/* just any random function that does an ioctl() */
+			err = snd_ctl_get_power_state(ctl, &state);
+			if (err == -ENODEV)
+				unplugged = TRUE;
+		}
+	}
+}
+
+void close_mixer_device(void)
+{
+	check_unplugged();
+	close_hctl();
+
+	display_card_info();
+	set_view_mode(view_mode);
+}
+
+bool select_card_by_name(const char *device_name)
+{
+	int err;
+	bool opened;
+	char *msg;
+
+	close_hctl();
+	unplugged = FALSE;
+
+	opened = FALSE;
+	if (device_name) {
+		err = snd_mixer_attach(mixer, device_name);
+		if (err >= 0)
+			opened = TRUE;
+		else {
+			msg = casprintf(_("Cannot open mixer device '%s'."), device_name);
+			show_alsa_error(msg, err);
+			free(msg);
+		}
+	}
+	if (opened) {
+		mixer_device_name = cstrdup(device_name);
+
+		err = snd_mixer_load(mixer);
+		if (err < 0)
+			fatal_alsa_error(_("cannot load mixer controls"), err);
+	}
+
+	display_card_info();
+	set_view_mode(view_mode);
+	return opened;
+}
+
+static void show_help(void)
+{
+	const char *help[] = {
+		_("Esc     Exit"),
+		_("F1 ? H  Help"),
+		_("F2 /    System information"),
+		_("F3      Show playback controls"),
+		_("F4      Show capture controls"),
+		_("F5      Show all controls"),
+		_("Tab     Toggle view mode (F3/F4/F5)"),
+		_("F6 S    Select sound card"),
+		_("L       Redraw screen"),
+		"",
+		_("Left    Move to the previous control"),
+		_("Right   Move to the next control"),
+		"",
+		_("Up/Down    Change volume"),
+		_("+ -        Change volume"),
+		_("Page Up/Dn Change volume in big steps"),
+		_("End        Set volume to 0%"),
+		_("0-9        Set volume to 0%-90%"),
+		_("Q W E      Increase left/both/right volumes"),
+		/* TRANSLATORS: or Y instead of Z */
+		_("Z X C      Decrease left/both/right volumes"),
+		_("B          Balance left and right volumes"),
+		"",
+		_("M          Toggle mute"),
+		/* TRANSLATORS: or , . */
+		_("< >        Toggle left/right mute"),
+		"",
+		_("Space      Toggle capture"),
+		/* TRANSLATORS: or Insert Delete */
+		_("; '        Toggle left/right capture"),
+		"",
+		_("Authors:"),
+		_("  Tim Janik"),
+		_("  Jaroslav Kysela <perex@perex.cz>"),
+		_("  Clemens Ladisch <clemens@ladisch.de>"),
+	};
+	show_text(help, ARRAY_SIZE(help), _("Help"));
+}
+
+void refocus_control(void)
+{
+	if (focus_control_index < controls_count) {
+		snd_mixer_selem_get_id(controls[focus_control_index].elem, current_selem_id);
+		current_control_flags = controls[focus_control_index].flags;
+	}
+
+	display_controls();
+}
+
+static struct control *get_focus_control(unsigned int type)
+{
+	if (focus_control_index >= 0 &&
+	    focus_control_index < controls_count &&
+	    (controls[focus_control_index].flags & IS_ACTIVE) &&
+	    (controls[focus_control_index].flags & type))
+		return &controls[focus_control_index];
+	else
+		return NULL;
+}
+
+static void change_enum_to_percent(struct control *control, int value)
+{
+	unsigned int i;
+	unsigned int index;
+	unsigned int new_index;
+	int items;
+	int err;
+
+	i = ffs(control->enum_channel_bits) - 1;
+	err = snd_mixer_selem_get_enum_item(control->elem, i, &index);
+	if (err < 0)
+		return;
+	new_index = index;
+	if (value == 0) {
+		new_index = 0;
+	} else if (value == 100) {
+		items = snd_mixer_selem_get_enum_items(control->elem);
+		if (items < 1)
+			return;
+		new_index = items - 1;
+	}
+	if (new_index == index)
+		return;
+	for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
+		if (control->enum_channel_bits & (1 << i))
+			snd_mixer_selem_set_enum_item(control->elem, i, new_index);
+}
+
+static void change_enum_relative(struct control *control, int delta)
+{
+	int items;
+	unsigned int i;
+	unsigned int index;
+	int new_index;
+	int err;
+
+	items = snd_mixer_selem_get_enum_items(control->elem);
+	if (items < 1)
+		return;
+	err = snd_mixer_selem_get_enum_item(control->elem, 0, &index);
+	if (err < 0)
+		return;
+	new_index = (int)index + delta;
+	if (new_index < 0)
+		new_index = 0;
+	else if (new_index >= items)
+		new_index = items - 1;
+	if (new_index == index)
+		return;
+	for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
+		if (control->enum_channel_bits & (1 << i))
+			snd_mixer_selem_set_enum_item(control->elem, i, new_index);
+}
+
+static void change_volume_to_percent(struct control *control, int value, unsigned int channels)
+{
+	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)
+		set_func = set_normalized_playback_volume;
+	else
+		set_func = set_normalized_capture_volume;
+	if (channels & LEFT)
+		set_func(control->elem, control->volume_channels[0], value / 100.0, 0);
+	if (channels & RIGHT)
+		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)
+{
+	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_func = get_normalized_playback_volume;
+		set_func = set_normalized_playback_volume;
+	} else {
+		get_func = get_normalized_capture_volume;
+		set_func = set_normalized_capture_volume;
+	}
+	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) {
+		left = clamp_volume(left + delta / 100.0);
+		set_func(control->elem, control->volume_channels[0], left, dir);
+	}
+	if (channels & RIGHT) {
+		right = clamp_volume(right + delta / 100.0);
+		set_func(control->elem, control->volume_channels[1], right, dir);
+	}
+}
+
+static void change_control_to_percent(int value, unsigned int channels)
+{
+	struct control *control;
+
+	control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
+	if (!control)
+		return;
+	if (control->flags & TYPE_ENUM)
+		change_enum_to_percent(control, value);
+	else
+		change_volume_to_percent(control, value, channels);
+	display_controls();
+}
+
+static void change_control_relative(int delta, unsigned int channels)
+{
+	struct control *control;
+
+	control = get_focus_control(TYPE_PVOLUME | TYPE_CVOLUME | TYPE_ENUM);
+	if (!control)
+		return;
+	if (control->flags & TYPE_ENUM)
+		change_enum_relative(control, delta);
+	else
+		change_volume_relative(control, delta, channels);
+	display_controls();
+}
+
+static void toggle_switches(unsigned int type, unsigned int channels)
+{
+	struct control *control;
+	unsigned int switch_1_mask;
+	int (*get_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int *);
+	int (*set_func)(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, int);
+	snd_mixer_selem_channel_id_t channel_ids[2];
+	int left, right;
+	int err;
+
+	control = get_focus_control(type);
+	if (!control)
+		return;
+	if (type == TYPE_PSWITCH) {
+		switch_1_mask = HAS_PSWITCH_1;
+		get_func = snd_mixer_selem_get_playback_switch;
+		set_func = snd_mixer_selem_set_playback_switch;
+		channel_ids[0] = control->pswitch_channels[0];
+		channel_ids[1] = control->pswitch_channels[1];
+	} else {
+		switch_1_mask = HAS_CSWITCH_1;
+		get_func = snd_mixer_selem_get_capture_switch;
+		set_func = snd_mixer_selem_set_capture_switch;
+		channel_ids[0] = control->cswitch_channels[0];
+		channel_ids[1] = control->cswitch_channels[1];
+	}
+	if (!(control->flags & switch_1_mask))
+		channels = LEFT;
+	if (channels & LEFT) {
+		err = get_func(control->elem, channel_ids[0], &left);
+		if (err < 0)
+			return;
+	}
+	if (channels & RIGHT) {
+		err = get_func(control->elem, channel_ids[1], &right);
+		if (err < 0)
+			return;
+	}
+	if (channels & LEFT)
+		set_func(control->elem, channel_ids[0], !left);
+	if (channels & RIGHT)
+		set_func(control->elem, channel_ids[1], !right);
+	display_controls();
+}
+
+static void toggle_mute(unsigned int channels)
+{
+	toggle_switches(TYPE_PSWITCH, channels);
+}
+
+static void toggle_capture(unsigned int channels)
+{
+	toggle_switches(TYPE_CSWITCH, channels);
+}
+
+static void balance_volumes(void)
+{
+	struct control *control;
+	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) {
+		left = get_normalized_playback_volume(control->elem, control->volume_channels[0]);
+		right = get_normalized_playback_volume(control->elem, control->volume_channels[1]);
+	} else {
+		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) {
+		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 {
+		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();
+}
+
+static void on_handle_key(int key)
+{
+	switch (key) {
+	case 27:
+	case KEY_CANCEL:
+	case KEY_F(10):
+		mixer_widget.close();
+		break;
+	case KEY_F(1):
+	case KEY_HELP:
+	case 'H':
+	case 'h':
+	case '?':
+		show_help();
+		break;
+	case KEY_F(2):
+	case '/':
+		create_proc_files_list();
+		break;
+	case KEY_F(3):
+		set_view_mode(VIEW_MODE_PLAYBACK);
+		break;
+	case KEY_F(4):
+		set_view_mode(VIEW_MODE_CAPTURE);
+		break;
+	case KEY_F(5):
+		set_view_mode(VIEW_MODE_ALL);
+		break;
+	case '\t':
+		set_view_mode((enum view_mode)((view_mode + 1) % VIEW_MODE_COUNT));
+		break;
+	case KEY_F(6):
+	case 'S':
+	case 's':
+		create_card_select_list();
+		break;
+	case KEY_REFRESH:
+	case 12:
+	case 'L':
+	case 'l':
+		clearok(mixer_widget.window, TRUE);
+		display_controls();
+		break;
+	case KEY_LEFT:
+	case 'P':
+	case 'p':
+		if (focus_control_index > 0) {
+			--focus_control_index;
+			refocus_control();
+		}
+		break;
+	case KEY_RIGHT:
+	case 'N':
+	case 'n':
+		if (focus_control_index < controls_count - 1) {
+			++focus_control_index;
+			refocus_control();
+		}
+		break;
+	case KEY_PPAGE:
+		change_control_relative(5, LEFT | RIGHT);
+		break;
+	case KEY_NPAGE:
+		change_control_relative(-5, LEFT | RIGHT);
+		break;
+#if 0
+	case KEY_BEG:
+	case KEY_HOME:
+		change_control_to_percent(100, LEFT | RIGHT);
+		break;
+#endif
+	case KEY_LL:
+	case KEY_END:
+		change_control_to_percent(0, LEFT | RIGHT);
+		break;
+	case KEY_UP:
+	case '+':
+	case 'K':
+	case 'k':
+	case 'W':
+	case 'w':
+		change_control_relative(1, LEFT | RIGHT);
+		break;
+	case KEY_DOWN:
+	case '-':
+	case 'J':
+	case 'j':
+	case 'X':
+	case 'x':
+		change_control_relative(-1, LEFT | RIGHT);
+		break;
+	case '0': case '1': case '2': case '3': case '4':
+	case '5': case '6': case '7': case '8': case '9':
+		change_control_to_percent((key - '0') * 10, LEFT | RIGHT);
+		break;
+	case 'Q':
+	case 'q':
+		change_control_relative(1, LEFT);
+		break;
+	case 'Y':
+	case 'y':
+	case 'Z':
+	case 'z':
+		change_control_relative(-1, LEFT);
+		break;
+	case 'E':
+	case 'e':
+		change_control_relative(1, RIGHT);
+		break;
+	case 'C':
+	case 'c':
+		change_control_relative(-1, RIGHT);
+		break;
+	case 'M':
+	case 'm':
+		toggle_mute(LEFT | RIGHT);
+		break;
+	case 'B':
+	case 'b':
+	case '=':
+		balance_volumes();
+		break;
+	case '<':
+	case ',':
+		toggle_mute(LEFT);
+		break;
+	case '>':
+	case '.':
+		toggle_mute(RIGHT);
+		break;
+	case ' ':
+		toggle_capture(LEFT | RIGHT);
+		break;
+	case KEY_IC:
+	case ';':
+		toggle_capture(LEFT);
+		break;
+	case KEY_DC:
+	case '\'':
+		toggle_capture(RIGHT);
+		break;
+	}
+}
+
+static void create(void)
+{
+	static const char title[] = " AlsaMixer v" SND_UTIL_VERSION_STR " ";
+
+	widget_init(&mixer_widget, screen_lines, screen_cols, 0, 0,
+		    attr_mixer_frame, WIDGET_BORDER);
+	if (screen_cols >= (sizeof(title) - 1) + 2) {
+		wattrset(mixer_widget.window, attr_mixer_active);
+		mvwaddstr(mixer_widget.window, 0, (screen_cols - (sizeof(title) - 1)) / 2, title);
+	}
+	init_mixer_layout();
+	display_card_info();
+	set_view_mode(view_mode);
+}
+
+static void on_window_size_changed(void)
+{
+	create();
+}
+
+static void on_close(void)
+{
+	widget_free(&mixer_widget);
+}
+
+void mixer_shutdown(void)
+{
+	free_controls();
+	if (mixer)
+		snd_mixer_close(mixer);
+	if (current_selem_id)
+		snd_mixer_selem_id_free(current_selem_id);
+}
+
+struct widget mixer_widget = {
+	.handle_key = on_handle_key,
+	.window_size_changed = on_window_size_changed,
+	.close = on_close,
+};
+
+void create_mixer_widget(void)
+{
+	create();
+}
diff --git a/alsamixer/mixer_widget.h b/alsamixer/mixer_widget.h
new file mode 100644
index 0000000..da8628e
--- /dev/null
+++ b/alsamixer/mixer_widget.h
@@ -0,0 +1,36 @@
+#ifndef MIXER_WIDGET_H_INCLUDED
+#define MIXER_WIDGET_H_INCLUDED
+
+#include CURSESINC
+#include <alsa/asoundlib.h>
+#include "widget.h"
+
+enum view_mode {
+	VIEW_MODE_PLAYBACK,
+	VIEW_MODE_CAPTURE,
+	VIEW_MODE_ALL,
+	VIEW_MODE_COUNT,
+};
+
+extern snd_mixer_t *mixer;
+extern char *mixer_device_name;
+extern bool unplugged;
+
+extern struct widget mixer_widget;
+
+extern enum view_mode view_mode;
+
+extern int focus_control_index;
+extern snd_mixer_selem_id_t *current_selem_id;
+extern unsigned int current_control_flags;
+
+extern bool controls_changed;
+
+void create_mixer_object(struct snd_mixer_selem_regopt *selem_regopt);
+void create_mixer_widget(void);
+void mixer_shutdown(void);
+void close_mixer_device(void);
+bool select_card_by_name(const char *device_name);
+void refocus_control(void);
+
+#endif
diff --git a/alsamixer/proc_files.c b/alsamixer/proc_files.c
new file mode 100644
index 0000000..b2f5f21
--- /dev/null
+++ b/alsamixer/proc_files.c
@@ -0,0 +1,169 @@
+/*
+ * proc_files.c - shows ALSA system information files
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <assert.h>
+#include <menu.h>
+#include <unistd.h>
+#include "gettext_curses.h"
+#include "utils.h"
+#include "die.h"
+#include "mem.h"
+#include "colors.h"
+#include "widget.h"
+#include "textbox.h"
+#include "proc_files.h"
+
+static struct widget proc_widget;
+static ITEM *items[7];
+static unsigned int items_count;
+static MENU *menu;
+
+static void on_menu_key(int key)
+{
+	static const struct {
+		int key;
+		int request;
+	} key_map[] = {
+		{ KEY_DOWN, REQ_DOWN_ITEM },
+		{ KEY_UP, REQ_UP_ITEM },
+		{ KEY_HOME, REQ_FIRST_ITEM },
+		{ KEY_NPAGE, REQ_SCR_DPAGE },
+		{ KEY_PPAGE, REQ_SCR_UPAGE },
+		{ KEY_BEG, REQ_FIRST_ITEM },
+		{ KEY_END, REQ_LAST_ITEM },
+	};
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(key_map); ++i)
+		if (key_map[i].key == key) {
+			menu_driver(menu, key_map[i].request);
+			break;
+		}
+}
+
+static void on_handle_key(int key)
+{
+	ITEM *item;
+
+	switch (key) {
+	case 27:
+	case KEY_CANCEL:
+		proc_widget.close();
+		break;
+	case 10:
+	case 13:
+	case KEY_ENTER:
+		item = current_item(menu);
+		if (item)
+			show_textfile(item_name(item));
+		break;
+	default:
+		on_menu_key(key);
+		break;
+	}
+}
+
+static bool create(void)
+{
+	int rows, columns;
+	const char *title;
+
+	if (screen_lines < 3 || screen_cols < 20) {
+		proc_widget.close();
+		beep();
+		return FALSE;
+	}
+	scale_menu(menu, &rows, &columns);
+	rows += 2;
+	columns += 2;
+	if (rows > screen_lines)
+		rows = screen_lines;
+	if (columns > screen_cols)
+		columns = screen_cols;
+
+	widget_init(&proc_widget, rows, columns, SCREEN_CENTER, SCREEN_CENTER,
+		    attr_menu, WIDGET_BORDER | WIDGET_SUBWINDOW);
+
+	title = _("Select File");
+	mvwprintw(proc_widget.window, 0, (columns - 2 - get_mbs_width(title)) / 2, " %s ", title);
+	set_menu_win(menu, proc_widget.window);
+	set_menu_sub(menu, proc_widget.subwindow);
+	return TRUE;
+}
+
+static void on_window_size_changed(void)
+{
+	unpost_menu(menu);
+	if (!create())
+		return;
+	post_menu(menu);
+}
+
+static void on_close(void)
+{
+	unsigned int i;
+
+	unpost_menu(menu);
+	free_menu(menu);
+	for (i = 0; i < items_count; ++i)
+		free_item(items[i]);
+	widget_free(&proc_widget);
+}
+
+static void add_item(const char *file_name)
+{
+	if (access(file_name, F_OK) == 0) {
+		items[items_count] = new_item(file_name, NULL);
+		if (!items[items_count])
+			fatal_error("cannot create menu item");
+		++items_count;
+		assert(items_count < ARRAY_SIZE(items));
+	}
+}
+
+static struct widget proc_widget = {
+	.handle_key = on_handle_key,
+	.window_size_changed = on_window_size_changed,
+	.close = on_close,
+};
+
+void create_proc_files_list(void)
+{
+	items_count = 0;
+	add_item("/proc/asound/version");
+	add_item("/proc/asound/cards");
+	add_item("/proc/asound/devices");
+	add_item("/proc/asound/oss/devices");
+	add_item("/proc/asound/timers");
+	add_item("/proc/asound/pcm");
+	items[items_count] = NULL;
+
+	menu = new_menu(items);
+	if (!menu)
+		fatal_error("cannot create menu");
+	set_menu_fore(menu, attr_menu_selected);
+	set_menu_back(menu, attr_menu);
+	set_menu_mark(menu, NULL);
+	menu_opts_off(menu, O_SHOWDESC);
+
+	if (!create())
+		return;
+
+	post_menu(menu);
+}
diff --git a/alsamixer/proc_files.h b/alsamixer/proc_files.h
new file mode 100644
index 0000000..8862c71
--- /dev/null
+++ b/alsamixer/proc_files.h
@@ -0,0 +1,6 @@
+#ifndef PROC_FILES_H_INCLUDED
+#define PROC_FILES_H_INCLUDED
+
+void create_proc_files_list(void);
+
+#endif
diff --git a/alsamixer/textbox.c b/alsamixer/textbox.c
new file mode 100644
index 0000000..a979d3c
--- /dev/null
+++ b/alsamixer/textbox.c
@@ -0,0 +1,397 @@
+/*
+ * textbox.c - show a text box for messages, files or help
+ * Copyright (c) 1998,1999 Tim Janik
+ *                         Jaroslav Kysela <perex@perex.cz>
+ * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include CURSESINC
+#include <alsa/asoundlib.h>
+#include "gettext_curses.h"
+#include "utils.h"
+#include "die.h"
+#include "mem.h"
+#include "colors.h"
+#include "widget.h"
+#include "textbox.h"
+
+#define MAX_FILE_SIZE 1048576
+
+static void create_text_box(const char *const *lines, unsigned int count,
+			    const char *title, int attrs);
+
+void show_error(const char *msg, int err)
+{
+	const char *lines[2];
+	unsigned int count;
+
+	lines[0] = msg;
+	count = 1;
+	if (err) {
+		lines[1] = strerror(err);
+		count = 2;
+	}
+	create_text_box(lines, count, _("Error"), attr_errormsg);
+}
+
+void show_alsa_error(const char *msg, int err)
+{
+	const char *lines[2];
+	unsigned int count;
+
+	lines[0] = msg;
+	count = 1;
+	if (err < 0) {
+		lines[1] = snd_strerror(err);
+		count = 2;
+	}
+	create_text_box(lines, count, _("Error"), attr_errormsg);
+}
+
+static char *read_file(const char *file_name, unsigned int *file_size)
+{
+	FILE *f;
+	int err;
+	char *buf;
+	unsigned int allocated = 2048;
+	unsigned int bytes_read;
+
+	f = fopen(file_name, "r");
+	if (!f) {
+		err = errno;
+		buf = casprintf(_("Cannot open file \"%s\"."), file_name);
+		show_error(buf, err);
+		free(buf);
+		return NULL;
+	}
+	*file_size = 0;
+	buf = NULL;
+	do {
+		allocated *= 2;
+		buf = crealloc(buf, allocated);
+		bytes_read = fread(buf + *file_size, 1, allocated - *file_size, f);
+		*file_size += bytes_read;
+	} while (*file_size == allocated && allocated < MAX_FILE_SIZE);
+	fclose(f);
+	if (*file_size > 0 && buf[*file_size - 1] != '\n' && *file_size < allocated) {
+		buf[*file_size] = '\n';
+		++*file_size;
+	}
+	return buf;
+}
+
+void show_textfile(const char *file_name)
+{
+	char *buf;
+	unsigned int file_size;
+	unsigned int line_count;
+	unsigned int i;
+	const char **lines;
+	const char *start_line;
+
+	buf = read_file(file_name, &file_size);
+	if (!buf)
+		return;
+	line_count = 0;
+	for (i = 0; i < file_size; ++i)
+		line_count += buf[i] == '\n';
+	lines = ccalloc(line_count, sizeof *lines);
+	line_count = 0;
+	start_line = buf;
+	for (i = 0; i < file_size; ++i) {
+		if (buf[i] == '\n') {
+			lines[line_count++] = start_line;
+			buf[i] = '\0';
+			start_line = &buf[i + 1];
+		}
+		if (buf[i] == '\t')
+			buf[i] = ' ';
+	}
+	create_text_box(lines, line_count, file_name, attr_textbox);
+	free(lines);
+	free(buf);
+}
+
+void show_text(const char *const *lines, unsigned int count, const char *title)
+{
+	create_text_box(lines, count, title, attr_textbox);
+}
+
+/**********************************************************************/
+
+static struct widget text_widget;
+static char *title;
+static int widget_attrs;
+static char **text_lines;
+static unsigned int text_lines_count;
+static int max_line_width;
+static int text_box_y;
+static int text_box_x;
+static int max_scroll_y;
+static int max_scroll_x;
+static int current_top;
+static int current_left;
+
+static void update_text_lines(void)
+{
+	int i;
+	int width;
+	const char *line_begin;
+	const char *line_end;
+	int cur_y, cur_x;
+	int rest_of_line;
+
+	for (i = 0; i < text_box_y; ++i) {
+		width = current_left;
+		line_begin = mbs_at_width(text_lines[current_top + i], &width, 1);
+		wmove(text_widget.window, i + 1, 1);
+		if (width > current_left)
+			waddch(text_widget.window, ' ');
+		if (*line_begin != '\0') {
+			width = text_box_x - (width > current_left);
+			line_end = mbs_at_width(line_begin, &width, -1);
+			if (width)
+				waddnstr(text_widget.window, line_begin,
+					 line_end - line_begin);
+		}
+		getyx(text_widget.window, cur_y, cur_x);
+		if (cur_y == i + 1) {
+			rest_of_line = text_box_x + 1 - cur_x;
+			if (rest_of_line > 0)
+				wprintw(text_widget.window, "%*s", rest_of_line, "");
+		}
+	}
+}
+
+static void update_y_scroll_bar(void)
+{
+	int length;
+	int begin, end;
+	int i;
+
+	if (max_scroll_y <= 0 || text_lines_count == 0)
+		return;
+	length = text_box_y * text_box_y / text_lines_count;
+	if (length >= text_box_y)
+		return;
+	begin = current_top * (text_box_y - length) / max_scroll_y;
+	end = begin + length;
+	for (i = 0; i < text_box_y; ++i)
+		mvwaddch(text_widget.window, i + 1, text_box_x + 1,
+			 i >= begin && i < end ? ACS_BOARD : ' ');
+}
+
+static void update_x_scroll_bar(void)
+{
+	int length;
+	int begin, end;
+	int i;
+
+	if (max_scroll_x <= 0 || max_line_width <= 0)
+		return;
+	length = text_box_x * text_box_x / max_line_width;
+	if (length >= text_box_x)
+		return;
+	begin = current_left * (text_box_x - length) / max_scroll_x;
+	end = begin + length;
+	wmove(text_widget.window, text_box_y + 1, 1);
+	for (i = 0; i < text_box_x; ++i)
+		waddch(text_widget.window, i >= begin && i < end ? ACS_BOARD : ' ');
+}
+
+static void move_x(int delta)
+{
+	int left;
+
+	left = current_left + delta;
+	if (left < 0)
+		left = 0;
+	else if (left > max_scroll_x)
+		left = max_scroll_x;
+	if (left != current_left) {
+		current_left = left;
+		update_text_lines();
+		update_x_scroll_bar();
+	}
+}
+
+static void move_y(int delta)
+{
+	int top;
+
+	top = current_top + delta;
+	if (top < 0)
+		top = 0;
+	else if (top > max_scroll_y)
+		top = max_scroll_y;
+	if (top != current_top) {
+		current_top = top;
+		update_text_lines();
+		update_y_scroll_bar();
+	}
+}
+
+static void on_handle_key(int key)
+{
+	switch (key) {
+	case 10:
+	case 13:
+	case 27:
+	case KEY_CANCEL:
+	case KEY_ENTER:
+	case KEY_CLOSE:
+	case KEY_EXIT:
+		text_widget.close();
+		break;
+	case KEY_DOWN:
+	case KEY_SF:
+	case 'J':
+	case 'j':
+	case 'X':
+	case 'x':
+		move_y(1);
+		break;
+	case KEY_UP:
+	case KEY_SR:
+	case 'K':
+	case 'k':
+	case 'W':
+	case 'w':
+		move_y(-1);
+		break;
+	case KEY_LEFT:
+	case 'H':
+	case 'h':
+	case 'P':
+	case 'p':
+		move_x(-1);
+		break;
+	case KEY_RIGHT:
+	case 'L':
+	case 'l':
+	case 'N':
+	case 'n':
+		move_x(1);
+		break;
+	case KEY_NPAGE:
+	case ' ':
+		move_y(text_box_y);
+		break;
+	case KEY_PPAGE:
+	case KEY_BACKSPACE:
+	case 'B':
+	case 'b':
+		move_y(-text_box_y);
+		break;
+	case KEY_HOME:
+	case KEY_BEG:
+		move_x(-max_scroll_x);
+		break;
+	case KEY_LL:
+	case KEY_END:
+		move_x(max_scroll_x);
+		break;
+	case '\t':
+		move_x(8);
+		break;
+	case KEY_BTAB:
+		move_x(-8);
+		break;
+	}
+}
+
+static bool create(void)
+{
+	int len, width;
+
+	if (screen_lines < 3 || screen_cols < 8) {
+		text_widget.close();
+		beep();
+		return FALSE;
+	}
+
+	width = max_line_width;
+	len = get_mbs_width(title) + 2;
+	if (width < len)
+		width = len;
+
+	text_box_y = text_lines_count;
+	if (text_box_y > screen_lines - 2)
+		text_box_y = screen_lines - 2;
+	max_scroll_y = text_lines_count - text_box_y;
+	text_box_x = width;
+	if (text_box_x > screen_cols - 2)
+		text_box_x = screen_cols - 2;
+	max_scroll_x = max_line_width - text_box_x;
+
+	widget_init(&text_widget, text_box_y + 2, text_box_x + 2,
+		    SCREEN_CENTER, SCREEN_CENTER, widget_attrs, WIDGET_BORDER);
+	mvwprintw(text_widget.window, 0, (text_box_x + 2 - get_mbs_width(title) - 2) / 2, " %s ", title);
+
+	if (current_top > max_scroll_y)
+		current_top = max_scroll_y;
+	if (current_left > max_scroll_x)
+		current_left = max_scroll_x;
+	update_text_lines();
+	update_y_scroll_bar();
+	update_x_scroll_bar();
+	return TRUE;
+}
+
+static void on_window_size_changed(void)
+{
+	create();
+}
+
+static void on_close(void)
+{
+	unsigned int i;
+
+	for (i = 0; i < text_lines_count; ++i)
+		free(text_lines[i]);
+	free(text_lines);
+	widget_free(&text_widget);
+}
+
+static struct widget text_widget = {
+	.handle_key = on_handle_key,
+	.window_size_changed = on_window_size_changed,
+	.close = on_close,
+};
+
+static void create_text_box(const char *const *lines, unsigned int count,
+			    const char *title_, int attrs)
+{
+	unsigned int i;
+
+	text_lines = ccalloc(count, sizeof *text_lines);
+	for (i = 0; i < count; ++i)
+		text_lines[i] = cstrdup(lines[i]);
+	text_lines_count = count;
+	max_line_width = get_max_mbs_width(lines, count);
+	title = cstrdup(title_);
+	widget_attrs = attrs;
+
+	current_top = 0;
+	current_left = 0;
+
+	create();
+}
diff --git a/alsamixer/textbox.h b/alsamixer/textbox.h
new file mode 100644
index 0000000..7dc290b
--- /dev/null
+++ b/alsamixer/textbox.h
@@ -0,0 +1,10 @@
+#ifndef TEXTBOX_H_INCLUDED
+#define TEXTBOX_H_INCLUDED
+
+void show_error(const char *msg, int err);
+void show_alsa_error(const char *msg, int err);
+void show_text(const char *const *text_lines, unsigned int count,
+	       const char *title);
+void show_textfile(const char *file_name);
+
+#endif
diff --git a/alsamixer/utils.c b/alsamixer/utils.c
new file mode 100644
index 0000000..3602bef
--- /dev/null
+++ b/alsamixer/utils.c
@@ -0,0 +1,111 @@
+/*
+ * utils.c - multibyte-string helpers
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _XOPEN_SOURCE
+#include "aconfig.h"
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include "utils.h"
+
+/*
+ * mbs_at_width - compute screen position in a string
+ *
+ * For displaying strings on the screen, we have to know how many character
+ * cells are occupied.  This function calculates the position in a multibyte
+ * string that is at a desired position.
+ *
+ * Parameters:
+ * s:     the string
+ * width: on input, the desired number of character cells; on output, the actual
+ *        position, in character cells, of the return value
+ * dir:   -1 or 1; in which direction to round if a multi-column character goes
+ *        over the desired width
+ *
+ * Return value:
+ * Pointer to the place in the string that is as near the desired width as
+ * possible.  If the string is too short, the return value points to the
+ * terminating zero.  If the last character is a multi-column character that
+ * goes over the desired width, the return value may be one character cell
+ * earlier or later than desired, depending on the dir parameter.
+ * In any case, the return value points after any zero-width characters that
+ * follow the last character.
+ */
+const char *mbs_at_width(const char *s, int *width, int dir)
+{
+	size_t len;
+	wchar_t wc;
+	int bytes;
+	int width_so_far, w;
+
+	if (*width <= 0)
+		return s;
+	mbtowc(NULL, NULL, 0); /* reset shift state */
+	len = strlen(s);
+	width_so_far = 0;
+	while (len && (bytes = mbtowc(&wc, s, len)) > 0) {
+		w = wcwidth(wc);
+		if (width_so_far + w > *width && dir < 0)
+			break;
+		if (w >= 0)
+			width_so_far += w;
+		s += bytes;
+		len -= bytes;
+		if (width_so_far >= *width) {
+			while (len && (bytes = mbtowc(&wc, s, len)) > 0) {
+				w = wcwidth(wc);
+				if (w != 0)
+					break;
+				s += bytes;
+				len -= bytes;
+			}
+			break;
+		}
+	}
+	*width = width_so_far;
+	return s;
+}
+
+/*
+ * get_mbs_width - compute screen width of a string
+ */
+unsigned int get_mbs_width(const char *s)
+{
+	int width;
+
+	width = INT_MAX;
+	mbs_at_width(s, &width, 1);
+	return width;
+}
+
+/*
+ * get_max_mbs_width - get width of longest string in an array
+ */
+unsigned int get_max_mbs_width(const char *const *s, unsigned int count)
+{
+	unsigned int max_width, i, len;
+
+	max_width = 0;
+	for (i = 0; i < count; ++i) {
+		len = get_mbs_width(s[i]);
+		if (len > max_width)
+			max_width = len;
+	}
+	return max_width;
+}
diff --git a/alsamixer/utils.h b/alsamixer/utils.h
new file mode 100644
index 0000000..00a52dd
--- /dev/null
+++ b/alsamixer/utils.h
@@ -0,0 +1,10 @@
+#ifndef UTILS_H_INCLUDED
+#define UTILS_H_INCLUDED
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof *(a))
+
+unsigned int get_mbs_width(const char *s);
+unsigned int get_max_mbs_width(const char *const *s, unsigned int count);
+const char *mbs_at_width(const char *s, int *width, int dir);
+
+#endif
diff --git a/alsamixer/volume_mapping.c b/alsamixer/volume_mapping.c
new file mode 100644
index 0000000..1c0d7c4
--- /dev/null
+++ b/alsamixer/volume_mapping.c
@@ -0,0 +1,185 @@
+/*
+ * 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"
+
+#ifdef __UCLIBC__
+/* 10^x = 10^(log e^x) = (e^x)^log10 = e^(x * log 10) */
+#define exp10(x) (exp((x) * log(10)))
+#endif /* __UCLIBC__ */
+
+#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/alsamixer/widget.c b/alsamixer/widget.c
new file mode 100644
index 0000000..75da4c2
--- /dev/null
+++ b/alsamixer/widget.c
@@ -0,0 +1,140 @@
+/*
+ * widget.c - handles widget objects and the widget stack
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "aconfig.h"
+#include <stdlib.h>
+#include <term.h>
+#include "die.h"
+#include "widget.h"
+
+int screen_lines;
+int screen_cols;
+
+static int cursor_visibility = -1;
+
+static void widget_handle_key(int key)
+{
+}
+
+static void update_cursor_visibility(void)
+{
+	struct widget *active_widget;
+
+	active_widget = get_active_widget();
+	if (active_widget &&
+	    active_widget->cursor_visibility != cursor_visibility) {
+		cursor_visibility = active_widget->cursor_visibility;
+		curs_set(cursor_visibility);
+	}
+}
+
+void widget_init(struct widget *widget, int lines_, int cols, int y, int x,
+		 chtype bkgd, unsigned int flags)
+{
+	WINDOW *old_window;
+
+	if (y == SCREEN_CENTER)
+		y = (screen_lines - lines_) / 2;
+	if (x == SCREEN_CENTER)
+		x = (screen_cols - cols) / 2;
+
+	old_window = widget->window;
+	widget->window = newwin(lines_, cols, y, x);
+	if (!widget->window)
+		fatal_error("cannot create window");
+	keypad(widget->window, TRUE);
+	nodelay(widget->window, TRUE);
+	leaveok(widget->window, !(flags & WIDGET_CURSOR_VISIBLE));
+	wbkgdset(widget->window, bkgd);
+	werase(widget->window);
+
+	if (flags & WIDGET_BORDER)
+		box(widget->window, 0, 0);
+	if (flags & WIDGET_SUBWINDOW) {
+		if (widget->subwindow)
+			delwin(widget->subwindow);
+		widget->subwindow = derwin(widget->window,
+					   lines_ - 2, cols - 2, 1, 1);
+		if (!widget->subwindow)
+			fatal_error("cannot create subwindow");
+		wbkgdset(widget->subwindow, bkgd);
+	}
+	widget->cursor_visibility = !!(flags & WIDGET_CURSOR_VISIBLE);
+
+	if (widget->panel) {
+		replace_panel(widget->panel, widget->window);
+	} else {
+		widget->panel = new_panel(widget->window);
+		if (!widget->panel)
+			fatal_error("cannot create panel");
+		set_panel_userptr(widget->panel, widget);
+	}
+
+	if (!widget->handle_key)
+		widget->handle_key = widget_handle_key;
+
+	if (old_window)
+		delwin(old_window);
+
+	update_cursor_visibility();
+}
+
+void widget_free(struct widget *widget)
+{
+	if (widget->panel) {
+		del_panel(widget->panel);
+		widget->panel = NULL;
+	}
+	if (widget->subwindow) {
+		delwin(widget->subwindow);
+		widget->subwindow = NULL;
+	}
+	if (widget->window) {
+		delwin(widget->window);
+		widget->window = NULL;
+	}
+
+	update_cursor_visibility();
+}
+
+struct widget *get_active_widget(void)
+{
+	PANEL *active_panel;
+
+	active_panel = panel_below(NULL);
+	if (active_panel)
+		return panel_userptr(active_panel);
+	else
+		return NULL;
+}
+
+void window_size_changed(void)
+{
+	PANEL *panel, *below;
+	struct widget *widget;
+
+	getmaxyx(stdscr, screen_lines, screen_cols);
+	if (tigetflag("xenl") != 1 && tigetflag("am") != 1)
+		--screen_lines;
+
+	for (panel = panel_below(NULL); panel; panel = below) {
+		below = panel_below(panel);
+		widget = panel_userptr(panel);
+		widget->window_size_changed();
+	}
+}
diff --git a/alsamixer/widget.h b/alsamixer/widget.h
new file mode 100644
index 0000000..6adb526
--- /dev/null
+++ b/alsamixer/widget.h
@@ -0,0 +1,33 @@
+#ifndef WIDGET_H_INCLUDED
+#define WIDGET_H_INCLUDED
+
+#include <panel.h>
+
+#define WIDGET_BORDER		0x1
+#define WIDGET_SUBWINDOW	0x2
+#define WIDGET_CURSOR_VISIBLE	0x4
+
+#define SCREEN_CENTER	-1
+
+struct widget {
+	WINDOW *window;
+	WINDOW *subwindow; /* optional: contents without border */
+	PANEL *panel;
+	int cursor_visibility;
+
+	void (*handle_key)(int key);
+	void (*window_size_changed)(void);
+	void (*close)(void);
+};
+
+extern int screen_lines;
+extern int screen_cols;
+
+void widget_init(struct widget *widget,
+		 int lines_, int cols, int y, int x,
+		 chtype bkgd, unsigned int flags);
+void widget_free(struct widget *widget);
+struct widget *get_active_widget(void);
+void window_size_changed(void);
+
+#endif
diff --git a/alsaucm/Makefile.am b/alsaucm/Makefile.am
new file mode 100644
index 0000000..4eba677
--- /dev/null
+++ b/alsaucm/Makefile.am
@@ -0,0 +1,13 @@
+bin_PROGRAMS = \
+        alsaucm
+
+alsaucm_SOURCES = usecase.c
+
+INCLUDES = \
+         -Wall -I$(top_srcdir)/include
+
+alsaucm_LDADD = -lasound
+
+# local build
+INCLUDES += -I$(top_srcdir)/../alsa-lib/include
+alsaucm_LDADD += -L$(top_srcdir)/../alsa-lib/src/.libs
diff --git a/alsaucm/go.sh b/alsaucm/go.sh
new file mode 100755
index 0000000..2aadd1c
--- /dev/null
+++ b/alsaucm/go.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+#GDB="gdb --args"
+
+ALSA_CONFIG_UCM="$HOME/alsa/alsa-lib/test/ucm" \
+LD_PRELOAD="$HOME/alsa/alsa-lib/src/.libs/libasound.so" \
+$GDB ./alsaucm "$@"
diff --git a/alsaucm/usecase.c b/alsaucm/usecase.c
new file mode 100644
index 0000000..1c94680
--- /dev/null
+++ b/alsaucm/usecase.c
@@ -0,0 +1,505 @@
+/*
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser 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.
+ *
+ *  Support for the verb/device/modifier core logic and API, 
+ *  command line tool and file parser was kindly sponsored by 
+ *  Texas Instruments Inc.
+ *  Support for multiple active modifiers and devices, 
+ *  transition sequences, multiple client access and user defined use
+ *  cases was kindly sponsored by Wolfson Microelectronics PLC.
+ * 
+ *  Copyright (C) 2008-2010 SlimLogic Ltd
+ *  Copyright (C) 2010 Wolfson Microelectronics PLC
+ *  Copyright (C) 2010 Texas Instruments Inc.
+ *  Copyright (C) 2010 Red Hat Inc.
+ *  Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ *           Stefan Schmidt <stefan@slimlogic.co.uk>
+ *           Justin Xu <justinx@slimlogic.co.uk>
+ *           Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+#include <alsa/use-case.h>
+#include "aconfig.h"
+#include "version.h"
+
+#define MAX_BUF 256
+
+struct context {
+	snd_use_case_mgr_t *uc_mgr;
+	const char *command;
+	char *card;
+	char **argv;
+	int argc;
+	int arga;
+	char *batch;
+	unsigned int interactive:1;
+	unsigned int no_open:1;
+	unsigned int do_exit:1;
+};
+
+enum uc_cmd {
+	/* management */
+	OM_UNKNOWN = 0,
+	OM_OPEN,
+	OM_RESET,
+	OM_RELOAD,
+	OM_LISTCARDS,
+	OM_LIST2,
+	OM_LIST1,
+
+	/* set/get */
+	OM_SET,
+	OM_GET,
+	OM_GETI,
+
+	/* misc */
+	OM_HELP,
+	OM_QUIT,
+};
+
+struct cmd {
+	int code;
+	int args;
+	unsigned int opencard:1;
+	const char *id;
+};
+
+static struct cmd cmds[] = {
+	{ OM_OPEN, 1, 0, "open" },
+	{ OM_RESET, 0, 1, "reset" },
+	{ OM_RELOAD, 0, 1, "reload" },
+	{ OM_LISTCARDS, 0, 0, "listcards" },
+	{ OM_LIST1, 1, 1, "list1" },
+	{ OM_LIST2, 1, 1, "list" },
+	{ OM_SET, 2, 1, "set" },
+	{ OM_GET, 1, 1, "get" },
+	{ OM_GETI, 1, 1, "geti" },
+	{ OM_HELP, 0, 0, "help" },
+	{ OM_QUIT, 0, 0, "quit" },
+	{ OM_HELP, 0, 0, "h" },
+	{ OM_HELP, 0, 0, "?" },
+	{ OM_QUIT, 0, 0, "q" },
+	{ OM_UNKNOWN, 0, 0, NULL }
+};
+
+static void dump_help(struct context *context)
+{
+	if (context->command)
+		printf("Usage: %s <options> [command]\n", context->command);
+	printf(
+"\nAvailable options:\n"
+"  -h,--help                  this help\n"
+"  -c,--card NAME             open card NAME\n"
+"  -i,--interactive           interactive mode\n"
+"  -b,--batch FILE            batch mode (use '-' for the stdin input)\n"
+"  -n,--no-open               do not open first card found\n"
+"\nAvailable commands:\n"
+"  open NAME                  open card NAME\n"
+"  reset                      reset sound card to default state\n"
+"  reload                     reload configuration\n"
+"  listcards                  list available cards\n"
+"  list IDENTIFIER            list command\n"
+"  get IDENTIFIER             get string value\n"
+"  geti IDENTIFIER            get integer value\n"
+"  set IDENTIFIER VALUE       set string value\n"
+"  h,help                     help\n"
+"  q,quit                     quit\n"
+);
+}
+
+static int parse_line(struct context *context, char *line)
+{
+	char *start, **nargv;
+	int c;
+
+	context->argc = 0;
+	while (*line) {
+		while (*line && (*line == ' ' || *line == '\t' ||
+							*line == '\n'))
+			line++;
+		c = *line;
+		if (c == '\"' || c == '\'') {
+			start = ++line;
+			while (*line && *line != c)
+				line++;
+			if (*line) {
+				*line = '\0';
+				line++;
+			}
+		} else {
+			start = line;
+			while (*line && *line != ' ' && *line != '\t' &&
+			       *line != '\n')
+				line++;
+			if (*line) {
+				*line = '\0';
+				line++;
+			}
+		}
+		if (start[0] == '\0' && context->argc == 0)
+			return 0;
+		if (context->argc + 1 >= context->arga) {
+			context->arga += 4;
+			nargv = realloc(context->argv,
+					context->arga * sizeof(char *));
+			if (nargv == NULL)
+				return -ENOMEM;
+			context->argv = nargv;
+		}
+		context->argv[context->argc++] = start;
+	}
+	return 0;
+}
+
+static int do_one(struct context *context, struct cmd *cmd, char **argv)
+{
+	const char **list, *str;
+	long lval;
+	int err, i, j, entries;
+
+	if (cmd->opencard && context->uc_mgr == NULL) {
+		fprintf(stderr, "%s: command '%s' requires an open card\n",
+				context->command, cmd->id);
+		return 0;
+	}
+	switch (cmd->code) {
+	case OM_OPEN:
+		if (context->uc_mgr)
+			snd_use_case_mgr_close(context->uc_mgr);
+		context->uc_mgr = NULL;
+		free(context->card);
+		context->card = strdup(argv[0]);
+		err = snd_use_case_mgr_open(&context->uc_mgr, context->card);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to open sound card %s: %s\n",
+				context->command, context->card,
+				snd_strerror(err));
+			return err;
+		}
+		break;
+	case OM_RESET:
+		err = snd_use_case_mgr_reset(context->uc_mgr);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to reset sound card %s: %s\n",
+				context->command, context->card,
+				snd_strerror(err));
+			return err;
+		}
+		break;
+	case OM_RELOAD:
+		err = snd_use_case_mgr_reload(context->uc_mgr);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to reload manager %s: %s\n",
+				context->command, context->card,
+				snd_strerror(err));
+			return err;
+		}
+		break;
+	case OM_LISTCARDS:
+		err = snd_use_case_card_list(&list);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to get card list: %s\n",
+				context->command,
+				snd_strerror(err));
+			return err;
+		}
+		if (err == 0) {
+			printf("  list is empty\n");
+			return 0;
+		}
+		for (i = 0; i < err / 2; i++) {
+			printf("  %i: %s\n", i, list[i*2]);
+			if (list[i*2+1])
+				printf("    %s\n", list[i*2+1]);
+		}
+		snd_use_case_free_list(list, err);
+		break;
+	case OM_LIST1:
+	case OM_LIST2:
+		switch (cmd->code) {
+		case OM_LIST1:
+		    entries = 1;
+		    break;
+		case OM_LIST2:
+		    entries = 2;
+		    break;
+		}
+
+		err = snd_use_case_get_list(context->uc_mgr,
+					    argv[0],
+					    &list);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to get list %s: %s\n",
+				context->command, argv[0],
+				snd_strerror(err));
+			return err;
+		}
+		if (err == 0) {
+			printf("  list is empty\n");
+			return 0;
+		}
+		for (i = 0; i < err / entries; i++) {
+			printf("  %i: %s\n", i, list[i*entries]);
+			for (j = 0; j < entries - 1; j++)
+				if (list[i*entries+j+1])
+					printf("    %s\n", list[i*entries+j+1]);
+		}
+		snd_use_case_free_list(list, err);
+		break;
+	case OM_SET:
+		err = snd_use_case_set(context->uc_mgr, argv[0], argv[1]);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to set %s=%s: %s\n",
+				context->command, argv[0], argv[1],
+				snd_strerror(err));
+			return err;
+		}
+		break;
+	case OM_GET:
+		err = snd_use_case_get(context->uc_mgr, argv[0], &str);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to get %s: %s\n",
+				context->command, argv[0],
+				snd_strerror(err));
+			return err;
+		}
+		printf("  %s=%s\n", argv[0], str);
+		free((void *)str);
+		break;
+	case OM_GETI:
+		err = snd_use_case_geti(context->uc_mgr, argv[0], &lval);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to get integer %s: %s\n",
+				context->command, argv[0],
+				snd_strerror(err));
+			return lval;
+		}
+		printf("  %s=%li\n", argv[0], lval);
+		break;
+	case OM_QUIT:
+		context->do_exit = 1;
+		break;
+	case OM_HELP:
+		dump_help(context);
+		break;
+	default:
+		fprintf(stderr, "%s: unimplemented command '%s'\n",
+				context->command, cmd->id);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int do_commands(struct context *context)
+{
+	char *command, **argv;
+	struct cmd *cmd;
+	int i, acnt, err;
+
+	for (i = 0; i < context->argc && !context->do_exit; i++) {
+		command = context->argv[i];
+		for (cmd = cmds; cmd->id != NULL; cmd++) {
+			if (strcmp(cmd->id, command) == 0)
+				break;
+		}
+		if (cmd->id == NULL) {
+			fprintf(stderr, "%s: unknown command '%s'\n",
+						context->command, command);
+			return -EINVAL;
+		}
+		acnt = context->argc - (i + 1);
+		if (acnt < cmd->args) {
+			fprintf(stderr, "%s: expected %i arguments (got %i)\n",
+					context->command, cmd->args, acnt);
+			return -EINVAL;
+		}
+		argv = context->argv + i + 1;
+		err = do_one(context, cmd, argv);
+		if (err < 0)
+			return err;
+		i += cmd->args;
+	}
+	return 0;
+}
+
+static void my_exit(struct context *context, int exitcode)
+{
+	if (context->uc_mgr)
+		snd_use_case_mgr_close(context->uc_mgr);
+	if (context->arga > 0)
+		free(context->argv);
+	if (context->card)
+		free(context->card);
+	if (context->batch)
+		free(context->batch);
+	free(context);
+	exit(exitcode);
+}
+
+enum {
+	OPT_VERSION = 1,
+};
+
+int main(int argc, char *argv[])
+{
+	static const char short_options[] = "hb:c:in";
+	static const struct option long_options[] = {
+		{"help", 0, 0, 'h'},
+		{"version", 0, 0, OPT_VERSION},
+		{"card", 1, 0, 'c'},
+		{"interactive", 0, 0, 'i'},
+		{"batch", 1, 0, 'b'},
+		{"no-open", 0, 0, 'n'},
+		{0, 0, 0, 0}
+	};
+	struct context *context;
+	const char *command = argv[0];
+	const char **list;
+	int c, err, option_index;
+	char cmd[MAX_BUF];
+	FILE *in;
+
+	context = calloc(1, sizeof(*context));
+	if (context == NULL)
+		return EXIT_FAILURE;
+	context->command = command;
+	while ((c = getopt_long(argc, argv, short_options,
+				 long_options, &option_index)) != -1) {
+		switch (c) {
+		case 'h':
+			dump_help(context);
+			break;
+		case OPT_VERSION:
+			printf("%s: version " SND_UTIL_VERSION_STR "\n", command);
+			break;
+		case 'c':
+			if (context->card)
+				free(context->card);
+			context->card = strdup(optarg);
+			break;
+		case 'i':
+			context->interactive = 1;
+			context->batch = NULL;
+			break;
+		case 'b':
+			context->batch = strdup(optarg);
+			context->interactive = 0;
+			break;
+		case 'n':
+			context->no_open = 1;
+			break;
+		default:
+			fprintf(stderr, "Try '%s --help' for more information.\n", command);
+			my_exit(context, EXIT_FAILURE);
+		}
+	}
+
+	if (!context->no_open && context->card == NULL) {
+		err = snd_use_case_card_list(&list);
+		if (err < 0) {
+			fprintf(stderr, "%s: unable to obtain card list: %s\n", command, snd_strerror(err));
+			my_exit(context, EXIT_FAILURE);
+		}
+		if (err == 0) {
+			printf("No card found\n");
+			my_exit(context, EXIT_SUCCESS);
+		}
+		context->card = strdup(list[0]);
+		snd_use_case_free_list(list, err);
+	}
+
+	/* open library */
+	if (!context->no_open) {
+		err = snd_use_case_mgr_open(&context->uc_mgr,
+					    context->card);
+		if (err < 0) {
+			fprintf(stderr,
+				"%s: error failed to open sound card %s: %s\n",
+				command, context->card, snd_strerror(err));
+			my_exit(context, EXIT_FAILURE);
+		}
+	}
+
+	/* parse and execute any command line commands */
+	if (argc > optind) {
+		context->argv = argv + optind;
+		context->argc = argc - optind;
+		err = do_commands(context);
+		if (err < 0)
+			my_exit(context, EXIT_FAILURE);
+	}
+
+	if (!context->interactive && !context->batch)
+		my_exit(context, EXIT_SUCCESS);
+
+	if (context->interactive) {
+		printf("%s: Interacive mode - 'q' to quit\n", command);
+		in = stdin;
+	} else {
+		if (strcmp(context->batch, "-") == 0) {
+			in = stdin;
+		} else {
+			in = fopen(context->batch, "r");
+			if (in == NULL) {
+				fprintf(stderr, "%s: error failed to open file '%s': %s\n",
+					command, context->batch, strerror(-errno));
+				my_exit(context, EXIT_FAILURE);
+			}
+		}
+	}
+
+	/* run the interactive command parser and handler */
+	while (!context->do_exit && !feof(in)) {
+		if (context->interactive)
+			printf("%s>> ", argv[0]);
+		fflush(stdin);
+		if (fgets(cmd, MAX_BUF, in) == NULL)
+			break;
+		err = parse_line(context, cmd);
+		if (err < 0) {
+			fprintf(stderr, "%s: unable to parse line\n",
+				command);
+			my_exit(context, EXIT_FAILURE);
+		}
+		err = do_commands(context);
+		if (err < 0) {
+			if (context->interactive)
+				printf("^^^ error, try again\n");
+			else
+				my_exit(context, EXIT_FAILURE);
+		}
+	}
+	
+	if (in != stdin)
+		fclose(in);
+
+	my_exit(context, EXIT_SUCCESS);
+	return EXIT_SUCCESS;
+}
diff --git a/amidi/Makefile.am b/amidi/Makefile.am
new file mode 100644
index 0000000..0b31a67
--- /dev/null
+++ b/amidi/Makefile.am
@@ -0,0 +1,5 @@
+INCLUDES = -I$(top_srcdir)/include
+EXTRA_DIST = amidi.1
+
+bin_PROGRAMS = amidi
+man_MANS = amidi.1
diff --git a/amidi/amidi.1 b/amidi/amidi.1
new file mode 100644
index 0000000..c58d65c
--- /dev/null
+++ b/amidi/amidi.1
@@ -0,0 +1,155 @@
+.TH AMIDI 1 "26 Jun 2006"
+
+.SH NAME
+amidi \- read from and write to ALSA RawMIDI ports
+
+.SH SYNOPSIS
+\fBamidi\fP [\fI\-p port\fP] [\fI\-s file\fP | \fI\-S data\fP]
+[\fI\-r file\fP] [\fI\-d\fP] [\fI\-t seconds\fP] [\fI\-a\fP]
+
+.SH DESCRIPTION
+.B amidi
+is a command-line utility which allows to receive and send
+SysEx (system exclusive) data from/to external MIDI devices.
+It can also send any other MIDI commands.
+
+.B amidi
+handles only files containing raw MIDI commands, without timing
+information.
+.B amidi
+does not support Standard MIDI (.mid) files, but
+.B aplaymidi(1)
+and
+.B arecordmidi(1)
+do.
+
+.SH OPTIONS
+
+Use the
+.I \-h,
+.I \-V,
+.I \-l,
+or
+.I \-L
+options to display information;
+or use at least one of the
+.I \-s,
+.I \-r,
+.I \-S,
+or
+.I \-d
+options to specify what data to send or receive.
+
+.TP
+.I \-h, \-\-help
+Help: prints a list of options.
+
+.TP
+.I \-V, \-\-version
+Prints the current version.
+
+.TP
+.I \-l, \-\-list\-devices
+Prints a list of all hardware MIDI ports.
+
+.TP
+.I \-L, \-\-list\-rawmidis
+Prints all RawMIDI definitions.
+(used when debugging configuration files)
+
+.TP
+.I \-p, \-\-port=name
+Sets the name of the ALSA RawMIDI port to use.
+If this is not specified,
+.B amidi
+uses the default port defined in the configuration file
+(the default for this is port 0 on card 0, which may not exist).
+
+.TP
+.I \-s, \-\-send=filename
+Sends the contents of the specified file to the MIDI port.
+The file must contain raw MIDI commands (e.g. a .syx file);
+for Standard MIDI (.mid) files, use
+.B aplaymidi(1).
+
+.TP
+.I \-r, \-\-receive=filename
+Writes data received from the MIDI port into the specified file.
+The file will contain raw MIDI commands (such as in a .syx file);
+to record a Standard MIDI (.mid) file, use
+.B arecordmidi(1).
+
+.B amidi
+will filter out any Active Sensing bytes (FEh), unless the
+.I \-a
+option has been given.
+
+.TP
+.I \-S, \-\-send\-hex="..."
+Sends the bytes specified as hexadecimal numbers to the MIDI port.
+
+.TP
+.I \-d, \-\-dump
+Prints data received from the MIDI port as hexadecimal bytes.
+Active Sensing bytes (FEh) will not be shown, unless the
+.I \-a
+option has been given.
+
+This option is useful for debugging.
+
+.TP
+.I \-t, \-\-timeout=seconds
+Stops receiving data when no data has been received for the specified
+amount of time.
+
+If this option has not been given, you must press Ctrl+C (or kill
+.B amidi\fR)
+to stop receiving data.
+
+.TP
+.I \-a, \-\-active\-sensing
+Does not ignore Active Sensing bytes (FEh) when saving or printing
+received MIDI commands.
+
+.SH EXAMPLES
+
+.TP
+.B amidi \-p hw:0 \-s my_settings.syx
+will send the MIDI commands in
+.I my_settings.syx
+to port
+.I hw:0.
+
+.TP
+.B amidi \-S 'F0 43 10 4C 00 00 7E 00 F7'
+sends an XG Reset to the default port.
+
+.TP
+.B amidi \-p hw:1,2 \-S F0411042110C000000000074F7 \-r dump.syx \-t 1
+sends a \(lqParameter Dump Request\(rq to a GS device, saves the received
+parameter data to the file
+.I dump.syx,
+and stops after the device has finished sending data
+(when no data has been received for one second).
+
+.TP
+.B amidi \-p virtual \-d
+creates a virtual RawMIDI port and prints all data sent to this port.
+
+.SH FILES
+.I /usr/share/alsa/alsa.conf
+default rawmidi definitions
+.br
+.I /etc/asound.conf
+system-wide rawmidi definitions
+.br
+.I ~/.asoundrc
+user specific rawmidi definitions
+
+.SH SEE ALSO
+aplaymidi(1)
+.br
+arecordmidi(1)
+
+.SH AUTHOR
+Clemens Ladisch <clemens@ladisch.de>
diff --git a/amidi/amidi.c b/amidi/amidi.c
new file mode 100644
index 0000000..cedf18c
--- /dev/null
+++ b/amidi/amidi.c
@@ -0,0 +1,620 @@
+/*
+ *  amidi.c - read from/write to RawMIDI ports
+ *
+ *  Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *   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
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/poll.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <alsa/asoundlib.h>
+#include "aconfig.h"
+#include "version.h"
+
+static int do_device_list, do_rawmidi_list;
+static char *port_name = "default";
+static char *send_file_name;
+static char *receive_file_name;
+static char *send_hex;
+static char *send_data;
+static int send_data_length;
+static int receive_file;
+static int dump;
+static int timeout;
+static int stop;
+static snd_rawmidi_t *input, **inputp;
+static snd_rawmidi_t *output, **outputp;
+
+static void error(const char *format, ...)
+{
+	va_list ap;
+
+	va_start(ap, format);
+	vfprintf(stderr, format, ap);
+	va_end(ap);
+	putc('\n', stderr);
+}
+
+static void usage(void)
+{
+	printf(
+		"Usage: amidi options\n"
+		"\n"
+		"-h, --help             this help\n"
+		"-V, --version          print current version\n"
+		"-l, --list-devices     list all hardware ports\n"
+		"-L, --list-rawmidis    list all RawMIDI definitions\n"
+		"-p, --port=name        select port by name\n"
+		"-s, --send=file        send the contents of a (.syx) file\n"
+		"-r, --receive=file     write received data into a file\n"
+		"-S, --send-hex=\"...\"   send hexadecimal bytes\n"
+		"-d, --dump             print received data as hexadecimal bytes\n"
+		"-t, --timeout=seconds  exits when no data has been received\n"
+		"                       for the specified duration\n"
+		"-a, --active-sensing   don't ignore active sensing bytes\n");
+}
+
+static void version(void)
+{
+	puts("amidi version " SND_UTIL_VERSION_STR);
+}
+
+static void *my_malloc(size_t size)
+{
+	void *p = malloc(size);
+	if (!p) {
+		error("out of memory");
+		exit(EXIT_FAILURE);
+	}
+	return p;
+}
+
+static void list_device(snd_ctl_t *ctl, int card, int device)
+{
+	snd_rawmidi_info_t *info;
+	const char *name;
+	const char *sub_name;
+	int subs, subs_in, subs_out;
+	int sub;
+	int err;
+
+	snd_rawmidi_info_alloca(&info);
+	snd_rawmidi_info_set_device(info, device);
+
+	snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);
+	err = snd_ctl_rawmidi_info(ctl, info);
+	if (err >= 0)
+		subs_in = snd_rawmidi_info_get_subdevices_count(info);
+	else
+		subs_in = 0;
+
+	snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);
+	err = snd_ctl_rawmidi_info(ctl, info);
+	if (err >= 0)
+		subs_out = snd_rawmidi_info_get_subdevices_count(info);
+	else
+		subs_out = 0;
+
+	subs = subs_in > subs_out ? subs_in : subs_out;
+	if (!subs)
+		return;
+
+	for (sub = 0; sub < subs; ++sub) {
+		snd_rawmidi_info_set_stream(info, sub < subs_in ?
+					    SND_RAWMIDI_STREAM_INPUT :
+					    SND_RAWMIDI_STREAM_OUTPUT);
+		snd_rawmidi_info_set_subdevice(info, sub);
+		err = snd_ctl_rawmidi_info(ctl, info);
+		if (err < 0) {
+			error("cannot get rawmidi information %d:%d:%d: %s\n",
+			      card, device, sub, snd_strerror(err));
+			return;
+		}
+		name = snd_rawmidi_info_get_name(info);
+		sub_name = snd_rawmidi_info_get_subdevice_name(info);
+		if (sub == 0 && sub_name[0] == '\0') {
+			printf("%c%c  hw:%d,%d    %s",
+			       sub < subs_in ? 'I' : ' ',
+			       sub < subs_out ? 'O' : ' ',
+			       card, device, name);
+			if (subs > 1)
+				printf(" (%d subdevices)", subs);
+			putchar('\n');
+			break;
+		} else {
+			printf("%c%c  hw:%d,%d,%d  %s\n",
+			       sub < subs_in ? 'I' : ' ',
+			       sub < subs_out ? 'O' : ' ',
+			       card, device, sub, sub_name);
+		}
+	}
+}
+
+static void list_card_devices(int card)
+{
+	snd_ctl_t *ctl;
+	char name[32];
+	int device;
+	int err;
+
+	sprintf(name, "hw:%d", card);
+	if ((err = snd_ctl_open(&ctl, name, 0)) < 0) {
+		error("cannot open control for card %d: %s", card, snd_strerror(err));
+		return;
+	}
+	device = -1;
+	for (;;) {
+		if ((err = snd_ctl_rawmidi_next_device(ctl, &device)) < 0) {
+			error("cannot determine device number: %s", snd_strerror(err));
+			break;
+		}
+		if (device < 0)
+			break;
+		list_device(ctl, card, device);
+	}
+	snd_ctl_close(ctl);
+}
+
+static void device_list(void)
+{
+	int card, err;
+
+	card = -1;
+	if ((err = snd_card_next(&card)) < 0) {
+		error("cannot determine card number: %s", snd_strerror(err));
+		return;
+	}
+	if (card < 0) {
+		error("no sound card found");
+		return;
+	}
+	puts("Dir Device    Name");
+	do {
+		list_card_devices(card);
+		if ((err = snd_card_next(&card)) < 0) {
+			error("cannot determine card number: %s", snd_strerror(err));
+			break;
+		}
+	} while (card >= 0);
+}
+
+static void rawmidi_list(void)
+{
+	snd_output_t *output;
+	snd_config_t *config;
+	int err;
+
+	if ((err = snd_config_update()) < 0) {
+		error("snd_config_update failed: %s", snd_strerror(err));
+		return;
+	}
+	if ((err = snd_output_stdio_attach(&output, stdout, 0)) < 0) {
+		error("snd_output_stdio_attach failed: %s", snd_strerror(err));
+		return;
+	}
+	if (snd_config_search(snd_config, "rawmidi", &config) >= 0) {
+		puts("RawMIDI list:");
+		snd_config_save(config, output);
+	}
+	snd_output_close(output);
+}
+
+static void load_file(void)
+{
+	int fd;
+	off_t length;
+
+	fd = open(send_file_name, O_RDONLY);
+	if (fd == -1) {
+		error("cannot open %s - %s", send_file_name, strerror(errno));
+		return;
+	}
+	length = lseek(fd, 0, SEEK_END);
+	if (length == (off_t)-1) {
+		error("cannot determine length of %s: %s", send_file_name, strerror(errno));
+		goto _error;
+	}
+	send_data = my_malloc(length);
+	lseek(fd, 0, SEEK_SET);
+	if (read(fd, send_data, length) != length) {
+		error("cannot read from %s: %s", send_file_name, strerror(errno));
+		goto _error;
+	}
+	if (length >= 4 && !memcmp(send_data, "MThd", 4)) {
+		error("%s is a Standard MIDI File; use aplaymidi to send it", send_file_name);
+		goto _error;
+	}
+	send_data_length = length;
+	goto _exit;
+_error:
+	free(send_data);
+	send_data = NULL;
+_exit:
+	close(fd);
+}
+
+static int hex_value(char c)
+{
+	if ('0' <= c && c <= '9')
+		return c - '0';
+	if ('A' <= c && c <= 'F')
+		return c - 'A' + 10;
+	if ('a' <= c && c <= 'f')
+		return c - 'a' + 10;
+	error("invalid character %c", c);
+	return -1;
+}
+
+static void parse_data(void)
+{
+	const char *p;
+	int i, value;
+
+	send_data = my_malloc(strlen(send_hex)); /* guesstimate */
+	i = 0;
+	value = -1; /* value is >= 0 when the first hex digit of a byte has been read */
+	for (p = send_hex; *p; ++p) {
+		int digit;
+		if (isspace((unsigned char)*p)) {
+			if (value >= 0) {
+				send_data[i++] = value;
+				value = -1;
+			}
+			continue;
+		}
+		digit = hex_value(*p);
+		if (digit < 0) {
+			send_data = NULL;
+			return;
+		}
+		if (value < 0) {
+			value = digit;
+		} else {
+			send_data[i++] = (value << 4) | digit;
+			value = -1;
+		}
+	}
+	if (value >= 0)
+		send_data[i++] = value;
+	send_data_length = i;
+}
+
+/*
+ * prints MIDI commands, formatting them nicely
+ */
+static void print_byte(unsigned char byte)
+{
+	static enum {
+		STATE_UNKNOWN,
+		STATE_1PARAM,
+		STATE_1PARAM_CONTINUE,
+		STATE_2PARAM_1,
+		STATE_2PARAM_2,
+		STATE_2PARAM_1_CONTINUE,
+		STATE_SYSEX
+	} state = STATE_UNKNOWN;
+	int newline = 0;
+
+	if (byte >= 0xf8)
+		newline = 1;
+	else if (byte >= 0xf0) {
+		newline = 1;
+		switch (byte) {
+		case 0xf0:
+			state = STATE_SYSEX;
+			break;
+		case 0xf1:
+		case 0xf3:
+			state = STATE_1PARAM;
+			break;
+		case 0xf2:
+			state = STATE_2PARAM_1;
+			break;
+		case 0xf4:
+		case 0xf5:
+		case 0xf6:
+			state = STATE_UNKNOWN;
+			break;
+		case 0xf7:
+			newline = state != STATE_SYSEX;
+			state = STATE_UNKNOWN;
+			break;
+		}
+	} else if (byte >= 0x80) {
+		newline = 1;
+		if (byte >= 0xc0 && byte <= 0xdf)
+			state = STATE_1PARAM;
+		else
+			state = STATE_2PARAM_1;
+	} else /* b < 0x80 */ {
+		int running_status = 0;
+		newline = state == STATE_UNKNOWN;
+		switch (state) {
+		case STATE_1PARAM:
+			state = STATE_1PARAM_CONTINUE;
+			break;
+		case STATE_1PARAM_CONTINUE:
+			running_status = 1;
+			break;
+		case STATE_2PARAM_1:
+			state = STATE_2PARAM_2;
+			break;
+		case STATE_2PARAM_2:
+			state = STATE_2PARAM_1_CONTINUE;
+			break;
+		case STATE_2PARAM_1_CONTINUE:
+			running_status = 1;
+			state = STATE_2PARAM_2;
+			break;
+		default:
+			break;
+		}
+		if (running_status)
+			fputs("\n  ", stdout);
+	}
+	printf("%c%02X", newline ? '\n' : ' ', byte);
+}
+
+static void sig_handler(int dummy)
+{
+	stop = 1;
+}
+
+static void add_send_hex_data(const char *str)
+{
+	int length;
+	char *s;
+
+	length = (send_hex ? strlen(send_hex) + 1 : 0) + strlen(str) + 1;
+	s = my_malloc(length);
+	if (send_hex) {
+		strcpy(s, send_hex);
+		strcat(s, " ");
+	} else {
+		s[0] = '\0';
+	}
+	strcat(s, str);
+	free(send_hex);
+	send_hex = s;
+}
+
+int main(int argc, char *argv[])
+{
+	static const char short_options[] = "hVlLp:s:r:S::dt:a";
+	static const struct option long_options[] = {
+		{"help", 0, NULL, 'h'},
+		{"version", 0, NULL, 'V'},
+		{"list-devices", 0, NULL, 'l'},
+		{"list-rawmidis", 0, NULL, 'L'},
+		{"port", 1, NULL, 'p'},
+		{"send", 1, NULL, 's'},
+		{"receive", 1, NULL, 'r'},
+		{"send-hex", 2, NULL, 'S'},
+		{"dump", 0, NULL, 'd'},
+		{"timeout", 1, NULL, 't'},
+		{"active-sensing", 0, NULL, 'a'},
+		{ }
+	};
+	int c, err, ok = 0;
+	int ignore_active_sensing = 1;
+	int do_send_hex = 0;
+
+	while ((c = getopt_long(argc, argv, short_options,
+		     		long_options, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			usage();
+			return 0;
+		case 'V':
+			version();
+			return 0;
+		case 'l':
+			do_device_list = 1;
+			break;
+		case 'L':
+			do_rawmidi_list = 1;
+			break;
+		case 'p':
+			port_name = optarg;
+			break;
+		case 's':
+			send_file_name = optarg;
+			break;
+		case 'r':
+			receive_file_name = optarg;
+			break;
+		case 'S':
+			do_send_hex = 1;
+			if (optarg)
+				add_send_hex_data(optarg);
+			break;
+		case 'd':
+			dump = 1;
+			break;
+		case 't':
+			timeout = atoi(optarg);
+			break;
+		case 'a':
+			ignore_active_sensing = 0;
+			break;
+		default:
+			error("Try `amidi --help' for more information.");
+			return 1;
+		}
+	}
+	if (do_send_hex) {
+		/* data for -S can be specified as multiple arguments */
+		if (!send_hex && !argv[optind]) {
+			error("Please specify some data for --send-hex.");
+			return 1;
+		}
+		for (; argv[optind]; ++optind)
+			add_send_hex_data(argv[optind]);
+	} else {
+		if (argv[optind]) {
+			error("%s is not an option.", argv[optind]);
+			return 1;
+		}
+	}
+
+	if (do_rawmidi_list)
+		rawmidi_list();
+	if (do_device_list)
+		device_list();
+	if (do_rawmidi_list || do_device_list)
+		return 0;
+
+	if (!send_file_name && !receive_file_name && !send_hex && !dump) {
+		error("Please specify at least one of --send, --receive, --send-hex, or --dump.");
+		return 1;
+	}
+	if (send_file_name && send_hex) {
+		error("--send and --send-hex cannot be specified at the same time.");
+		return 1;
+	}
+
+	if (send_file_name)
+		load_file();
+	else if (send_hex)
+		parse_data();
+	if ((send_file_name || send_hex) && !send_data)
+		return 1;
+
+	if (receive_file_name) {
+		receive_file = creat(receive_file_name, 0666);
+		if (receive_file == -1) {
+			error("cannot create %s: %s", receive_file_name, strerror(errno));
+			return -1;
+		}
+	} else {
+		receive_file = -1;
+	}
+
+	if (receive_file_name || dump)
+		inputp = &input;
+	else
+		inputp = NULL;
+	if (send_data)
+		outputp = &output;
+	else
+		outputp = NULL;
+
+	if ((err = snd_rawmidi_open(inputp, outputp, port_name, SND_RAWMIDI_NONBLOCK)) < 0) {
+		error("cannot open port \"%s\": %s", port_name, snd_strerror(err));
+		goto _exit2;
+	}
+
+	if (inputp)
+		snd_rawmidi_read(input, NULL, 0); /* trigger reading */
+
+	if (send_data) {
+		if ((err = snd_rawmidi_nonblock(output, 0)) < 0) {
+			error("cannot set blocking mode: %s", snd_strerror(err));
+			goto _exit;
+		}
+		if ((err = snd_rawmidi_write(output, send_data, send_data_length)) < 0) {
+			error("cannot send data: %s", snd_strerror(err));
+			goto _exit;
+		}
+	}
+
+	if (inputp) {
+		int read = 0;
+		int npfds, time = 0;
+		struct pollfd *pfds;
+
+		timeout *= 1000;
+		npfds = snd_rawmidi_poll_descriptors_count(input);
+		pfds = alloca(npfds * sizeof(struct pollfd));
+		snd_rawmidi_poll_descriptors(input, pfds, npfds);
+		signal(SIGINT, sig_handler);
+		for (;;) {
+			unsigned char buf[256];
+			int i, length;
+			unsigned short revents;
+
+			err = poll(pfds, npfds, 200);
+			if (stop || (err < 0 && errno == EINTR))
+				break;
+			if (err < 0) {
+				error("poll failed: %s", strerror(errno));
+				break;
+			}
+			if (err == 0) {
+				time += 200;
+				if (timeout && time >= timeout)
+					break;
+				continue;
+			}
+			if ((err = snd_rawmidi_poll_descriptors_revents(input, pfds, npfds, &revents)) < 0) {
+				error("cannot get poll events: %s", snd_strerror(errno));
+				break;
+			}
+			if (revents & (POLLERR | POLLHUP))
+				break;
+			if (!(revents & POLLIN))
+				continue;
+			err = snd_rawmidi_read(input, buf, sizeof(buf));
+			if (err == -EAGAIN)
+				continue;
+			if (err < 0) {
+				error("cannot read from port \"%s\": %s", port_name, snd_strerror(err));
+				break;
+			}
+			length = 0;
+			for (i = 0; i < err; ++i)
+				if (!ignore_active_sensing || buf[i] != 0xfe)
+					buf[length++] = buf[i];
+			if (length == 0)
+				continue;
+			read += length;
+			time = 0;
+			if (receive_file != -1)
+				write(receive_file, buf, length);
+			if (dump) {
+				for (i = 0; i < length; ++i)
+					print_byte(buf[i]);
+				fflush(stdout);
+			}
+		}
+		if (isatty(fileno(stdout)))
+			printf("\n%d bytes read\n", read);
+	}
+
+	ok = 1;
+_exit:
+	if (inputp)
+		snd_rawmidi_close(input);
+	if (outputp)
+		snd_rawmidi_close(output);
+_exit2:
+	if (receive_file != -1)
+		close(receive_file);
+	return !ok;
+}
diff --git a/amixer/Makefile.am b/amixer/Makefile.am
new file mode 100644
index 0000000..fcd0e81
--- /dev/null
+++ b/amixer/Makefile.am
@@ -0,0 +1,10 @@
+INCLUDES = -I$(top_srcdir)/include
+LDADD = -lm
+# LDFLAGS = -static
+# CFLAGS += -g -Wall
+
+bin_PROGRAMS = amixer
+amixer_SOURCES = amixer.c
+noinst_HEADERS = amixer.h
+man_MANS = amixer.1
+EXTRA_DIST = amixer.1
diff --git a/amixer/amixer.1 b/amixer/amixer.1
new file mode 100644
index 0000000..b1ac323
--- /dev/null
+++ b/amixer/amixer.1
@@ -0,0 +1,141 @@
+.TH AMIXER 1 "11 Aug 2000"
+.SH NAME
+amixer \- command-line mixer for ALSA soundcard driver
+.SH SYNOPSIS
+\fBamixer\fP [\fI\-option\fP] [\fIcmd\fP]
+.SH DESCRIPTION
+\fBamixer\fP allows command-line control of the mixer for the ALSA
+soundcard driver.
+\fBamixer\fP supports multiple soundcards.
+
+\fBamixer\fR with no arguments will display the current mixer settings
+for the default soundcard and device. This is a good way to see a list
+of the simple mixer controls you can use.
+
+.SH COMMANDS
+
+.TP
+\fIhelp\fP
+Shows syntax.
+
+.TP
+\fIinfo\fP
+Shows the information about a mixer device.
+
+.TP
+\fIscontrols\fP
+Shows a complete list of simple mixer controls.
+
+.TP
+\fIscontents\fP
+Shows a complete list of simple mixer controls with their contents.
+
+.TP
+\fIset\fP or \fIsset\fP <\fISCONTROL\fP> <\fIPARAMETER\fP> ...
+Sets the simple mixer control contents. The parameter can be the volume
+either as a percentage from 0% to 100% with \fI%\fP suffix,
+a dB gain with \fIdB\fP suffix (like -12.5dB), or an exact hardware value.
+The dB gain can be used only for the mixer elements with available
+dB information.
+When plus(+) or minus(\-) letter is appended after
+volume value, the volume is incremented or decremented from the current
+value, respectively.
+
+The parameters \fIcap, nocap, mute, unmute, toggle\fP are used to
+change capture (recording) and muting for the group specified.
+
+The optional modifiers can be put as extra parameters to specify
+the stream direction or channels to apply.
+The modifiers \fIplayback\fP and \fIcapture\fP specify the stream,
+and the modifiers \fIfront, rear, center, woofer\fP are used to specify
+channels to be changed. 
+
+A simple mixer control must be specified. Only one device can be controlled
+at a time.
+
+.TP
+\fIget\fP or \fIsget\fP <\fISCONTROL\fP>
+Shows the simple mixer control contents.
+
+A simple mixer control must be specified. Only one device can be controlled
+at a time.
+
+.TP
+\fIcontrols\fP
+Shows a complete list of card controls.
+
+.TP
+\fIcontents\fP
+Shows a complete list of card controls with their contents.
+
+.TP
+\fIcset\fP <\fICONTROL\fP> <\fIPARAMETER\fP> ...
+Sets the card control contents. The identifier has these components: iface,
+name, index, device, subdevice, numid. The next argument specifies the value
+of control.
+
+.TP
+\fIcget\fP <\fICONTROL\fP>
+Shows the card control contents. The identifier has same syntax as for
+the \fIcset\fP command.
+
+.SH OPTIONS
+
+.TP
+\fI\-c\fP card
+
+Select the card number to control. The device name created from this
+parameter has syntax 'hw:N' where N is specified card number.
+
+.TP
+\fI\-D\fP device
+
+Select the device name to control. The default control name is 'default'.
+
+.TP
+\fI\-s\fP | \fI\-\-stdin\fP
+
+Read from stdin and execute the command on each line sequentially.
+When this option is given, the command in command-line arguments is ignored.
+
+Only sset and cset are accepted.  Other commands are ignored.
+The commands to unmatched ids are ignored without errors too.
+
+.TP
+\fI\-h\fP 
+Help: show syntax.
+.TP
+\fI\-q\fP
+Quiet mode. Do not show results of changes.
+
+.SH EXAMPLES
+
+.TP
+\fBamixer \-c 1 sset Line,0 80%,40% unmute cap\fR
+will set the second soundcard's left line input volume to 80% and
+right line input to 40%, unmute it, and select it as a source for
+capture (recording).\fR
+
+.TP
+\fBamixer \-c 1 \-\- sset Master playback -20dB\fR
+will set the master volume of the second card to -20dB.  If the master
+has multiple channels, all channels are set to the same value.
+
+.TP
+\fBamixer \-c 1 set PCM 2dB+\fR
+will increase the PCM volume of the second card with 2dB.  When both
+playback and capture volumes exist, this is applied to both volumes.
+
+.TP
+\fBamixer \-c 2 cset iface=MIXER,name='Line Playback Volume",index=1 40%\fR
+will set the third soundcard's second line playback volume(s) to 40%
+
+.TP
+\fBamixer \-c 2 cset numid=34 40%\fR
+will set the 34th soundcard element to 40%
+
+.SH BUGS 
+None known.
+.SH AUTHOR
+\fBamixer\fP is by Jaroslav Kysela <perex@perex.cz>.
+This document is by Paul Winkler <zarmzarm@erols.com> and Jaroslav Kysela <perex@perex.cz>.
diff --git a/amixer/amixer.c b/amixer/amixer.c
new file mode 100644
index 0000000..9d2855d
--- /dev/null
+++ b/amixer/amixer.c
@@ -0,0 +1,2036 @@
+/*
+ *   ALSA command line mixer utility
+ *   Copyright (c) 1999-2000 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <math.h>
+#include <errno.h>
+#include <assert.h>
+#include <alsa/asoundlib.h>
+#include <sys/poll.h>
+#include "amixer.h"
+
+#define LEVEL_BASIC		(1<<0)
+#define LEVEL_INACTIVE		(1<<1)
+#define LEVEL_ID		(1<<2)
+
+static int quiet = 0;
+static int debugflag = 0;
+static int no_check = 0;
+static int smixer_level = 0;
+static int ignore_error = 0;
+static struct snd_mixer_selem_regopt smixer_options;
+static char card[64] = "default";
+
+static void error(const char *fmt,...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	fprintf(stderr, "amixer: ");
+	vfprintf(stderr, fmt, va);
+	fprintf(stderr, "\n");
+	va_end(va);
+}
+
+static int help(void)
+{
+	printf("Usage: amixer <options> [command]\n");
+	printf("\nAvailable options:\n");
+	printf("  -h,--help       this help\n");
+	printf("  -c,--card N     select the card\n");
+	printf("  -D,--device N   select the device, default '%s'\n", card);
+	printf("  -d,--debug      debug mode\n");
+	printf("  -n,--nocheck    do not perform range checking\n");
+	printf("  -v,--version    print version of this program\n");
+	printf("  -q,--quiet      be quiet\n");
+	printf("  -i,--inactive   show also inactive controls\n");
+	printf("  -a,--abstract L select abstraction level (none or basic)\n");
+	printf("  -s,--stdin      Read and execute commands from stdin sequentially\n");
+	printf("\nAvailable commands:\n");
+	printf("  scontrols       show all mixer simple controls\n");
+	printf("  scontents	  show contents of all mixer simple controls (default command)\n");
+	printf("  sset sID P      set contents for one mixer simple control\n");
+	printf("  sget sID        get contents for one mixer simple control\n");
+	printf("  controls        show all controls for given card\n");
+	printf("  contents        show contents of all controls for given card\n");
+	printf("  cset cID P      set control contents for one control\n");
+	printf("  cget cID        get control contents for one control\n");
+	return 0;
+}
+
+static int info(void)
+{
+	int err;
+	snd_ctl_t *handle;
+	snd_mixer_t *mhandle;
+	snd_ctl_card_info_t *info;
+	snd_ctl_elem_list_t *clist;
+	snd_ctl_card_info_alloca(&info);
+	snd_ctl_elem_list_alloca(&clist);
+	
+	if ((err = snd_ctl_open(&handle, card, 0)) < 0) {
+		error("Control device %s open error: %s", card, snd_strerror(err));
+		return err;
+	}
+	
+	if ((err = snd_ctl_card_info(handle, info)) < 0) {
+		error("Control device %s hw info error: %s", card, snd_strerror(err));
+		return err;
+	}
+	printf("Card %s '%s'/'%s'\n", card, snd_ctl_card_info_get_id(info),
+	       snd_ctl_card_info_get_longname(info));
+	printf("  Mixer name	: '%s'\n", snd_ctl_card_info_get_mixername(info));
+	printf("  Components	: '%s'\n", snd_ctl_card_info_get_components(info));
+	if ((err = snd_ctl_elem_list(handle, clist)) < 0) {
+		error("snd_ctl_elem_list failure: %s", snd_strerror(err));
+	} else {
+		printf("  Controls      : %i\n", snd_ctl_elem_list_get_count(clist));
+	}
+	snd_ctl_close(handle);
+	if ((err = snd_mixer_open(&mhandle, 0)) < 0) {
+		error("Mixer open error: %s", snd_strerror(err));
+		return err;
+	}
+	if (smixer_level == 0 && (err = snd_mixer_attach(mhandle, card)) < 0) {
+		error("Mixer attach %s error: %s", card, snd_strerror(err));
+		snd_mixer_close(mhandle);
+		return err;
+	}
+	if ((err = snd_mixer_selem_register(mhandle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
+		error("Mixer register error: %s", snd_strerror(err));
+		snd_mixer_close(mhandle);
+		return err;
+	}
+	err = snd_mixer_load(mhandle);
+	if (err < 0) {
+		error("Mixer load %s error: %s", card, snd_strerror(err));
+		snd_mixer_close(mhandle);
+		return err;
+	}
+	printf("  Simple ctrls  : %i\n", snd_mixer_get_count(mhandle));
+	snd_mixer_close(mhandle);
+	return 0;
+}
+
+static const char *control_iface(snd_ctl_elem_id_t *id)
+{
+	return snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id));
+}
+
+static const char *control_type(snd_ctl_elem_info_t *info)
+{
+	return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info));
+}
+
+static const char *control_access(snd_ctl_elem_info_t *info)
+{
+	static char result[10];
+	char *res = result;
+
+	*res++ = snd_ctl_elem_info_is_readable(info) ? 'r' : '-';
+	*res++ = snd_ctl_elem_info_is_writable(info) ? 'w' : '-';
+	*res++ = snd_ctl_elem_info_is_inactive(info) ? 'i' : '-';
+	*res++ = snd_ctl_elem_info_is_volatile(info) ? 'v' : '-';
+	*res++ = snd_ctl_elem_info_is_locked(info) ? 'l' : '-';
+	*res++ = snd_ctl_elem_info_is_tlv_readable(info) ? 'R' : '-';
+	*res++ = snd_ctl_elem_info_is_tlv_writable(info) ? 'W' : '-';
+	*res++ = snd_ctl_elem_info_is_tlv_commandable(info) ? 'C' : '-';
+	*res++ = '\0';
+	return result;
+}
+
+#define check_range(val, min, max) \
+	(no_check ? (val) : ((val < min) ? (min) : (val > max) ? (max) : (val))) 
+#if 0
+static int convert_range(int val, int omin, int omax, int nmin, int nmax)
+{
+	int orange = omax - omin, nrange = nmax - nmin;
+	
+	if (orange == 0)
+		return 0;
+	return rint((((double)nrange * ((double)val - (double)omin)) + ((double)orange / 2.0)) / ((double)orange + (double)nmin));
+}
+#endif
+
+#if 0
+static int convert_db_range(int val, int omin, int omax, int nmin, int nmax)
+{
+	int orange = omax - omin, nrange = nmax - nmin;
+	
+	if (orange == 0)
+		return 0;
+	return rint((((double)nrange * ((double)val - (double)omin)) + ((double)orange / 2.0)) / (double)orange + (double)nmin);
+}
+#endif
+
+/* Fuction to convert from volume to percentage. val = volume */
+
+static int convert_prange(int val, int min, int max)
+{
+	int range = max - min;
+	int tmp;
+
+	if (range == 0)
+		return 0;
+	val -= min;
+	tmp = rint((double)val/(double)range * 100);
+	return tmp;
+}
+
+/* Function to convert from percentage to volume. val = percentage */
+
+#define convert_prange1(val, min, max) \
+	ceil((val) * ((max) - (min)) * 0.01 + (min))
+
+static const char *get_percent(int val, int min, int max)
+{
+	static char str[32];
+	int p;
+	
+	p = convert_prange(val, min, max);
+	sprintf(str, "%i [%i%%]", val, p);
+	return str;
+}
+
+#if 0
+static const char *get_percent1(int val, int min, int max, int min_dB, int max_dB)
+{
+	static char str[32];
+	int p, db;
+
+	p = convert_prange(val, min, max);
+	db = convert_db_range(val, min, max, min_dB, max_dB);
+	sprintf(str, "%i [%i%%] [%i.%02idB]", val, p, db / 100, abs(db % 100));
+	return str;
+}
+#endif
+
+static long get_integer(char **ptr, long min, long max)
+{
+	long val = min;
+	char *p = *ptr, *s;
+
+	if (*p == ':')
+		p++;
+	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
+		goto out;
+
+	s = p;
+	val = strtol(s, &p, 10);
+	if (*p == '.') {
+		p++;
+		strtol(p, &p, 10);
+	}
+	if (*p == '%') {
+		val = (long)convert_prange1(strtod(s, NULL), min, max);
+		p++;
+	}
+	val = check_range(val, min, max);
+	if (*p == ',')
+		p++;
+ out:
+	*ptr = p;
+	return val;
+}
+
+static long get_integer64(char **ptr, long long min, long long max)
+{
+	long long val = min;
+	char *p = *ptr, *s;
+
+	if (*p == ':')
+		p++;
+	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
+		goto out;
+
+	s = p;
+	val = strtol(s, &p, 10);
+	if (*p == '.') {
+		p++;
+		strtol(p, &p, 10);
+	}
+	if (*p == '%') {
+		val = (long long)convert_prange1(strtod(s, NULL), min, max);
+		p++;
+	}
+	val = check_range(val, min, max);
+	if (*p == ',')
+		p++;
+ out:
+	*ptr = p;
+	return val;
+}
+
+struct volume_ops {
+	int (*get_range)(snd_mixer_elem_t *elem, long *min, long *max);
+	int (*get)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c,
+		   long *value);
+	int (*set)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c,
+		   long value);
+};
+	
+enum { VOL_RAW, VOL_DB };
+
+struct volume_ops_set {
+	int (*has_volume)(snd_mixer_elem_t *elem);
+	struct volume_ops v[2];
+};
+
+static int set_playback_dB(snd_mixer_elem_t *elem,
+			   snd_mixer_selem_channel_id_t c, long value)
+{
+	return snd_mixer_selem_set_playback_dB(elem, c, value, 0);
+}
+
+static int set_capture_dB(snd_mixer_elem_t *elem,
+			  snd_mixer_selem_channel_id_t c, long value)
+{
+	return snd_mixer_selem_set_capture_dB(elem, c, value, 0);
+}
+
+static const struct volume_ops_set vol_ops[2] = {
+	{
+		.has_volume = snd_mixer_selem_has_playback_volume,
+		.v = {{ snd_mixer_selem_get_playback_volume_range,
+			snd_mixer_selem_get_playback_volume,
+			snd_mixer_selem_set_playback_volume },
+		      { snd_mixer_selem_get_playback_dB_range,
+			snd_mixer_selem_get_playback_dB,
+			set_playback_dB }},
+	},
+	{
+		.has_volume = snd_mixer_selem_has_capture_volume,
+		.v = {{ snd_mixer_selem_get_capture_volume_range,
+			snd_mixer_selem_get_capture_volume,
+			snd_mixer_selem_set_capture_volume },
+		      { snd_mixer_selem_get_capture_dB_range,
+			snd_mixer_selem_get_capture_dB,
+			set_capture_dB }},
+	},
+};
+
+static int set_volume_simple(snd_mixer_elem_t *elem,
+			     snd_mixer_selem_channel_id_t chn,
+			     char **ptr, int dir)
+{
+	long val, orig, pmin, pmax;
+	char *p = *ptr, *s;
+	int invalid = 0, err = 0, vol_type = VOL_RAW;
+
+	if (! vol_ops[dir].has_volume(elem))
+		invalid = 1;
+
+	if (*p == ':')
+		p++;
+	if (*p == '\0' || (!isdigit(*p) && *p != '-'))
+		goto skip;
+
+	if (! invalid &&
+	    vol_ops[dir].v[VOL_RAW].get_range(elem, &pmin, &pmax) < 0)
+		invalid = 1;
+
+	s = p;
+	val = strtol(s, &p, 10);
+	if (*p == '.') {
+		p++;
+		strtol(p, &p, 10);
+	}
+	if (*p == '%') {
+		if (! invalid)
+			val = (long)convert_prange1(strtod(s, NULL), pmin, pmax);
+		p++;
+	} else if (p[0] == 'd' && p[1] == 'B') {
+		if (! invalid) {
+			val = (long)(strtod(s, NULL) * 100.0);
+			vol_type = VOL_DB;
+			if (vol_ops[dir].v[vol_type].get_range(elem, &pmin, &pmax) < 0)
+				invalid = 1;
+		}
+		p += 2;
+	}
+	if (*p == '+' || *p == '-') {
+		if (! invalid) {
+			if (vol_ops[dir].v[vol_type].get(elem, chn, &orig) < 0)
+				invalid = 1;
+			if (*p == '+')
+				val = orig + val;
+			else
+				val = orig - val;
+		}
+		p++;
+	}
+	if (! invalid) {
+		val = check_range(val, pmin, pmax);
+		err = vol_ops[dir].v[vol_type].set(elem, chn, val);
+	}
+ skip:
+	if (*p == ',')
+		p++;
+	*ptr = p;
+	return err ? err : (invalid ? -ENOENT : 0);
+}
+
+static int get_bool_simple(char **ptr, char *str, int invert, int orig)
+{
+	if (**ptr == ':')
+		(*ptr)++;
+	if (!strncasecmp(*ptr, str, strlen(str))) {
+		orig = 1 ^ (invert ? 1 : 0);
+		while (**ptr != '\0' && **ptr != ',' && **ptr != ':')
+			(*ptr)++;
+	}
+	if (**ptr == ',' || **ptr == ':')
+		(*ptr)++;
+	return orig;
+}
+		
+static int simple_skip_word(char **ptr, char *str)
+{
+	char *xptr = *ptr;
+	if (*xptr == ':')
+		xptr++;
+	if (!strncasecmp(xptr, str, strlen(str))) {
+		while (*xptr != '\0' && *xptr != ',' && *xptr != ':')
+			xptr++;
+		if (*xptr == ',' || *xptr == ':')
+			xptr++;
+		*ptr = xptr;
+		return 1;
+	}
+	return 0;
+}
+		
+static void show_control_id(snd_ctl_elem_id_t *id)
+{
+	unsigned int index, device, subdevice;
+	printf("numid=%u,iface=%s,name='%s'",
+	       snd_ctl_elem_id_get_numid(id),
+	       control_iface(id),
+	       snd_ctl_elem_id_get_name(id));
+	index = snd_ctl_elem_id_get_index(id);
+	device = snd_ctl_elem_id_get_device(id);
+	subdevice = snd_ctl_elem_id_get_subdevice(id);
+	if (index)
+		printf(",index=%i", index);
+	if (device)
+		printf(",device=%i", device);
+	if (subdevice)
+		printf(",subdevice=%i", subdevice);
+}
+
+static void print_spaces(unsigned int spaces)
+{
+	while (spaces-- > 0)
+		putc(' ', stdout);
+}
+
+static void print_dB(long dB)
+{
+	printf("%li.%02lidB", dB / 100, (dB < 0 ? -dB : dB) % 100);
+}
+
+static void decode_tlv(unsigned int spaces, unsigned int *tlv, unsigned int tlv_size)
+{
+	unsigned int type = tlv[0];
+	unsigned int size;
+	unsigned int idx = 0;
+
+	if (tlv_size < 2 * sizeof(unsigned int)) {
+		printf("TLV size error!\n");
+		return;
+	}
+	print_spaces(spaces);
+	printf("| ");
+	type = tlv[idx++];
+	size = tlv[idx++];
+	tlv_size -= 2 * sizeof(unsigned int);
+	if (size > tlv_size) {
+		printf("TLV size error (%i, %i, %i)!\n", type, size, tlv_size);
+		return;
+	}
+	switch (type) {
+	case SND_CTL_TLVT_CONTAINER:
+		size += sizeof(unsigned int) -1;
+		size /= sizeof(unsigned int);
+		while (idx < size) {
+			if (tlv[idx+1] > (size - idx) * sizeof(unsigned int)) {
+				printf("TLV size error in compound!\n");
+				return;
+			}
+			decode_tlv(spaces + 2, tlv + idx, tlv[idx+1]);
+			idx += 2 + (tlv[1] + sizeof(unsigned int) - 1) / sizeof(unsigned int);
+		}
+		break;
+	case SND_CTL_TLVT_DB_SCALE:
+		printf("dBscale-");
+		if (size != 2 * sizeof(unsigned int)) {
+			while (size > 0) {
+				printf("0x%08x,", tlv[idx++]);
+				size -= sizeof(unsigned int);
+			}
+		} else {
+			printf("min=");
+			print_dB((int)tlv[2]);
+			printf(",step=");
+			print_dB(tlv[3] & 0xffff);
+			printf(",mute=%i", (tlv[3] >> 16) & 1);
+		}
+		break;
+#ifdef SND_CTL_TLVT_DB_LINEAR
+	case SND_CTL_TLVT_DB_LINEAR:
+		printf("dBlinear-");
+		if (size != 2 * sizeof(unsigned int)) {
+			while (size > 0) {
+				printf("0x%08x,", tlv[idx++]);
+				size -= sizeof(unsigned int);
+			}
+		} else {
+			printf("min=");
+			print_dB(tlv[2]);
+			printf(",max=");
+			print_dB(tlv[3]);
+		}
+		break;
+#endif
+#ifdef SND_CTL_TLVT_DB_RANGE
+	case SND_CTL_TLVT_DB_RANGE:
+		printf("dBrange-\n");
+		if ((size % (6 * sizeof(unsigned int))) != 0) {
+			while (size > 0) {
+				printf("0x%08x,", tlv[idx++]);
+				size -= sizeof(unsigned int);
+			}
+			break;
+		}
+		while (size > 0) {
+			print_spaces(spaces + 2);
+			printf("rangemin=%i,", tlv[idx++]);
+			printf(",rangemax=%i\n", tlv[idx++]);
+			decode_tlv(spaces + 4, tlv + idx, 4 * sizeof(unsigned int));
+			idx += 4;
+			size -= 6 * sizeof(unsigned int);
+		}
+		break;
+#endif
+#ifdef SND_CTL_TLVT_DB_MINMAX
+	case SND_CTL_TLVT_DB_MINMAX:
+	case SND_CTL_TLVT_DB_MINMAX_MUTE:
+		if (type == SND_CTL_TLVT_DB_MINMAX_MUTE)
+			printf("dBminmaxmute-");
+		else
+			printf("dBminmax-");
+		if (size != 2 * sizeof(unsigned int)) {
+			while (size > 0) {
+				printf("0x%08x,", tlv[idx++]);
+				size -= sizeof(unsigned int);
+			}
+		} else {
+			printf("min=");
+			print_dB(tlv[2]);
+			printf(",max=");
+			print_dB(tlv[3]);
+		}
+		break;
+#endif
+	default:
+		printf("unk-%i-", type);
+		while (size > 0) {
+			printf("0x%08x,", tlv[idx++]);
+			size -= sizeof(unsigned int);
+		}
+		break;
+	}
+	putc('\n', stdout);
+}
+
+static int show_control(const char *space, snd_hctl_elem_t *elem,
+			int level)
+{
+	int err;
+	unsigned int item, idx, count, *tlv;
+	snd_ctl_elem_type_t type;
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_value_t *control;
+	snd_aes_iec958_t iec958;
+	snd_ctl_elem_id_alloca(&id);
+	snd_ctl_elem_info_alloca(&info);
+	snd_ctl_elem_value_alloca(&control);
+	if ((err = snd_hctl_elem_info(elem, info)) < 0) {
+		error("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err));
+		return err;
+	}
+	if (level & LEVEL_ID) {
+		snd_hctl_elem_get_id(elem, id);
+		show_control_id(id);
+		printf("\n");
+	}
+	count = snd_ctl_elem_info_get_count(info);
+	type = snd_ctl_elem_info_get_type(info);
+	printf("%s; type=%s,access=%s,values=%i", space, control_type(info), control_access(info), count);
+	switch (type) {
+	case SND_CTL_ELEM_TYPE_INTEGER:
+		printf(",min=%li,max=%li,step=%li\n", 
+		       snd_ctl_elem_info_get_min(info),
+		       snd_ctl_elem_info_get_max(info),
+		       snd_ctl_elem_info_get_step(info));
+		break;
+	case SND_CTL_ELEM_TYPE_INTEGER64:
+		printf(",min=%Li,max=%Li,step=%Li\n", 
+		       snd_ctl_elem_info_get_min64(info),
+		       snd_ctl_elem_info_get_max64(info),
+		       snd_ctl_elem_info_get_step64(info));
+		break;
+	case SND_CTL_ELEM_TYPE_ENUMERATED:
+	{
+		unsigned int items = snd_ctl_elem_info_get_items(info);
+		printf(",items=%u\n", items);
+		for (item = 0; item < items; item++) {
+			snd_ctl_elem_info_set_item(info, item);
+			if ((err = snd_hctl_elem_info(elem, info)) < 0) {
+				error("Control %s element info error: %s\n", card, snd_strerror(err));
+				return err;
+			}
+			printf("%s; Item #%u '%s'\n", space, item, snd_ctl_elem_info_get_item_name(info));
+		}
+		break;
+	}
+	default:
+		printf("\n");
+		break;
+	}
+	if (level & LEVEL_BASIC) {
+		if (!snd_ctl_elem_info_is_readable(info))
+			goto __skip_read;
+		if ((err = snd_hctl_elem_read(elem, control)) < 0) {
+			error("Control %s element read error: %s\n", card, snd_strerror(err));
+			return err;
+		}
+		printf("%s: values=", space);
+		for (idx = 0; idx < count; idx++) {
+			if (idx > 0)
+				printf(",");
+			switch (type) {
+			case SND_CTL_ELEM_TYPE_BOOLEAN:
+				printf("%s", snd_ctl_elem_value_get_boolean(control, idx) ? "on" : "off");
+				break;
+			case SND_CTL_ELEM_TYPE_INTEGER:
+				printf("%li", snd_ctl_elem_value_get_integer(control, idx));
+				break;
+			case SND_CTL_ELEM_TYPE_INTEGER64:
+				printf("%Li", snd_ctl_elem_value_get_integer64(control, idx));
+				break;
+			case SND_CTL_ELEM_TYPE_ENUMERATED:
+				printf("%u", snd_ctl_elem_value_get_enumerated(control, idx));
+				break;
+			case SND_CTL_ELEM_TYPE_BYTES:
+				printf("0x%02x", snd_ctl_elem_value_get_byte(control, idx));
+				break;
+			case SND_CTL_ELEM_TYPE_IEC958:
+				snd_ctl_elem_value_get_iec958(control, &iec958);
+				printf("[AES0=0x%02x AES1=0x%02x AES2=0x%02x AES3=0x%02x]",
+				       iec958.status[0], iec958.status[1],
+				       iec958.status[2], iec958.status[3]);
+				break;
+			default:
+				printf("?");
+				break;
+			}
+		}
+		printf("\n");
+	      __skip_read:
+		if (!snd_ctl_elem_info_is_tlv_readable(info))
+			goto __skip_tlv;
+		tlv = malloc(4096);
+		if ((err = snd_hctl_elem_tlv_read(elem, tlv, 4096)) < 0) {
+			error("Control %s element TLV read error: %s\n", card, snd_strerror(err));
+			free(tlv);
+			return err;
+		}
+		decode_tlv(strlen(space), tlv, 4096);
+		free(tlv);
+	}
+      __skip_tlv:
+	return 0;
+}
+
+static int controls(int level)
+{
+	int err;
+	snd_hctl_t *handle;
+	snd_hctl_elem_t *elem;
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_id_alloca(&id);
+	snd_ctl_elem_info_alloca(&info);
+	
+	if ((err = snd_hctl_open(&handle, card, 0)) < 0) {
+		error("Control %s open error: %s", card, snd_strerror(err));
+		return err;
+	}
+	if ((err = snd_hctl_load(handle)) < 0) {
+		error("Control %s local error: %s\n", card, snd_strerror(err));
+		return err;
+	}
+	for (elem = snd_hctl_first_elem(handle); elem; elem = snd_hctl_elem_next(elem)) {
+		if ((err = snd_hctl_elem_info(elem, info)) < 0) {
+			error("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err));
+			return err;
+		}
+		if (!(level & LEVEL_INACTIVE) && snd_ctl_elem_info_is_inactive(info))
+			continue;
+		snd_hctl_elem_get_id(elem, id);
+		show_control_id(id);
+		printf("\n");
+		if (level & LEVEL_BASIC)
+			show_control("  ", elem, 1);
+	}
+	snd_hctl_close(handle);
+	return 0;
+}
+
+static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char *space, int level)
+{
+	snd_mixer_selem_channel_id_t chn;
+	long pmin = 0, pmax = 0;
+	long cmin = 0, cmax = 0;
+	long pvol, cvol;
+	int psw, csw;
+	int pmono, cmono, mono_ok = 0;
+	long db;
+	snd_mixer_elem_t *elem;
+	
+	elem = snd_mixer_find_selem(handle, id);
+	if (!elem) {
+		error("Mixer %s simple element not found", card);
+		return -ENOENT;
+	}
+
+	if (level & LEVEL_BASIC) {
+		printf("%sCapabilities:", space);
+		if (snd_mixer_selem_has_common_volume(elem)) {
+			printf(" volume");
+			if (snd_mixer_selem_has_playback_volume_joined(elem))
+				printf(" volume-joined");
+		} else {
+			if (snd_mixer_selem_has_playback_volume(elem)) {
+				printf(" pvolume");
+				if (snd_mixer_selem_has_playback_volume_joined(elem))
+					printf(" pvolume-joined");
+			}
+			if (snd_mixer_selem_has_capture_volume(elem)) {
+				printf(" cvolume");
+				if (snd_mixer_selem_has_capture_volume_joined(elem))
+					printf(" cvolume-joined");
+			}
+		}
+		if (snd_mixer_selem_has_common_switch(elem)) {
+			printf(" switch");
+			if (snd_mixer_selem_has_playback_switch_joined(elem))
+				printf(" switch-joined");
+		} else {
+			if (snd_mixer_selem_has_playback_switch(elem)) {
+				printf(" pswitch");
+				if (snd_mixer_selem_has_playback_switch_joined(elem))
+					printf(" pswitch-joined");
+			}
+			if (snd_mixer_selem_has_capture_switch(elem)) {
+				printf(" cswitch");
+				if (snd_mixer_selem_has_capture_switch_joined(elem))
+					printf(" cswitch-joined");
+				if (snd_mixer_selem_has_capture_switch_exclusive(elem))
+					printf(" cswitch-exclusive");
+			}
+		}
+		if (snd_mixer_selem_is_enum_playback(elem)) {
+			printf(" penum");
+		} else if (snd_mixer_selem_is_enum_capture(elem)) {
+			printf(" cenum");
+		} else if (snd_mixer_selem_is_enumerated(elem)) {
+			printf(" enum");
+		}
+		printf("\n");
+		if (snd_mixer_selem_is_enumerated(elem)) {
+			int i, items;
+			unsigned int idx;
+			char itemname[40];
+			items = snd_mixer_selem_get_enum_items(elem);
+			printf("  Items:");
+			for (i = 0; i < items; i++) {
+				snd_mixer_selem_get_enum_item_name(elem, i, sizeof(itemname) - 1, itemname);
+				printf(" '%s'", itemname);
+			}
+			printf("\n");
+			for (i = 0; !snd_mixer_selem_get_enum_item(elem, i, &idx); i++) {
+				snd_mixer_selem_get_enum_item_name(elem, idx, sizeof(itemname) - 1, itemname);
+				printf("  Item%d: '%s'\n", i, itemname);
+			}
+			return 0; /* no more thing to do */
+		}
+		if (snd_mixer_selem_has_capture_switch_exclusive(elem))
+			printf("%sCapture exclusive group: %i\n", space,
+			       snd_mixer_selem_get_capture_group(elem));
+		if (snd_mixer_selem_has_playback_volume(elem) ||
+		    snd_mixer_selem_has_playback_switch(elem)) {
+			printf("%sPlayback channels:", space);
+			if (snd_mixer_selem_is_playback_mono(elem)) {
+				printf(" Mono");
+			} else {
+				int first = 1;
+				for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++){
+					if (!snd_mixer_selem_has_playback_channel(elem, chn))
+						continue;
+					if (!first)
+						printf(" -");
+					printf(" %s", snd_mixer_selem_channel_name(chn));
+					first = 0;
+				}
+			}
+			printf("\n");
+		}
+		if (snd_mixer_selem_has_capture_volume(elem) ||
+		    snd_mixer_selem_has_capture_switch(elem)) {
+			printf("%sCapture channels:", space);
+			if (snd_mixer_selem_is_capture_mono(elem)) {
+				printf(" Mono");
+			} else {
+				int first = 1;
+				for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++){
+					if (!snd_mixer_selem_has_capture_channel(elem, chn))
+						continue;
+					if (!first)
+						printf(" -");
+					printf(" %s", snd_mixer_selem_channel_name(chn));
+					first = 0;
+				}
+			}
+			printf("\n");
+		}
+		if (snd_mixer_selem_has_playback_volume(elem) ||
+		    snd_mixer_selem_has_capture_volume(elem)) {
+			printf("%sLimits:", space);
+			if (snd_mixer_selem_has_common_volume(elem)) {
+				snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax);
+				snd_mixer_selem_get_capture_volume_range(elem, &cmin, &cmax);
+				printf(" %li - %li", pmin, pmax);
+			} else {
+				if (snd_mixer_selem_has_playback_volume(elem)) {
+					snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax);
+					printf(" Playback %li - %li", pmin, pmax);
+				}
+				if (snd_mixer_selem_has_capture_volume(elem)) {
+					snd_mixer_selem_get_capture_volume_range(elem, &cmin, &cmax);
+					printf(" Capture %li - %li", cmin, cmax);
+				}
+			}
+			printf("\n");
+		}
+		pmono = snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_MONO) &&
+		        (snd_mixer_selem_is_playback_mono(elem) || 
+			 (!snd_mixer_selem_has_playback_volume(elem) &&
+			  !snd_mixer_selem_has_playback_switch(elem)));
+		cmono = snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_MONO) &&
+		        (snd_mixer_selem_is_capture_mono(elem) || 
+			 (!snd_mixer_selem_has_capture_volume(elem) &&
+			  !snd_mixer_selem_has_capture_switch(elem)));
+#if 0
+		printf("pmono = %i, cmono = %i (%i, %i, %i, %i)\n", pmono, cmono,
+				snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_MONO),
+				snd_mixer_selem_is_capture_mono(elem),
+				snd_mixer_selem_has_capture_volume(elem),
+				snd_mixer_selem_has_capture_switch(elem));
+#endif
+		if (pmono || cmono) {
+			if (!mono_ok) {
+				printf("%s%s:", space, "Mono");
+				mono_ok = 1;
+			}
+			if (snd_mixer_selem_has_common_volume(elem)) {
+				snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &pvol);
+				printf(" %s", get_percent(pvol, pmin, pmax));
+				if (!snd_mixer_selem_get_playback_dB(elem, SND_MIXER_SCHN_MONO, &db)) {
+					printf(" [");
+					print_dB(db);
+					printf("]");
+				}
+			}
+			if (snd_mixer_selem_has_common_switch(elem)) {
+				snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &psw);
+				printf(" [%s]", psw ? "on" : "off");
+			}
+		}
+		if (pmono && snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_MONO)) {
+			int title = 0;
+			if (!mono_ok) {
+				printf("%s%s:", space, "Mono");
+				mono_ok = 1;
+			}
+			if (!snd_mixer_selem_has_common_volume(elem)) {
+				if (snd_mixer_selem_has_playback_volume(elem)) {
+					printf(" Playback");
+					title = 1;
+					snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &pvol);
+					printf(" %s", get_percent(pvol, pmin, pmax));
+					if (!snd_mixer_selem_get_playback_dB(elem, SND_MIXER_SCHN_MONO, &db)) {
+						printf(" [");
+						print_dB(db);
+						printf("]");
+					}
+				}
+			}
+			if (!snd_mixer_selem_has_common_switch(elem)) {
+				if (snd_mixer_selem_has_playback_switch(elem)) {
+					if (!title)
+						printf(" Playback");
+					snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &psw);
+					printf(" [%s]", psw ? "on" : "off");
+				}
+			}
+		}
+		if (cmono && snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_MONO)) {
+			int title = 0;
+			if (!mono_ok) {
+				printf("%s%s:", space, "Mono");
+				mono_ok = 1;
+			}
+			if (!snd_mixer_selem_has_common_volume(elem)) {
+				if (snd_mixer_selem_has_capture_volume(elem)) {
+					printf(" Capture");
+					title = 1;
+					snd_mixer_selem_get_capture_volume(elem, SND_MIXER_SCHN_MONO, &cvol);
+					printf(" %s", get_percent(cvol, cmin, cmax));
+					if (!snd_mixer_selem_get_capture_dB(elem, SND_MIXER_SCHN_MONO, &db)) {
+						printf(" [");
+						print_dB(db);
+						printf("]");
+					}
+				}
+			}
+			if (!snd_mixer_selem_has_common_switch(elem)) {
+				if (snd_mixer_selem_has_capture_switch(elem)) {
+					if (!title)
+						printf(" Capture");
+					snd_mixer_selem_get_capture_switch(elem, SND_MIXER_SCHN_MONO, &csw);
+					printf(" [%s]", csw ? "on" : "off");
+				}
+			}
+		}
+		if (pmono || cmono)
+			printf("\n");
+		if (!pmono || !cmono) {
+			for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
+				if ((pmono || !snd_mixer_selem_has_playback_channel(elem, chn)) &&
+				    (cmono || !snd_mixer_selem_has_capture_channel(elem, chn)))
+					continue;
+				printf("%s%s:", space, snd_mixer_selem_channel_name(chn));
+				if (!pmono && !cmono && snd_mixer_selem_has_common_volume(elem)) {
+					snd_mixer_selem_get_playback_volume(elem, chn, &pvol);
+					printf(" %s", get_percent(pvol, pmin, pmax));
+					if (!snd_mixer_selem_get_playback_dB(elem, chn, &db)) {
+						printf(" [");
+						print_dB(db);
+						printf("]");
+					}
+				}
+				if (!pmono && !cmono && snd_mixer_selem_has_common_switch(elem)) {
+					snd_mixer_selem_get_playback_switch(elem, chn, &psw);
+					printf(" [%s]", psw ? "on" : "off");
+				}
+				if (!pmono && snd_mixer_selem_has_playback_channel(elem, chn)) {
+					int title = 0;
+					if (!snd_mixer_selem_has_common_volume(elem)) {
+						if (snd_mixer_selem_has_playback_volume(elem)) {
+							printf(" Playback");
+							title = 1;
+							snd_mixer_selem_get_playback_volume(elem, chn, &pvol);
+							printf(" %s", get_percent(pvol, pmin, pmax));
+							if (!snd_mixer_selem_get_playback_dB(elem, chn, &db)) {
+								printf(" [");
+								print_dB(db);
+								printf("]");
+							}
+						}
+					}
+					if (!snd_mixer_selem_has_common_switch(elem)) {
+						if (snd_mixer_selem_has_playback_switch(elem)) {
+							if (!title)
+								printf(" Playback");
+							snd_mixer_selem_get_playback_switch(elem, chn, &psw);
+							printf(" [%s]", psw ? "on" : "off");
+						}
+					}
+				}
+				if (!cmono && snd_mixer_selem_has_capture_channel(elem, chn)) {
+					int title = 0;
+					if (!snd_mixer_selem_has_common_volume(elem)) {
+						if (snd_mixer_selem_has_capture_volume(elem)) {
+							printf(" Capture");
+							title = 1;
+							snd_mixer_selem_get_capture_volume(elem, chn, &cvol);
+							printf(" %s", get_percent(cvol, cmin, cmax));
+							if (!snd_mixer_selem_get_capture_dB(elem, chn, &db)) {
+								printf(" [");
+								print_dB(db);
+								printf("]");
+							}
+						}
+					}
+					if (!snd_mixer_selem_has_common_switch(elem)) {
+						if (snd_mixer_selem_has_capture_switch(elem)) {
+							if (!title)
+								printf(" Capture");
+							snd_mixer_selem_get_capture_switch(elem, chn, &csw);
+							printf(" [%s]", csw ? "on" : "off");
+						}
+					}
+				}
+				printf("\n");
+			}
+		}
+	}
+	return 0;
+}
+
+static int selems(int level)
+{
+	int err;
+	snd_mixer_t *handle;
+	snd_mixer_selem_id_t *sid;
+	snd_mixer_elem_t *elem;
+	snd_mixer_selem_id_alloca(&sid);
+	
+	if ((err = snd_mixer_open(&handle, 0)) < 0) {
+		error("Mixer %s open error: %s", card, snd_strerror(err));
+		return err;
+	}
+	if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) {
+		error("Mixer attach %s error: %s", card, snd_strerror(err));
+		snd_mixer_close(handle);
+		return err;
+	}
+	if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
+		error("Mixer register error: %s", snd_strerror(err));
+		snd_mixer_close(handle);
+		return err;
+	}
+	err = snd_mixer_load(handle);
+	if (err < 0) {
+		error("Mixer %s load error: %s", card, snd_strerror(err));
+		snd_mixer_close(handle);
+		return err;
+	}
+	for (elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next(elem)) {
+		snd_mixer_selem_get_id(elem, sid);
+		if (!(level & LEVEL_INACTIVE) && !snd_mixer_selem_is_active(elem))
+			continue;
+		printf("Simple mixer control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
+		show_selem(handle, sid, "  ", level);
+	}
+	snd_mixer_close(handle);
+	return 0;
+}
+
+static int parse_control_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) {
+				fprintf(stderr, "amixer: 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;
+}
+
+static int parse_simple_id(const char *str, snd_mixer_selem_id_t *sid)
+{
+	int c, size;
+	char buf[128];
+	char *ptr = buf;
+
+	while (*str == ' ' || *str == '\t')
+		str++;
+	if (!(*str))
+		return -EINVAL;
+	size = 1;	/* for '\0' */
+	if (*str != '"' && *str != '\'') {
+		while (*str && *str != ',') {
+			if (size < (int)sizeof(buf)) {
+				*ptr++ = *str;
+				size++;
+			}
+			str++;
+		}
+	} else {
+		c = *str++;
+		while (*str && *str != c) {
+			if (size < (int)sizeof(buf)) {
+				*ptr++ = *str;
+				size++;
+			}
+			str++;
+		}
+		if (*str == c)
+			str++;
+	}
+	if (*str == '\0') {
+		snd_mixer_selem_id_set_index(sid, 0);
+		*ptr = 0;
+		goto _set;
+	}
+	if (*str != ',')
+		return -EINVAL;
+	*ptr = 0;	/* terminate the string */
+	str++;
+	if (!isdigit(*str))
+		return -EINVAL;
+	snd_mixer_selem_id_set_index(sid, atoi(str));
+       _set:
+	snd_mixer_selem_id_set_name(sid, buf);
+	return 0;
+}
+
+static int get_ctl_enum_item_index(snd_ctl_t *handle, snd_ctl_elem_info_t *info,
+				   char **ptrp)
+{
+	char *ptr = *ptrp;
+	int items, i, len;
+	const char *name;
+	
+	items = snd_ctl_elem_info_get_items(info);
+	if (items <= 0)
+		return -1;
+
+	for (i = 0; i < items; i++) {
+		snd_ctl_elem_info_set_item(info, i);
+		if (snd_ctl_elem_info(handle, info) < 0)
+			return -1;
+		name = snd_ctl_elem_info_get_item_name(info);
+		len = strlen(name);
+		if (! strncmp(name, ptr, len)) {
+			if (! ptr[len] || ptr[len] == ',' || ptr[len] == '\n') {
+				ptr += len;
+				*ptrp = ptr;
+				return i;
+			}
+		}
+	}
+	return -1;
+}
+
+static int cset(int argc, char *argv[], int roflag, int keep_handle)
+{
+	int err;
+	static snd_ctl_t *handle = NULL;
+	snd_ctl_elem_info_t *info;
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_value_t *control;
+	char *ptr;
+	unsigned int idx, count;
+	long tmp;
+	snd_ctl_elem_type_t type;
+	snd_ctl_elem_info_alloca(&info);
+	snd_ctl_elem_id_alloca(&id);
+	snd_ctl_elem_value_alloca(&control);
+
+	if (argc < 1) {
+		fprintf(stderr, "Specify a full control identifier: [[iface=<iface>,][name='name',][index=<index>,][device=<device>,][subdevice=<subdevice>]]|[numid=<numid>]\n");
+		return -EINVAL;
+	}
+	if (parse_control_id(argv[0], id)) {
+		fprintf(stderr, "Wrong control identifier: %s\n", argv[0]);
+		return -EINVAL;
+	}
+	if (debugflag) {
+		printf("VERIFY ID: ");
+		show_control_id(id);
+		printf("\n");
+	}
+	if (handle == NULL &&
+	    (err = snd_ctl_open(&handle, card, 0)) < 0) {
+		error("Control %s open error: %s\n", card, snd_strerror(err));
+		return err;
+	}
+	snd_ctl_elem_info_set_id(info, id);
+	if ((err = snd_ctl_elem_info(handle, info)) < 0) {
+		if (ignore_error)
+			return 0;
+		error("Cannot find the given element from control %s\n", card);
+		if (! keep_handle) {
+			snd_ctl_close(handle);
+			handle = NULL;
+		}
+		return err;
+	}
+	snd_ctl_elem_info_get_id(info, id);	/* FIXME: Remove it when hctl find works ok !!! */
+	type = snd_ctl_elem_info_get_type(info);
+	count = snd_ctl_elem_info_get_count(info);
+	snd_ctl_elem_value_set_id(control, id);
+	
+	if (!roflag) {
+		ptr = argv[1];
+		for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) {
+			switch (type) {
+			case SND_CTL_ELEM_TYPE_BOOLEAN:
+				tmp = 0;
+				if (!strncasecmp(ptr, "on", 2) || !strncasecmp(ptr, "up", 2)) {
+					tmp = 1;
+					ptr += 2;
+				} else if (!strncasecmp(ptr, "yes", 3)) {
+					tmp = 1;
+					ptr += 3;
+				} else if (!strncasecmp(ptr, "toggle", 6)) {
+					tmp = snd_ctl_elem_value_get_boolean(control, idx);
+					tmp = tmp > 0 ? 0 : 1;
+					ptr += 6;
+				} else if (isdigit(*ptr)) {
+					tmp = atoi(ptr) > 0 ? 1 : 0;
+					while (isdigit(*ptr))
+						ptr++;
+				} else {
+					while (*ptr && *ptr != ',')
+						ptr++;
+				}
+				snd_ctl_elem_value_set_boolean(control, idx, tmp);
+				break;
+			case SND_CTL_ELEM_TYPE_INTEGER:
+				tmp = get_integer(&ptr,
+						  snd_ctl_elem_info_get_min(info),
+						  snd_ctl_elem_info_get_max(info));
+				snd_ctl_elem_value_set_integer(control, idx, tmp);
+				break;
+			case SND_CTL_ELEM_TYPE_INTEGER64:
+				tmp = get_integer64(&ptr,
+						  snd_ctl_elem_info_get_min64(info),
+						  snd_ctl_elem_info_get_max64(info));
+				snd_ctl_elem_value_set_integer64(control, idx, tmp);
+				break;
+			case SND_CTL_ELEM_TYPE_ENUMERATED:
+				tmp = get_ctl_enum_item_index(handle, info, &ptr);
+				if (tmp < 0)
+					tmp = get_integer(&ptr, 0, snd_ctl_elem_info_get_items(info) - 1);
+				snd_ctl_elem_value_set_enumerated(control, idx, tmp);
+				break;
+			case SND_CTL_ELEM_TYPE_BYTES:
+				tmp = get_integer(&ptr, 0, 255);
+				snd_ctl_elem_value_set_byte(control, idx, tmp);
+				break;
+			default:
+				break;
+			}
+			if (!strchr(argv[1], ','))
+				ptr = argv[1];
+			else if (*ptr == ',')
+				ptr++;
+		}
+		if ((err = snd_ctl_elem_write(handle, control)) < 0) {
+			if (!ignore_error)
+				error("Control %s element write error: %s\n", card, snd_strerror(err));
+			if (!keep_handle) {
+				snd_ctl_close(handle);
+				handle = NULL;
+			}
+			return ignore_error ? 0 : err;
+		}
+	}
+	if (! keep_handle) {
+		snd_ctl_close(handle);
+		handle = NULL;
+	}
+	if (!quiet) {
+		snd_hctl_t *hctl;
+		snd_hctl_elem_t *elem;
+		if ((err = snd_hctl_open(&hctl, card, 0)) < 0) {
+			error("Control %s open error: %s\n", card, snd_strerror(err));
+			return err;
+		}
+		if ((err = snd_hctl_load(hctl)) < 0) {
+			error("Control %s load error: %s\n", card, snd_strerror(err));
+			return err;
+		}
+		elem = snd_hctl_find_elem(hctl, id);
+		if (elem)
+			show_control("  ", elem, LEVEL_BASIC | LEVEL_ID);
+		else
+			printf("Could not find the specified element\n");
+		snd_hctl_close(hctl);
+	}
+	return 0;
+}
+
+typedef struct channel_mask {
+	char *name;
+	unsigned int mask;
+} channel_mask_t;
+static const channel_mask_t chanmask[] = {
+	{"frontleft", 1 << SND_MIXER_SCHN_FRONT_LEFT},
+	{"frontright", 1 << SND_MIXER_SCHN_FRONT_RIGHT},
+	{"frontcenter", 1 << SND_MIXER_SCHN_FRONT_CENTER},
+	{"front", ((1 << SND_MIXER_SCHN_FRONT_LEFT) |
+		   (1 << SND_MIXER_SCHN_FRONT_RIGHT))},
+	{"center", 1 << SND_MIXER_SCHN_FRONT_CENTER},
+	{"rearleft", 1 << SND_MIXER_SCHN_REAR_LEFT},
+	{"rearright", 1 << SND_MIXER_SCHN_REAR_RIGHT},
+	{"rear", ((1 << SND_MIXER_SCHN_REAR_LEFT) |
+		  (1 << SND_MIXER_SCHN_REAR_RIGHT))},
+	{"woofer", 1 << SND_MIXER_SCHN_WOOFER},
+	{NULL, 0}
+};
+
+static unsigned int channels_mask(char **arg, unsigned int def)
+{
+	const channel_mask_t *c;
+
+	for (c = chanmask; c->name; c++) {
+		if (strncasecmp(*arg, c->name, strlen(c->name)) == 0) {
+			while (**arg != '\0' && **arg != ',' && **arg != ' ' && **arg != '\t')
+				(*arg)++;
+			if (**arg == ',' || **arg == ' ' || **arg == '\t')
+				(*arg)++;
+			return c->mask;
+		}
+	}
+	return def;
+}
+
+static unsigned int dir_mask(char **arg, unsigned int def)
+{
+	int findend = 0;
+
+	if (strncasecmp(*arg, "playback", 8) == 0)
+		def = findend = 1;
+	else if (strncasecmp(*arg, "capture", 8) == 0)
+		def = findend = 2;
+	if (findend) {
+		while (**arg != '\0' && **arg != ',' && **arg != ' ' && **arg != '\t')
+			(*arg)++;
+		if (**arg == ',' || **arg == ' ' || **arg == '\t')
+			(*arg)++;
+	}
+	return def;
+}
+
+static int get_enum_item_index(snd_mixer_elem_t *elem, char **ptrp)
+{
+	char *ptr = *ptrp;
+	int items, i, len;
+	char name[40];
+	
+	items = snd_mixer_selem_get_enum_items(elem);
+	if (items <= 0)
+		return -1;
+
+	for (i = 0; i < items; i++) {
+		if (snd_mixer_selem_get_enum_item_name(elem, i, sizeof(name)-1, name) < 0)
+			continue;
+		len = strlen(name);
+		if (! strncmp(name, ptr, len)) {
+			if (! ptr[len] || ptr[len] == ',' || ptr[len] == '\n') {
+				ptr += len;
+				*ptrp = ptr;
+				return i;
+			}
+		}
+	}
+	return -1;
+}
+
+static int sset_enum(snd_mixer_elem_t *elem, unsigned int argc, char **argv)
+{
+	unsigned int idx, chn = 0;
+	int check_flag = ignore_error ? 0 : -1;
+
+	for (idx = 1; idx < argc; idx++) {
+		char *ptr = argv[idx];
+		while (*ptr) {
+			int ival = get_enum_item_index(elem, &ptr);
+			if (ival < 0)
+				return check_flag;
+			if (snd_mixer_selem_set_enum_item(elem, chn, ival) >= 0)
+				check_flag = 1;
+			/* skip separators */
+			while (*ptr == ',' || isspace(*ptr))
+				ptr++;
+		}
+	}
+	return check_flag;
+}
+
+static int sset_channels(snd_mixer_elem_t *elem, unsigned int argc, char **argv)
+{
+	unsigned int channels = ~0U;
+	unsigned int dir = 3, okflag = 3;
+	unsigned int idx;
+	snd_mixer_selem_channel_id_t chn;
+	int check_flag = ignore_error ? 0 : -1;
+
+	for (idx = 1; idx < argc; idx++) {
+		char *ptr = argv[idx], *optr;
+		int multi, firstchn = 1;
+		channels = channels_mask(&ptr, channels);
+		if (*ptr == '\0')
+			continue;
+		dir = dir_mask(&ptr, dir);
+		if (*ptr == '\0')
+			continue;
+		multi = (strchr(ptr, ',') != NULL);
+		optr = ptr;
+		for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
+			char *sptr = NULL;
+			int ival;
+
+			if (!(channels & (1 << chn)))
+				continue;
+
+			if ((dir & 1) && snd_mixer_selem_has_playback_channel(elem, chn)) {
+				sptr = ptr;
+				if (!strncmp(ptr, "mute", 4) && snd_mixer_selem_has_playback_switch(elem)) {
+					snd_mixer_selem_get_playback_switch(elem, chn, &ival);
+					if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "mute", 1, ival)) >= 0)
+						check_flag = 1;
+				} else if (!strncmp(ptr, "off", 3) && snd_mixer_selem_has_playback_switch(elem)) {
+					snd_mixer_selem_get_playback_switch(elem, chn, &ival);
+					if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "off", 1, ival)) >= 0)
+						check_flag = 1;
+				} else if (!strncmp(ptr, "unmute", 6) && snd_mixer_selem_has_playback_switch(elem)) {
+					snd_mixer_selem_get_playback_switch(elem, chn, &ival);
+					if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "unmute", 0, ival)) >= 0)
+						check_flag = 1;
+				} else if (!strncmp(ptr, "on", 2) && snd_mixer_selem_has_playback_switch(elem)) {
+					snd_mixer_selem_get_playback_switch(elem, chn, &ival);
+					if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "on", 0, ival)) >= 0)
+						check_flag = 1;
+				} else if (!strncmp(ptr, "toggle", 6) && snd_mixer_selem_has_playback_switch(elem)) {
+					if (firstchn || !snd_mixer_selem_has_playback_switch_joined(elem)) {
+						snd_mixer_selem_get_playback_switch(elem, chn, &ival);
+						if (snd_mixer_selem_set_playback_switch(elem, chn, (ival ? 1 : 0) ^ 1) >= 0)
+							check_flag = 1;
+					}
+					simple_skip_word(&ptr, "toggle");
+				} else if (isdigit(*ptr) || *ptr == '-' || *ptr == '+') {
+					if (set_volume_simple(elem, chn, &ptr, 0) >= 0)
+						check_flag = 1;
+				} else if (simple_skip_word(&ptr, "cap") || simple_skip_word(&ptr, "rec") ||
+					   simple_skip_word(&ptr, "nocap") || simple_skip_word(&ptr, "norec")) {
+					/* nothing */
+				} else {
+					okflag &= ~1;
+				}
+			}
+			if ((dir & 2) && snd_mixer_selem_has_capture_channel(elem, chn)) {
+				if (sptr != NULL)
+					ptr = sptr;
+				sptr = ptr;
+				if (!strncmp(ptr, "cap", 3) && snd_mixer_selem_has_capture_switch(elem)) {
+					snd_mixer_selem_get_capture_switch(elem, chn, &ival);
+					if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "cap", 0, ival)) >= 0)
+						check_flag = 1;
+				} else if (!strncmp(ptr, "rec", 3) && snd_mixer_selem_has_capture_switch(elem)) {
+					snd_mixer_selem_get_capture_switch(elem, chn, &ival);
+					if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "rec", 0, ival)) >= 0)
+						check_flag = 1;
+				} else if (!strncmp(ptr, "nocap", 5) && snd_mixer_selem_has_capture_switch(elem)) {
+					snd_mixer_selem_get_capture_switch(elem, chn, &ival);
+					if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "nocap", 1, ival)) >= 0)
+						check_flag = 1;
+				} else if (!strncmp(ptr, "norec", 5) && snd_mixer_selem_has_capture_switch(elem)) {
+					snd_mixer_selem_get_capture_switch(elem, chn, &ival);
+					if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "norec", 1, ival)) >= 0)
+						check_flag = 1;
+				} else if (!strncmp(ptr, "toggle", 6) && snd_mixer_selem_has_capture_switch(elem)) {
+					if (firstchn || !snd_mixer_selem_has_capture_switch_joined(elem)) {
+						snd_mixer_selem_get_capture_switch(elem, chn, &ival);
+						if (snd_mixer_selem_set_capture_switch(elem, chn, (ival ? 1 : 0) ^ 1) >= 0)
+							check_flag = 1;
+					}
+					simple_skip_word(&ptr, "toggle");
+				} else if (isdigit(*ptr) || *ptr == '-' || *ptr == '+') {
+					if (set_volume_simple(elem, chn, &ptr, 1) >= 0)
+						check_flag = 1;
+				} else if (simple_skip_word(&ptr, "mute") || simple_skip_word(&ptr, "off") ||
+					   simple_skip_word(&ptr, "unmute") || simple_skip_word(&ptr, "on")) {
+					/* nothing */
+				} else {
+					okflag &= ~2;
+				}
+			}
+			if (okflag == 0) {
+				if (debugflag) {
+					if (dir & 1)
+						error("Unknown playback setup '%s'..", ptr);
+					if (dir & 2)
+						error("Unknown capture setup '%s'..", ptr);
+				}
+				return 0; /* just skip it */
+			}
+			if (!multi)
+				ptr = optr;
+			firstchn = 0;
+		}
+	}
+	return check_flag;
+}
+
+static int sset(unsigned int argc, char *argv[], int roflag, int keep_handle)
+{
+	int err = 0;
+	static snd_mixer_t *handle = NULL;
+	snd_mixer_elem_t *elem;
+	snd_mixer_selem_id_t *sid;
+	snd_mixer_selem_id_alloca(&sid);
+
+	if (argc < 1) {
+		fprintf(stderr, "Specify a scontrol identifier: 'name',index\n");
+		return 1;
+	}
+	if (parse_simple_id(argv[0], sid)) {
+		fprintf(stderr, "Wrong scontrol identifier: %s\n", argv[0]);
+		return 1;
+	}
+	if (!roflag && argc < 2) {
+		fprintf(stderr, "Specify what you want to set...\n");
+		return 1;
+	}
+	if (handle == NULL) {
+		if ((err = snd_mixer_open(&handle, 0)) < 0) {
+			error("Mixer %s open error: %s\n", card, snd_strerror(err));
+			return err;
+		}
+		if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) {
+			error("Mixer attach %s error: %s", card, snd_strerror(err));
+			snd_mixer_close(handle);
+			handle = NULL;
+			return err;
+		}
+		if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
+			error("Mixer register error: %s", snd_strerror(err));
+			snd_mixer_close(handle);
+			handle = NULL;
+			return err;
+		}
+		err = snd_mixer_load(handle);
+		if (err < 0) {
+			error("Mixer %s load error: %s", card, snd_strerror(err));
+			snd_mixer_close(handle);
+			handle = NULL;
+			return err;
+		}
+	}
+	elem = snd_mixer_find_selem(handle, sid);
+	if (!elem) {
+		if (ignore_error)
+			return 0;
+		error("Unable to find simple control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
+		snd_mixer_close(handle);
+		handle = NULL;
+		return -ENOENT;
+	}
+	if (!roflag) {
+		/* enum control */
+		if (snd_mixer_selem_is_enumerated(elem))
+			err = sset_enum(elem, argc, argv);
+		else
+			err = sset_channels(elem, argc, argv);
+
+		if (!err)
+			goto done;
+		if (err < 0) {
+			error("Invalid command!");
+			goto done;
+		}
+	}
+	if (!quiet) {
+		printf("Simple mixer control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
+		show_selem(handle, sid, "  ", 1);
+	}
+ done:
+	if (! keep_handle) {
+		snd_mixer_close(handle);
+		handle = NULL;
+	}
+	return err < 0 ? 1 : 0;
+}
+
+static void events_info(snd_hctl_elem_t *helem)
+{
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_id_alloca(&id);
+	snd_hctl_elem_get_id(helem, id);
+	printf("event info: ");
+	show_control_id(id);
+	printf("\n");
+}
+
+static void events_value(snd_hctl_elem_t *helem)
+{
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_id_alloca(&id);
+	snd_hctl_elem_get_id(helem, id);
+	printf("event value: ");
+	show_control_id(id);
+	printf("\n");
+}
+
+static void events_remove(snd_hctl_elem_t *helem)
+{
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_id_alloca(&id);
+	snd_hctl_elem_get_id(helem, id);
+	printf("event remove: ");
+	show_control_id(id);
+	printf("\n");
+}
+
+static int element_callback(snd_hctl_elem_t *elem, unsigned int mask)
+{
+	if (mask == SND_CTL_EVENT_MASK_REMOVE) {
+		events_remove(elem);
+		return 0;
+	}
+	if (mask & SND_CTL_EVENT_MASK_INFO) 
+		events_info(elem);
+	if (mask & SND_CTL_EVENT_MASK_VALUE) 
+		events_value(elem);
+	return 0;
+}
+
+static void events_add(snd_hctl_elem_t *helem)
+{
+	snd_ctl_elem_id_t *id;
+	snd_ctl_elem_id_alloca(&id);
+	snd_hctl_elem_get_id(helem, id);
+	printf("event add: ");
+	show_control_id(id);
+	printf("\n");
+	snd_hctl_elem_set_callback(helem, element_callback);
+}
+
+static int ctl_callback(snd_hctl_t *ctl, unsigned int mask,
+		 snd_hctl_elem_t *elem)
+{
+	if (mask & SND_CTL_EVENT_MASK_ADD)
+		events_add(elem);
+	return 0;
+}
+
+static int events(int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED)
+{
+	snd_hctl_t *handle;
+	snd_hctl_elem_t *helem;
+	int err;
+
+	if ((err = snd_hctl_open(&handle, card, 0)) < 0) {
+		error("Control %s open error: %s\n", card, snd_strerror(err));
+		return err;
+	}
+	snd_hctl_set_callback(handle, ctl_callback);
+	if ((err = snd_hctl_load(handle)) < 0) {
+		error("Control %s hbuild error: %s\n", card, snd_strerror(err));
+		return err;
+	}
+	for (helem = snd_hctl_first_elem(handle); helem; helem = snd_hctl_elem_next(helem)) {
+		snd_hctl_elem_set_callback(helem, element_callback);
+	}
+	printf("Ready to listen...\n");
+	while (1) {
+		int res = snd_hctl_wait(handle, -1);
+		if (res >= 0) {
+			printf("Poll ok: %i\n", res);
+			res = snd_hctl_handle_events(handle);
+			assert(res > 0);
+		}
+	}
+	snd_hctl_close(handle);
+	return 0;
+}
+
+static void sevents_value(snd_mixer_selem_id_t *sid)
+{
+	printf("event value: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
+}
+
+static void sevents_info(snd_mixer_selem_id_t *sid)
+{
+	printf("event info: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
+}
+
+static void sevents_remove(snd_mixer_selem_id_t *sid)
+{
+	printf("event remove: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
+}
+
+static int melem_event(snd_mixer_elem_t *elem, unsigned int mask)
+{
+	snd_mixer_selem_id_t *sid;
+	snd_mixer_selem_id_alloca(&sid);
+	snd_mixer_selem_get_id(elem, sid);
+	if (mask == SND_CTL_EVENT_MASK_REMOVE) {
+		sevents_remove(sid);
+		return 0;
+	}
+	if (mask & SND_CTL_EVENT_MASK_INFO) 
+		sevents_info(sid);
+	if (mask & SND_CTL_EVENT_MASK_VALUE) 
+		sevents_value(sid);
+	return 0;
+}
+
+static void sevents_add(snd_mixer_elem_t *elem)
+{
+	snd_mixer_selem_id_t *sid;
+	snd_mixer_selem_id_alloca(&sid);
+	snd_mixer_selem_get_id(elem, sid);
+	printf("event add: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));
+	snd_mixer_elem_set_callback(elem, melem_event);
+}
+
+static int mixer_event(snd_mixer_t *mixer, unsigned int mask,
+		snd_mixer_elem_t *elem)
+{
+	if (mask & SND_CTL_EVENT_MASK_ADD)
+		sevents_add(elem);
+	return 0;
+}
+
+static int sevents(int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED)
+{
+	snd_mixer_t *handle;
+	int err;
+
+	if ((err = snd_mixer_open(&handle, 0)) < 0) {
+		error("Mixer %s open error: %s", card, snd_strerror(err));
+		return err;
+	}
+	if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) {
+		error("Mixer attach %s error: %s", card, snd_strerror(err));
+		snd_mixer_close(handle);
+		return err;
+	}
+	if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) {
+		error("Mixer register error: %s", snd_strerror(err));
+		snd_mixer_close(handle);
+		return err;
+	}
+	snd_mixer_set_callback(handle, mixer_event);
+	err = snd_mixer_load(handle);
+	if (err < 0) {
+		error("Mixer %s load error: %s", card, snd_strerror(err));
+		snd_mixer_close(handle);
+		return err;
+	}
+
+	printf("Ready to listen...\n");
+	while (1) {
+		int res;
+		res = snd_mixer_wait(handle, -1);
+		if (res >= 0) {
+			printf("Poll ok: %i\n", res);
+			res = snd_mixer_handle_events(handle);
+			assert(res >= 0);
+		}
+	}
+	snd_mixer_close(handle);
+	return 0;
+}
+
+/*
+ * split a line into tokens
+ * the content in the line buffer is modified
+ */
+static int split_line(char *buf, char **token, int max_token)
+{
+	char *dst;
+	int n, esc, quote;
+
+	for (n = 0; n < max_token; n++) {
+		while (isspace(*buf))
+			buf++;
+		if (! *buf || *buf == '\n')
+			return n;
+		/* skip comments */
+		if (*buf == '#' || *buf == '!')
+			return n;
+		esc = 0;
+		quote = 0;
+		token[n] = buf;
+		for (dst = buf; *buf && *buf != '\n'; buf++) {
+			if (esc)
+				esc = 0;
+			else if (isspace(*buf) && !quote) {
+				buf++;
+				break;
+			} else if (*buf == '\\') {
+				esc = 1;
+				continue;
+			} else if (*buf == '\'' || *buf == '"') {
+				if (! quote) {
+					quote = *buf;
+					continue;
+				} else if (*buf == quote) {
+					quote = 0;
+					continue;
+				}
+			}
+			*dst++ = *buf;
+		}
+		*dst = 0;
+	}
+	return n;
+}
+
+#define MAX_ARGS	32
+
+static int exec_stdin(void)
+{
+	int narg;
+	char buf[256], *args[MAX_ARGS];
+	int err = 0;
+
+	/* quiet = 1; */
+	ignore_error = 1;
+
+	while (fgets(buf, sizeof(buf), stdin)) {
+		narg = split_line(buf, args, MAX_ARGS);
+		if (narg > 0) {
+			if (!strcmp(args[0], "sset") || !strcmp(args[0], "set"))
+				err = sset(narg - 1, args + 1, 0, 1);
+			else if (!strcmp(args[0], "cset"))
+				err = cset(narg - 1, args + 1, 0, 1);
+			if (err < 0)
+				return 1;
+		}
+	}
+	return 0;
+}
+
+
+int main(int argc, char *argv[])
+{
+	int morehelp, level = 0;
+	int read_stdin = 0;
+	static const struct option long_option[] =
+	{
+		{"help", 0, NULL, 'h'},
+		{"card", 1, NULL, 'c'},
+		{"device", 1, NULL, 'D'},
+		{"quiet", 0, NULL, 'q'},
+		{"inactive", 0, NULL, 'i'},
+		{"debug", 0, NULL, 'd'},
+		{"nocheck", 0, NULL, 'n'},
+		{"version", 0, NULL, 'v'},
+		{"abstract", 1, NULL, 'a'},
+		{"stdin", 0, NULL, 's'},
+		{NULL, 0, NULL, 0},
+	};
+
+	morehelp = 0;
+	while (1) {
+		int c;
+
+		if ((c = getopt_long(argc, argv, "hc:D:qidnva:s", long_option, NULL)) < 0)
+			break;
+		switch (c) {
+		case 'h':
+			help();
+			return 0;
+		case 'c':
+			{
+				int i;
+				i = snd_card_get_index(optarg);
+				if (i >= 0 && i < 32)
+					sprintf(card, "hw:%i", i);
+				else {
+					fprintf(stderr, "Invalid card number.\n");
+					morehelp++;
+				}
+			}
+			break;
+		case 'D':
+			strncpy(card, optarg, sizeof(card)-1);
+			card[sizeof(card)-1] = '\0';
+			break;
+		case 'q':
+			quiet = 1;
+			break;
+		case 'i':
+			level |= LEVEL_INACTIVE;
+			break;
+		case 'd':
+			debugflag = 1;
+			break;
+		case 'n':
+			no_check = 1;
+			break;
+		case 'v':
+			printf("amixer version " SND_UTIL_VERSION_STR "\n");
+			return 1;
+		case 'a':
+			smixer_level = 1;
+			memset(&smixer_options, 0, sizeof(smixer_options));
+			smixer_options.ver = 1;
+			if (!strcmp(optarg, "none"))
+				smixer_options.abstract = SND_MIXER_SABSTRACT_NONE;
+			else if (!strcmp(optarg, "basic"))
+				smixer_options.abstract = SND_MIXER_SABSTRACT_BASIC;
+			else {
+				fprintf(stderr, "Select correct abstraction level (none or basic)...\n");
+				morehelp++;
+			}
+			break;
+		case 's':
+			read_stdin = 1;
+			break;
+		default:
+			fprintf(stderr, "Invalid switch or option needs an argument.\n");
+			morehelp++;
+		}
+	}
+	if (morehelp) {
+		help();
+		return 1;
+	}
+	smixer_options.device = card;
+
+	if (read_stdin)
+		return exec_stdin();
+
+	if (argc - optind <= 0) {
+		return selems(LEVEL_BASIC | level) ? 1 : 0;
+	}
+	if (!strcmp(argv[optind], "help")) {
+		return help() ? 1 : 0;
+	} else if (!strcmp(argv[optind], "info")) {
+		return info() ? 1 : 0;
+	} else if (!strcmp(argv[optind], "controls")) {
+		return controls(level) ? 1 : 0;
+	} else if (!strcmp(argv[optind], "contents")) {
+		return controls(LEVEL_BASIC | level) ? 1 : 0;
+	} else if (!strcmp(argv[optind], "scontrols") || !strcmp(argv[optind], "simple")) {
+		return selems(level) ? 1 : 0;
+	} else if (!strcmp(argv[optind], "scontents")) {
+		return selems(LEVEL_BASIC | level) ? 1 : 0;
+	} else if (!strcmp(argv[optind], "sset") || !strcmp(argv[optind], "set")) {
+		return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0;
+	} else if (!strcmp(argv[optind], "sget") || !strcmp(argv[optind], "get")) {
+		return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0;
+	} else if (!strcmp(argv[optind], "cset")) {
+		return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0;
+	} else if (!strcmp(argv[optind], "cget")) {
+		return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0;
+	} else if (!strcmp(argv[optind], "events")) {
+		return events(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL);
+	} else if (!strcmp(argv[optind], "sevents")) {
+		return sevents(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL);
+	} else {
+		fprintf(stderr, "amixer: Unknown command '%s'...\n", argv[optind]);
+	}
+
+	return 0;
+}
diff --git a/amixer/amixer.h b/amixer/amixer.h
new file mode 100644
index 0000000..3588f04
--- /dev/null
+++ b/amixer/amixer.h
@@ -0,0 +1,21 @@
+/*
+ *   ALSA command line mixer utility
+ *   Copyright (c) 1999 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 "../include/version.h"
diff --git a/amixer/go b/amixer/go
new file mode 100755
index 0000000..071627f
--- /dev/null
+++ b/amixer/go
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+#GDB="gdb --args"
+GDB=""
+
+ALSA_MIXER_SIMPLE_MODULES="$HOME/hg/alsa-lib/modules/mixer/simple/.libs" \
+ALSA_MIXER_SIMPLE="$HOME/hg/alsa-lib/src/conf/smixer.conf" \
+ALSA_MIXER_SIMPLE_MPYTHON="$HOME/hg/alsa-lib/modules/mixer/simple/python/main.py" \
+LD_PRELOAD="$HOME/hg/alsa-lib/src/.libs/libasound.so" \
+$GDB ./amixer "$@"
diff --git a/aplay/Makefile.am b/aplay/Makefile.am
new file mode 100644
index 0000000..92323d1
--- /dev/null
+++ b/aplay/Makefile.am
@@ -0,0 +1,31 @@
+LIBRT = @LIBRT@
+
+INCLUDES = -I$(top_srcdir)/include
+LDADD = $(LIBINTL) $(LIBRT)
+
+# debug flags
+#LDFLAGS = -static
+#LDADD += -ldl
+
+bin_PROGRAMS = aplay
+man_MANS = aplay.1 arecord.1
+noinst_HEADERS = formats.h
+
+EXTRA_DIST = aplay.1 arecord.1
+EXTRA_CLEAN = arecord
+
+arecord: aplay
+	rm -f arecord
+	$(LN_S) $< $@
+
+arecord.1: aplay.1
+	rm -f arecord.1
+	$(LN_S) $< $@
+
+install-exec-hook: arecord
+	rm -f $(DESTDIR)$(bindir)/arecord
+	(cd $(DESTDIR)$(bindir) && $(LN_S) aplay arecord)
+
+install-data-hook:
+	rm -f $(DESTDIR)$(mandir)/man1/arecord.1
+	(cd $(DESTDIR)$(mandir)/man1 && $(LN_S) aplay.1 arecord.1)
diff --git a/aplay/aplay.1 b/aplay/aplay.1
new file mode 100644
index 0000000..8ccf556
--- /dev/null
+++ b/aplay/aplay.1
@@ -0,0 +1,241 @@
+.TH APLAY 1 "1 January 2010"
+.SH NAME
+arecord, aplay \- command-line sound recorder and player for ALSA 
+soundcard driver
+.SH SYNOPSIS
+\fBarecord\fP [\fIflags\fP] [filename]
+.br
+\fBaplay\fP [\fIflags\fP] [filename [filename]] ...
+
+.SH DESCRIPTION
+\fBarecord\fP is a command-line soundfile recorder for the ALSA soundcard
+driver. It supports several file formats and multiple soundcards with
+multiple devices. If recording with interleaved mode samples the file is
+automatically split before the 2GB filesize.
+
+\fBaplay\fP is much the same, only it plays instead of recording. For
+supported soundfile formats, the sampling rate, bit depth, and so
+forth can be automatically determined from the soundfile header.
+
+If filename is not specified, the standard output or input is used. The \fBaplay\fP utility accepts multiple filenames.
+
+.SH OPTIONS
+.TP
+\fI\-h, \-\-help\fP
+Help: show syntax.
+.TP
+\fI\-\-version\fP
+Print current version.
+.TP
+\fI\-l, \-\-list\-devices\fP
+List all soundcards and digital audio devices
+.TP
+\fI\-L, \-\-list\-pcms\fP
+List all PCMs defined
+.TP
+\fI\-D, \-\-device=NAME\fP
+Select PCM by name
+.TP
+\fI\-q \-\-quiet\fP
+Quiet mode. Suppress messages (not sound :))
+.TP
+\fI\-t, \-\-file\-type TYPE\fP
+File type (voc, wav, raw or au).
+If this parameter is omitted the WAVE format is used.
+.TP
+\fI\-c, \-\-channels=#\fP
+The number of channels.
+The default is one channel.
+Valid values are 1 through 32.
+.TP
+\fI\-f \-\-format=FORMAT\fP
+Sample format
+.br
+Recognized sample formats are: S8 U8 S16_LE S16_BE U16_LE U16_BE S24_LE
+S24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE FLOAT_LE FLOAT_BE
+FLOAT64_LE FLOAT64_BE IEC958_SUBFRAME_LE IEC958_SUBFRAME_BE MU_LAW
+A_LAW IMA_ADPCM MPEG GSM SPECIAL S24_3LE S24_3BE U24_3LE U24_3BE S20_3LE
+S20_3BE U20_3LE U20_3BE S18_3LE S18_3BE U18_3LE
+.br
+Some of these may not be available on selected hardware
+.br
+The available format shortcuts are:
+.nf
+\-f cd (16 bit little endian, 44100, stereo) [\-f S16_LE \-c2 \-r44100]
+\-f cdr (16 bit big endian, 44100, stereo) [\-f S16_BE \-c2 \-f44100]
+\-f dat (16 bit little endian, 48000, stereo) [\-f S16_LE \-c2 \-r48000]
+.fi
+If no format is given U8 is used.
+.TP
+\fI\-r, \-\-rate=#<Hz>\fP
+Sampling rate in Hertz. The default rate is 8000 Hertz.
+If the value specified is less than 300, it is taken as the rate in kilohertz.
+Valid values are 2000 through 192000 Hertz.
+.TP
+\fI\-d, \-\-duration=#\fP
+Interrupt after # seconds.
+A value of zero means infinity.
+The default is zero, so if this option is omitted then the arecord process will run until it is killed.
+.TP
+\fI\-s, \-\-sleep\-min=#\fP
+Min ticks to sleep. The default is not to sleep.
+.TP
+\fI\-M, \-\-mmap\fP            
+Use memory-mapped (mmap) I/O mode for the audio stream.
+If this option is not set, the read/write I/O mode will be used.
+.TP
+\fI\-N, \-\-nonblock\fP          
+Open the audio device in non-blocking mode. If the device is busy the program will exit immediately.
+If this option is not set the program will block until the audio device is available again.
+.TP
+\fI\-F, \-\-period\-time=#\fP     
+Distance between interrupts is # microseconds.
+If no period time and no period size is given then a quarter of the buffer time is set.
+.TP
+\fI\-B, \-\-buffer\-time=#\fP     
+Buffer duration is # microseconds
+If no buffer time and no buffer size is given then the maximal allowed buffer time but not more than 500ms is set.
+.TP
+\fI\-\-period\-size=#\fP     
+Distance between interrupts is # frames
+If no period size and no period time is given then a quarter of the buffer size is set.
+.TP
+\fI\-\-buffer\-size=#\fP     
+Buffer duration is # frames
+If no buffer time and no buffer size is given then the maximal allowed buffer time but not more than 500ms is set.
+.TP
+\fI\-A, \-\-avail\-min=#\fP       
+Min available space for wakeup is # microseconds
+.TP
+\fI\-R, \-\-start\-delay=#\fP     
+Delay for automatic PCM start is # microseconds 
+(relative to buffer size if <= 0)
+.TP
+\fI\-T, \-\-stop\-delay=#\fP      
+Delay for automatic PCM stop is # microseconds from xrun
+.TP
+\fI\-v, \-\-verbose\fP           
+Show PCM structure and setup.
+This option is accumulative.  The VU meter is displayed when this
+is given twice or three times.
+.TP
+\fI\-V, \-\-vumeter=TYPE\fP
+Specifies the VU-meter type, either \fIstereo\fP or \fImono\fP.
+The stereo VU-meter is available only for 2-channel stereo samples
+with interleaved format.
+.TP
+\fI\-I, \-\-separate\-channels\fP 
+One file for each channel.  This option disables max\-file\-time
+and use\-strftime, and ignores SIGUSR1.  The stereo VU meter is
+not available with separate channels.
+.TP
+\fI\-P\fP
+Playback.  This is the default if the program is invoked
+by typing aplay.
+.TP
+\fI\-C\fP
+Record.  This is the default if the program is invoked
+by typing arecord.
+.TP
+\fI\-i, \-\-interactive\fP
+Allow interactive operation via stdin.
+Currently only pause/resume via space or enter key is implemented.
+.TP
+\fI\-\-disable\-resample\fP
+Disable automatic rate resample.
+.TP
+\fI\-\-disable\-channels\fP
+Disable automatic channel conversions.
+.TP
+\fI\-\-disable\-format\fP
+Disable automatic format conversions.
+.TP
+\fI\-\-disable\-softvol\fP
+Disable software volume control (softvol).
+.TP
+\fI\-\-test\-position\fP
+Test ring buffer position.
+.TP
+\fI\-\-test\-coef=<coef>\fP
+Test coefficient for ring buffer position; default is 8.
+Expression for validation is: coef * (buffer_size / 2).
+Minimum value is 1.
+.TP
+\fI\-\-test\-nowait\fP
+Do not wait for the ring buffer--eats the whole CPU.
+.TP
+\fI\-\-max\-file\-time\fP
+While recording, when the output file has been accumulating
+sound for this long,
+close it and open a new output file.  Default is the maximum
+size supported by the file format: 2 GiB for WAV files.
+This option has no effect if  \-\-separate\-channels is
+specified.
+.TP
+\fI\-\-process\-id\-file <file name>\fP
+aplay writes its process ID here, so other programs can
+send signals to it.
+.TP
+\fI\-\-use\-strftime\fP
+When recording, interpret %-codes in the file name parameter using
+the strftime facility whenever the output file is opened.  The
+important strftime codes are: %Y is the year, %m month, %d day of
+the month, %H hour, %M minute and %S second.  In addition, %v is
+the file number, starting at 1.  When this option is specified,
+intermediate directories for the output file are created automatically.
+This option has no effect if \-\-separate\-channels is specified.
+
+.SH SIGNALS
+When recording, SIGINT, SIGTERM and SIGABRT will close the output 
+file and exit.  SIGUSR1 will close the output file, open a new one,
+and continue recording.  However, SIGUSR1 does not work with
+\-\-separate\-channels.
+
+.SH EXAMPLES
+
+.TP
+\fBaplay \-c 1 \-t raw \-r 22050 \-f mu_law foobar\fR
+will play the raw file "foobar" as a
+22050-Hz, mono, 8-bit, Mu-Law .au file. 
+
+.TP
+\fBarecord \-d 10 \-f cd \-t wav \-D copy foobar.wav\fP
+will record foobar.wav as a 10-second, CD-quality wave file, using the
+PCM "copy" (which might be defined in the user's .asoundrc file as:
+.nf
+pcm.copy {
+  type plug
+  slave {
+    pcm hw
+  }
+  route_policy copy
+}
+.fi
+
+.TP
+\fBarecord \-t wav \-max-file_time 30 mon.wav\fP
+Record from the default audio source in monaural, 8,000 samples
+per second, 8 bits per sample.  Start a new file every
+30 seconds.  File names are mon-nn.wav, where nn increases
+from 01.  The file after mon-99.wav is mon-100.wav.
+
+.TP
+\fBarecord \-f cd \-t wav \-max-file-time 3600 --use-strftime %Y/%m/%d/listen-%H-%M-%v.wav\fP
+Record in stereo from the default audio source.  Create a new file
+every hour.  The files are placed in directories based on their start dates
+and have names which include their start times and file numbers.
+
+.SH SEE ALSO
+\fB
+alsamixer(1),
+amixer(1)
+\fP
+
+.SH BUGS 
+Note that .aiff files are not currently supported.
+
+.SH AUTHOR
+\fBarecord\fP and \fBaplay\fP are by Jaroslav Kysela <perex@perex.cz>
+This document is by Paul Winkler <zarmzarm@erols.com>.
+Updated for Alsa 0.9 by James Tappin <james@xena.uklinux.net>
+
diff --git a/aplay/aplay.c b/aplay/aplay.c
new file mode 100644
index 0000000..73a9544
--- /dev/null
+++ b/aplay/aplay.c
@@ -0,0 +1,2991 @@
+/*
+ *  aplay.c - plays and records
+ *
+ *      CREATIVE LABS CHANNEL-files
+ *      Microsoft WAVE-files
+ *      SPARC AUDIO .AU-files
+ *      Raw Data
+ *
+ *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *  Based on vplay program by Michael Beck
+ *
+ *
+ *   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
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+#include <locale.h>
+#include <alsa/asoundlib.h>
+#include <assert.h>
+#include <termios.h>
+#include <sys/poll.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <endian.h>
+#include "aconfig.h"
+#include "gettext.h"
+#include "formats.h"
+#include "version.h"
+
+#ifndef LLONG_MAX
+#define LLONG_MAX    9223372036854775807LL
+#endif
+
+#ifndef le16toh
+#include <asm/byteorder.h>
+#define le16toh(x) __le16_to_cpu(x)
+#define be16toh(x) __be16_to_cpu(x)
+#define le32toh(x) __le32_to_cpu(x)
+#define be32toh(x) __be32_to_cpu(x)
+#endif
+
+#define DEFAULT_FORMAT		SND_PCM_FORMAT_U8
+#define DEFAULT_SPEED 		8000
+
+#define FORMAT_DEFAULT		-1
+#define FORMAT_RAW		0
+#define FORMAT_VOC		1
+#define FORMAT_WAVE		2
+#define FORMAT_AU		3
+
+/* global data */
+
+static snd_pcm_sframes_t (*readi_func)(snd_pcm_t *handle, void *buffer, snd_pcm_uframes_t size);
+static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size);
+static snd_pcm_sframes_t (*readn_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size);
+static snd_pcm_sframes_t (*writen_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size);
+
+enum {
+	VUMETER_NONE,
+	VUMETER_MONO,
+	VUMETER_STEREO
+};
+
+static char *command;
+static snd_pcm_t *handle;
+static struct {
+	snd_pcm_format_t format;
+	unsigned int channels;
+	unsigned int rate;
+} hwparams, rhwparams;
+static int timelimit = 0;
+static int quiet_mode = 0;
+static int file_type = FORMAT_DEFAULT;
+static int open_mode = 0;
+static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
+static int mmap_flag = 0;
+static int interleaved = 1;
+static int nonblock = 0;
+static u_char *audiobuf = NULL;
+static snd_pcm_uframes_t chunk_size = 0;
+static unsigned period_time = 0;
+static unsigned buffer_time = 0;
+static snd_pcm_uframes_t period_frames = 0;
+static snd_pcm_uframes_t buffer_frames = 0;
+static int avail_min = -1;
+static int start_delay = 0;
+static int stop_delay = 0;
+static int monotonic = 0;
+static int interactive = 0;
+static int can_pause = 0;
+static int verbose = 0;
+static int vumeter = VUMETER_NONE;
+static int buffer_pos = 0;
+static size_t bits_per_sample, bits_per_frame;
+static size_t chunk_bytes;
+static int test_position = 0;
+static int test_coef = 8;
+static int test_nowait = 0;
+static snd_output_t *log;
+static long long max_file_size = 0;
+static int max_file_time = 0;
+static int use_strftime = 0;
+volatile static int recycle_capture_file = 0;
+static long term_c_lflag = -1;
+
+static int fd = -1;
+static off64_t pbrec_count = LLONG_MAX, fdcount;
+static int vocmajor, vocminor;
+
+static char *pidfile_name = NULL;
+FILE *pidf = NULL;
+static int pidfile_written = 0;
+
+/* needed prototypes */
+
+static void done_stdin(void);
+
+static void playback(char *filename);
+static void capture(char *filename);
+static void playbackv(char **filenames, unsigned int count);
+static void capturev(char **filenames, unsigned int count);
+
+static void begin_voc(int fd, size_t count);
+static void end_voc(int fd);
+static void begin_wave(int fd, size_t count);
+static void end_wave(int fd);
+static void begin_au(int fd, size_t count);
+static void end_au(int fd);
+
+static const struct fmt_capture {
+	void (*start) (int fd, size_t count);
+	void (*end) (int fd);
+	char *what;
+	long long max_filesize;
+} fmt_rec_table[] = {
+	{	NULL,		NULL,		N_("raw data"),		LLONG_MAX },
+	{	begin_voc,	end_voc,	N_("VOC"),		16000000LL },
+	/* FIXME: can WAV handle exactly 2GB or less than it? */
+	{	begin_wave,	end_wave,	N_("WAVE"),		2147483648LL },
+	{	begin_au,	end_au,		N_("Sparc Audio"),	LLONG_MAX }
+};
+
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define error(...) do {\
+	fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+	fprintf(stderr, __VA_ARGS__); \
+	putc('\n', stderr); \
+} while (0)
+#else
+#define error(args...) do {\
+	fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+	fprintf(stderr, ##args); \
+	putc('\n', stderr); \
+} while (0)
+#endif	
+
+static void usage(char *command)
+{
+	snd_pcm_format_t k;
+	printf(
+_("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"
+"-i, --interactive       allow interactive operation from stdin\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=#       test coefficient 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")
+		, command);
+	printf(_("Recognized 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(_("\nSome of these may not be available on selected hardware\n"));
+	printf(_("The availabled format shortcuts are:\n"));
+	printf(_("-f cd (16 bit little endian, 44100, stereo)\n"));
+	printf(_("-f cdr (16 bit big endian, 44100, stereo)\n"));
+	printf(_("-f dat (16 bit little endian, 48000, stereo)\n"));
+}
+
+static void device_list(void)
+{
+	snd_ctl_t *handle;
+	int card, err, dev, idx;
+	snd_ctl_card_info_t *info;
+	snd_pcm_info_t *pcminfo;
+	snd_ctl_card_info_alloca(&info);
+	snd_pcm_info_alloca(&pcminfo);
+
+	card = -1;
+	if (snd_card_next(&card) < 0 || card < 0) {
+		error(_("no soundcards found..."));
+		return;
+	}
+	printf(_("**** List of %s Hardware Devices ****\n"),
+	       snd_pcm_stream_name(stream));
+	while (card >= 0) {
+		char name[32];
+		sprintf(name, "hw:%d", card);
+		if ((err = snd_ctl_open(&handle, name, 0)) < 0) {
+			error("control open (%i): %s", card, snd_strerror(err));
+			goto next_card;
+		}
+		if ((err = snd_ctl_card_info(handle, info)) < 0) {
+			error("control hardware info (%i): %s", card, snd_strerror(err));
+			snd_ctl_close(handle);
+			goto next_card;
+		}
+		dev = -1;
+		while (1) {
+			unsigned int count;
+			if (snd_ctl_pcm_next_device(handle, &dev)<0)
+				error("snd_ctl_pcm_next_device");
+			if (dev < 0)
+				break;
+			snd_pcm_info_set_device(pcminfo, dev);
+			snd_pcm_info_set_subdevice(pcminfo, 0);
+			snd_pcm_info_set_stream(pcminfo, stream);
+			if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
+				if (err != -ENOENT)
+					error("control digital audio info (%i): %s", card, snd_strerror(err));
+				continue;
+			}
+			printf(_("card %i: %s [%s], device %i: %s [%s]\n"),
+				card, snd_ctl_card_info_get_id(info), snd_ctl_card_info_get_name(info),
+				dev,
+				snd_pcm_info_get_id(pcminfo),
+				snd_pcm_info_get_name(pcminfo));
+			count = snd_pcm_info_get_subdevices_count(pcminfo);
+			printf( _("  Subdevices: %i/%i\n"),
+				snd_pcm_info_get_subdevices_avail(pcminfo), count);
+			for (idx = 0; idx < (int)count; idx++) {
+				snd_pcm_info_set_subdevice(pcminfo, idx);
+				if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
+					error("control digital audio playback info (%i): %s", card, snd_strerror(err));
+				} else {
+					printf(_("  Subdevice #%i: %s\n"),
+						idx, snd_pcm_info_get_subdevice_name(pcminfo));
+				}
+			}
+		}
+		snd_ctl_close(handle);
+	next_card:
+		if (snd_card_next(&card) < 0) {
+			error("snd_card_next");
+			break;
+		}
+	}
+}
+
+static void pcm_list(void)
+{
+	void **hints, **n;
+	char *name, *descr, *descr1, *io;
+	const char *filter;
+
+	if (snd_device_name_hint(-1, "pcm", &hints) < 0)
+		return;
+	n = hints;
+	filter = stream == SND_PCM_STREAM_CAPTURE ? "Input" : "Output";
+	while (*n != NULL) {
+		name = snd_device_name_get_hint(*n, "NAME");
+		descr = snd_device_name_get_hint(*n, "DESC");
+		io = snd_device_name_get_hint(*n, "IOID");
+		if (io != NULL && strcmp(io, filter) != 0)
+			goto __end;
+		printf("%s\n", name);
+		if ((descr1 = descr) != NULL) {
+			printf("    ");
+			while (*descr1) {
+				if (*descr1 == '\n')
+					printf("\n    ");
+				else
+					putchar(*descr1);
+				descr1++;
+			}
+			putchar('\n');
+		}
+	      __end:
+	      	if (name != NULL)
+	      		free(name);
+		if (descr != NULL)
+			free(descr);
+		if (io != NULL)
+			free(io);
+		n++;
+	}
+	snd_device_name_free_hint(hints);
+}
+
+static void version(void)
+{
+	printf("%s: version " SND_UTIL_VERSION_STR " by Jaroslav Kysela <perex@perex.cz>\n", command);
+}
+
+/*
+ *	Subroutine to clean up before exit.
+ */
+static void prg_exit(int code) 
+{
+	done_stdin();
+	if (handle)
+		snd_pcm_close(handle);
+	if (pidfile_written)
+		remove (pidfile_name);
+	exit(code);
+}
+
+static void signal_handler(int sig)
+{
+	static int in_aborting;
+
+	if (in_aborting)
+		return;
+
+	in_aborting = 1;
+	if (verbose==2)
+		putchar('\n');
+	if (!quiet_mode)
+		fprintf(stderr, _("Aborted by signal %s...\n"), strsignal(sig));
+	if (stream == SND_PCM_STREAM_CAPTURE) {
+		if (fmt_rec_table[file_type].end) {
+			fmt_rec_table[file_type].end(fd);
+			fd = -1;
+		}
+		stream = -1;
+	}
+	if (fd > 1) {
+		close(fd);
+		fd = -1;
+	}
+	if (handle && sig != SIGABRT) {
+		snd_pcm_close(handle);
+		handle = NULL;
+	}
+	prg_exit(EXIT_FAILURE);
+}
+
+/* call on SIGUSR1 signal. */
+static void signal_handler_recycle (int sig)
+{
+	/* flag the capture loop to start a new output file */
+	recycle_capture_file = 1;
+}
+
+enum {
+	OPT_VERSION = 1,
+	OPT_PERIOD_SIZE,
+	OPT_BUFFER_SIZE,
+	OPT_DISABLE_RESAMPLE,
+	OPT_DISABLE_CHANNELS,
+	OPT_DISABLE_FORMAT,
+	OPT_DISABLE_SOFTVOL,
+	OPT_TEST_POSITION,
+	OPT_TEST_COEF,
+	OPT_TEST_NOWAIT,
+	OPT_MAX_FILE_TIME,
+	OPT_PROCESS_ID_FILE,
+	OPT_USE_STRFTIME
+};
+
+int main(int argc, char *argv[])
+{
+	int option_index;
+	static const char short_options[] = "hnlLD:qt:c:f:r:d:MNF:A:R:T:B:vV:IPCi";
+	static const struct option long_options[] = {
+		{"help", 0, 0, 'h'},
+		{"version", 0, 0, OPT_VERSION},
+		{"list-devnames", 0, 0, 'n'},
+		{"list-devices", 0, 0, 'l'},
+		{"list-pcms", 0, 0, 'L'},
+		{"device", 1, 0, 'D'},
+		{"quiet", 0, 0, 'q'},
+		{"file-type", 1, 0, 't'},
+		{"channels", 1, 0, 'c'},
+		{"format", 1, 0, 'f'},
+		{"rate", 1, 0, 'r'},
+		{"duration", 1, 0 ,'d'},
+		{"mmap", 0, 0, 'M'},
+		{"nonblock", 0, 0, 'N'},
+		{"period-time", 1, 0, 'F'},
+		{"period-size", 1, 0, OPT_PERIOD_SIZE},
+		{"avail-min", 1, 0, 'A'},
+		{"start-delay", 1, 0, 'R'},
+		{"stop-delay", 1, 0, 'T'},
+		{"buffer-time", 1, 0, 'B'},
+		{"buffer-size", 1, 0, OPT_BUFFER_SIZE},
+		{"verbose", 0, 0, 'v'},
+		{"vumeter", 1, 0, 'V'},
+		{"separate-channels", 0, 0, 'I'},
+		{"playback", 0, 0, 'P'},
+		{"capture", 0, 0, 'C'},
+		{"disable-resample", 0, 0, OPT_DISABLE_RESAMPLE},
+		{"disable-channels", 0, 0, OPT_DISABLE_CHANNELS},
+		{"disable-format", 0, 0, OPT_DISABLE_FORMAT},
+		{"disable-softvol", 0, 0, OPT_DISABLE_SOFTVOL},
+		{"test-position", 0, 0, OPT_TEST_POSITION},
+		{"test-coef", 1, 0, OPT_TEST_COEF},
+		{"test-nowait", 0, 0, OPT_TEST_NOWAIT},
+		{"max-file-time", 1, 0, OPT_MAX_FILE_TIME},
+		{"process-id-file", 1, 0, OPT_PROCESS_ID_FILE},
+		{"use-strftime", 0, 0, OPT_USE_STRFTIME},
+		{"interactive", 0, 0, 'i'},
+		{0, 0, 0, 0}
+	};
+	char *pcm_name = "default";
+	int tmp, err, c;
+	int do_device_list = 0, do_pcm_list = 0;
+	snd_pcm_info_t *info;
+
+#ifdef ENABLE_NLS
+	setlocale(LC_ALL, "");
+	textdomain(PACKAGE);
+#endif
+
+	snd_pcm_info_alloca(&info);
+
+	err = snd_output_stdio_attach(&log, stderr, 0);
+	assert(err >= 0);
+
+	command = argv[0];
+	file_type = FORMAT_DEFAULT;
+	if (strstr(argv[0], "arecord")) {
+		stream = SND_PCM_STREAM_CAPTURE;
+		file_type = FORMAT_WAVE;
+		command = "arecord";
+		start_delay = 1;
+	} else if (strstr(argv[0], "aplay")) {
+		stream = SND_PCM_STREAM_PLAYBACK;
+		command = "aplay";
+	} else {
+		error(_("command should be named either arecord or aplay"));
+		return 1;
+	}
+
+	chunk_size = -1;
+	rhwparams.format = DEFAULT_FORMAT;
+	rhwparams.rate = DEFAULT_SPEED;
+	rhwparams.channels = 1;
+
+	while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) {
+		switch (c) {
+		case 'h':
+			usage(command);
+			return 0;
+		case OPT_VERSION:
+			version();
+			return 0;
+		case 'l':
+			do_device_list = 1;
+			break;
+		case 'L':
+			do_pcm_list = 1;
+			break;
+		case 'D':
+			pcm_name = optarg;
+			break;
+		case 'q':
+			quiet_mode = 1;
+			break;
+		case 't':
+			if (strcasecmp(optarg, "raw") == 0)
+				file_type = FORMAT_RAW;
+			else if (strcasecmp(optarg, "voc") == 0)
+				file_type = FORMAT_VOC;
+			else if (strcasecmp(optarg, "wav") == 0)
+				file_type = FORMAT_WAVE;
+			else if (strcasecmp(optarg, "au") == 0 || strcasecmp(optarg, "sparc") == 0)
+				file_type = FORMAT_AU;
+			else {
+				error(_("unrecognized file format %s"), optarg);
+				return 1;
+			}
+			break;
+		case 'c':
+			rhwparams.channels = strtol(optarg, NULL, 0);
+			if (rhwparams.channels < 1 || rhwparams.channels > 256) {
+				error(_("value %i for channels is invalid"), rhwparams.channels);
+				return 1;
+			}
+			break;
+		case 'f':
+			if (strcasecmp(optarg, "cd") == 0 || strcasecmp(optarg, "cdr") == 0) {
+				if (strcasecmp(optarg, "cdr") == 0)
+					rhwparams.format = SND_PCM_FORMAT_S16_BE;
+				else
+					rhwparams.format = file_type == FORMAT_AU ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE;
+				rhwparams.rate = 44100;
+				rhwparams.channels = 2;
+			} else if (strcasecmp(optarg, "dat") == 0) {
+				rhwparams.format = file_type == FORMAT_AU ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE;
+				rhwparams.rate = 48000;
+				rhwparams.channels = 2;
+			} else {
+				rhwparams.format = snd_pcm_format_value(optarg);
+				if (rhwparams.format == SND_PCM_FORMAT_UNKNOWN) {
+					error(_("wrong extended format '%s'"), optarg);
+					prg_exit(EXIT_FAILURE);
+				}
+			}
+			break;
+		case 'r':
+			tmp = strtol(optarg, NULL, 0);
+			if (tmp < 300)
+				tmp *= 1000;
+			rhwparams.rate = tmp;
+			if (tmp < 2000 || tmp > 192000) {
+				error(_("bad speed value %i"), tmp);
+				return 1;
+			}
+			break;
+		case 'd':
+			timelimit = strtol(optarg, NULL, 0);
+			break;
+		case 'N':
+			nonblock = 1;
+			open_mode |= SND_PCM_NONBLOCK;
+			break;
+		case 'F':
+			period_time = strtol(optarg, NULL, 0);
+			break;
+		case 'B':
+			buffer_time = strtol(optarg, NULL, 0);
+			break;
+		case OPT_PERIOD_SIZE:
+			period_frames = strtol(optarg, NULL, 0);
+			break;
+		case OPT_BUFFER_SIZE:
+			buffer_frames = strtol(optarg, NULL, 0);
+			break;
+		case 'A':
+			avail_min = strtol(optarg, NULL, 0);
+			break;
+		case 'R':
+			start_delay = strtol(optarg, NULL, 0);
+			break;
+		case 'T':
+			stop_delay = strtol(optarg, NULL, 0);
+			break;
+		case 'v':
+			verbose++;
+			if (verbose > 1 && !vumeter)
+				vumeter = VUMETER_MONO;
+			break;
+		case 'V':
+			if (*optarg == 's')
+				vumeter = VUMETER_STEREO;
+			else if (*optarg == 'm')
+				vumeter = VUMETER_MONO;
+			else
+				vumeter = VUMETER_NONE;
+			break;
+		case 'M':
+			mmap_flag = 1;
+			break;
+		case 'I':
+			interleaved = 0;
+			break;
+		case 'P':
+			stream = SND_PCM_STREAM_PLAYBACK;
+			command = "aplay";
+			break;
+		case 'C':
+			stream = SND_PCM_STREAM_CAPTURE;
+			command = "arecord";
+			start_delay = 1;
+			if (file_type == FORMAT_DEFAULT)
+				file_type = FORMAT_WAVE;
+			break;
+		case 'i':
+			interactive = 1;
+			break;
+		case OPT_DISABLE_RESAMPLE:
+			open_mode |= SND_PCM_NO_AUTO_RESAMPLE;
+			break;
+		case OPT_DISABLE_CHANNELS:
+			open_mode |= SND_PCM_NO_AUTO_CHANNELS;
+			break;
+		case OPT_DISABLE_FORMAT:
+			open_mode |= SND_PCM_NO_AUTO_FORMAT;
+			break;
+		case OPT_DISABLE_SOFTVOL:
+			open_mode |= SND_PCM_NO_SOFTVOL;
+			break;
+		case OPT_TEST_POSITION:
+			test_position = 1;
+			break;
+		case OPT_TEST_COEF:
+			test_coef = strtol(optarg, NULL, 0);
+			if (test_coef < 1)
+				test_coef = 1;
+			break;
+		case OPT_TEST_NOWAIT:
+			test_nowait = 1;
+			break;
+		case OPT_MAX_FILE_TIME:
+			max_file_time = strtol(optarg, NULL, 0);
+			break;
+		case OPT_PROCESS_ID_FILE:
+			pidfile_name = optarg;
+			break;
+		case OPT_USE_STRFTIME:
+			use_strftime = 1;
+			break;
+		default:
+			fprintf(stderr, _("Try `%s --help' for more information.\n"), command);
+			return 1;
+		}
+	}
+
+	if (do_device_list) {
+		if (do_pcm_list) pcm_list();
+		device_list();
+		goto __end;
+	} else if (do_pcm_list) {
+		pcm_list();
+		goto __end;
+	}
+
+	err = snd_pcm_open(&handle, pcm_name, stream, open_mode);
+	if (err < 0) {
+		error(_("audio open error: %s"), snd_strerror(err));
+		return 1;
+	}
+
+	if ((err = snd_pcm_info(handle, info)) < 0) {
+		error(_("info error: %s"), snd_strerror(err));
+		return 1;
+	}
+
+	if (nonblock) {
+		err = snd_pcm_nonblock(handle, 1);
+		if (err < 0) {
+			error(_("nonblock setting error: %s"), snd_strerror(err));
+			return 1;
+		}
+	}
+
+	chunk_size = 1024;
+	hwparams = rhwparams;
+
+	audiobuf = (u_char *)malloc(1024);
+	if (audiobuf == NULL) {
+		error(_("not enough memory"));
+		return 1;
+	}
+
+	if (mmap_flag) {
+		writei_func = snd_pcm_mmap_writei;
+		readi_func = snd_pcm_mmap_readi;
+		writen_func = snd_pcm_mmap_writen;
+		readn_func = snd_pcm_mmap_readn;
+	} else {
+		writei_func = snd_pcm_writei;
+		readi_func = snd_pcm_readi;
+		writen_func = snd_pcm_writen;
+		readn_func = snd_pcm_readn;
+	}
+
+	if (pidfile_name) {
+		errno = 0;
+		pidf = fopen (pidfile_name, "w");
+		if (pidf) {
+			(void)fprintf (pidf, "%d\n", getpid());
+			fclose(pidf);
+			pidfile_written = 1;
+		} else {
+			error(_("Cannot create process ID file %s: %s"), 
+				pidfile_name, strerror (errno));
+			return 1;
+		}
+	}
+
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+	signal(SIGABRT, signal_handler);
+	signal(SIGUSR1, signal_handler_recycle);
+	if (interleaved) {
+		if (optind > argc - 1) {
+			if (stream == SND_PCM_STREAM_PLAYBACK)
+				playback(NULL);
+			else
+				capture(NULL);
+		} else {
+			while (optind <= argc - 1) {
+				if (stream == SND_PCM_STREAM_PLAYBACK)
+					playback(argv[optind++]);
+				else
+					capture(argv[optind++]);
+			}
+		}
+	} else {
+		if (stream == SND_PCM_STREAM_PLAYBACK)
+			playbackv(&argv[optind], argc - optind);
+		else
+			capturev(&argv[optind], argc - optind);
+	}
+	if (verbose==2)
+		putchar('\n');
+	snd_pcm_close(handle);
+	handle = NULL;
+	free(audiobuf);
+      __end:
+	snd_output_close(log);
+	snd_config_update_free_global();
+	prg_exit(EXIT_SUCCESS);
+	/* avoid warning */
+	return EXIT_SUCCESS;
+}
+
+/*
+ * Safe read (for pipes)
+ */
+ 
+static ssize_t safe_read(int fd, void *buf, size_t count)
+{
+	ssize_t result = 0, res;
+
+	while (count > 0) {
+		if ((res = read(fd, buf, count)) == 0)
+			break;
+		if (res < 0)
+			return result > 0 ? result : res;
+		count -= res;
+		result += res;
+		buf = (char *)buf + res;
+	}
+	return result;
+}
+
+/*
+ * Test, if it is a .VOC file and return >=0 if ok (this is the length of rest)
+ *                                       < 0 if not 
+ */
+static int test_vocfile(void *buffer)
+{
+	VocHeader *vp = buffer;
+
+	if (!memcmp(vp->magic, VOC_MAGIC_STRING, 20)) {
+		vocminor = LE_SHORT(vp->version) & 0xFF;
+		vocmajor = LE_SHORT(vp->version) / 256;
+		if (LE_SHORT(vp->version) != (0x1233 - LE_SHORT(vp->coded_ver)))
+			return -2;	/* coded version mismatch */
+		return LE_SHORT(vp->headerlen) - sizeof(VocHeader);	/* 0 mostly */
+	}
+	return -1;		/* magic string fail */
+}
+
+/*
+ * helper for test_wavefile
+ */
+
+static size_t test_wavefile_read(int fd, u_char *buffer, size_t *size, size_t reqsize, int line)
+{
+	if (*size >= reqsize)
+		return *size;
+	if ((size_t)safe_read(fd, buffer + *size, reqsize - *size) != reqsize - *size) {
+		error(_("read error (called from line %i)"), line);
+		prg_exit(EXIT_FAILURE);
+	}
+	return *size = reqsize;
+}
+
+#define check_wavefile_space(buffer, len, blimit) \
+	if (len > blimit) { \
+		blimit = len; \
+		if ((buffer = realloc(buffer, blimit)) == NULL) { \
+			error(_("not enough memory"));		  \
+			prg_exit(EXIT_FAILURE);  \
+		} \
+	}
+
+/*
+ * test, if it's a .WAV file, > 0 if ok (and set the speed, stereo etc.)
+ *                            == 0 if not
+ * Value returned is bytes to be discarded.
+ */
+static ssize_t test_wavefile(int fd, u_char *_buffer, size_t size)
+{
+	WaveHeader *h = (WaveHeader *)_buffer;
+	u_char *buffer = NULL;
+	size_t blimit = 0;
+	WaveFmtBody *f;
+	WaveChunkHeader *c;
+	u_int type, len;
+
+	if (size < sizeof(WaveHeader))
+		return -1;
+	if (h->magic != WAV_RIFF || h->type != WAV_WAVE)
+		return -1;
+	if (size > sizeof(WaveHeader)) {
+		check_wavefile_space(buffer, size - sizeof(WaveHeader), blimit);
+		memcpy(buffer, _buffer + sizeof(WaveHeader), size - sizeof(WaveHeader));
+	}
+	size -= sizeof(WaveHeader);
+	while (1) {
+		check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit);
+		test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__);
+		c = (WaveChunkHeader*)buffer;
+		type = c->type;
+		len = LE_INT(c->length);
+		len += len % 2;
+		if (size > sizeof(WaveChunkHeader))
+			memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader));
+		size -= sizeof(WaveChunkHeader);
+		if (type == WAV_FMT)
+			break;
+		check_wavefile_space(buffer, len, blimit);
+		test_wavefile_read(fd, buffer, &size, len, __LINE__);
+		if (size > len)
+			memmove(buffer, buffer + len, size - len);
+		size -= len;
+	}
+
+	if (len < sizeof(WaveFmtBody)) {
+		error(_("unknown length of 'fmt ' chunk (read %u, should be %u at least)"),
+		      len, (u_int)sizeof(WaveFmtBody));
+		prg_exit(EXIT_FAILURE);
+	}
+	check_wavefile_space(buffer, len, blimit);
+	test_wavefile_read(fd, buffer, &size, len, __LINE__);
+	f = (WaveFmtBody*) buffer;
+	if (LE_SHORT(f->format) == WAV_FMT_EXTENSIBLE) {
+		WaveFmtExtensibleBody *fe = (WaveFmtExtensibleBody*)buffer;
+		if (len < sizeof(WaveFmtExtensibleBody)) {
+			error(_("unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)"),
+					len, (u_int)sizeof(WaveFmtExtensibleBody));
+			prg_exit(EXIT_FAILURE);
+		}
+		if (memcmp(fe->guid_tag, WAV_GUID_TAG, 14) != 0) {
+			error(_("wrong format tag in extensible 'fmt ' chunk"));
+			prg_exit(EXIT_FAILURE);
+		}
+		f->format = fe->guid_format;
+	}
+        if (LE_SHORT(f->format) != WAV_FMT_PCM &&
+            LE_SHORT(f->format) != WAV_FMT_IEEE_FLOAT) {
+                error(_("can't play WAVE-file format 0x%04x which is not PCM or FLOAT encoded"), LE_SHORT(f->format));
+		prg_exit(EXIT_FAILURE);
+	}
+	if (LE_SHORT(f->channels) < 1) {
+		error(_("can't play WAVE-files with %d tracks"), LE_SHORT(f->channels));
+		prg_exit(EXIT_FAILURE);
+	}
+	hwparams.channels = LE_SHORT(f->channels);
+	switch (LE_SHORT(f->bit_p_spl)) {
+	case 8:
+		if (hwparams.format != DEFAULT_FORMAT &&
+		    hwparams.format != SND_PCM_FORMAT_U8)
+			fprintf(stderr, _("Warning: format is changed to U8\n"));
+		hwparams.format = SND_PCM_FORMAT_U8;
+		break;
+	case 16:
+		if (hwparams.format != DEFAULT_FORMAT &&
+		    hwparams.format != SND_PCM_FORMAT_S16_LE)
+			fprintf(stderr, _("Warning: format is changed to S16_LE\n"));
+		hwparams.format = SND_PCM_FORMAT_S16_LE;
+		break;
+	case 24:
+		switch (LE_SHORT(f->byte_p_spl) / hwparams.channels) {
+		case 3:
+			if (hwparams.format != DEFAULT_FORMAT &&
+			    hwparams.format != SND_PCM_FORMAT_S24_3LE)
+				fprintf(stderr, _("Warning: format is changed to S24_3LE\n"));
+			hwparams.format = SND_PCM_FORMAT_S24_3LE;
+			break;
+		case 4:
+			if (hwparams.format != DEFAULT_FORMAT &&
+			    hwparams.format != SND_PCM_FORMAT_S24_LE)
+				fprintf(stderr, _("Warning: format is changed to S24_LE\n"));
+			hwparams.format = SND_PCM_FORMAT_S24_LE;
+			break;
+		default:
+			error(_(" can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)"),
+			      LE_SHORT(f->bit_p_spl), LE_SHORT(f->byte_p_spl), hwparams.channels);
+			prg_exit(EXIT_FAILURE);
+		}
+		break;
+	case 32:
+                if (LE_SHORT(f->format) == WAV_FMT_PCM)
+                        hwparams.format = SND_PCM_FORMAT_S32_LE;
+                else if (LE_SHORT(f->format) == WAV_FMT_IEEE_FLOAT)
+                        hwparams.format = SND_PCM_FORMAT_FLOAT_LE;
+		break;
+	default:
+		error(_(" can't play WAVE-files with sample %d bits wide"),
+		      LE_SHORT(f->bit_p_spl));
+		prg_exit(EXIT_FAILURE);
+	}
+	hwparams.rate = LE_INT(f->sample_fq);
+	
+	if (size > len)
+		memmove(buffer, buffer + len, size - len);
+	size -= len;
+	
+	while (1) {
+		u_int type, len;
+
+		check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit);
+		test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__);
+		c = (WaveChunkHeader*)buffer;
+		type = c->type;
+		len = LE_INT(c->length);
+		if (size > sizeof(WaveChunkHeader))
+			memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader));
+		size -= sizeof(WaveChunkHeader);
+		if (type == WAV_DATA) {
+			if (len < pbrec_count && len < 0x7ffffffe)
+				pbrec_count = len;
+			if (size > 0)
+				memcpy(_buffer, buffer, size);
+			free(buffer);
+			return size;
+		}
+		len += len % 2;
+		check_wavefile_space(buffer, len, blimit);
+		test_wavefile_read(fd, buffer, &size, len, __LINE__);
+		if (size > len)
+			memmove(buffer, buffer + len, size - len);
+		size -= len;
+	}
+
+	/* shouldn't be reached */
+	return -1;
+}
+
+/*
+
+ */
+
+static int test_au(int fd, void *buffer)
+{
+	AuHeader *ap = buffer;
+
+	if (ap->magic != AU_MAGIC)
+		return -1;
+	if (BE_INT(ap->hdr_size) > 128 || BE_INT(ap->hdr_size) < 24)
+		return -1;
+	pbrec_count = BE_INT(ap->data_size);
+	switch (BE_INT(ap->encoding)) {
+	case AU_FMT_ULAW:
+		if (hwparams.format != DEFAULT_FORMAT &&
+		    hwparams.format != SND_PCM_FORMAT_MU_LAW)
+			fprintf(stderr, _("Warning: format is changed to MU_LAW\n"));
+		hwparams.format = SND_PCM_FORMAT_MU_LAW;
+		break;
+	case AU_FMT_LIN8:
+		if (hwparams.format != DEFAULT_FORMAT &&
+		    hwparams.format != SND_PCM_FORMAT_U8)
+			fprintf(stderr, _("Warning: format is changed to U8\n"));
+		hwparams.format = SND_PCM_FORMAT_U8;
+		break;
+	case AU_FMT_LIN16:
+		if (hwparams.format != DEFAULT_FORMAT &&
+		    hwparams.format != SND_PCM_FORMAT_S16_BE)
+			fprintf(stderr, _("Warning: format is changed to S16_BE\n"));
+		hwparams.format = SND_PCM_FORMAT_S16_BE;
+		break;
+	default:
+		return -1;
+	}
+	hwparams.rate = BE_INT(ap->sample_rate);
+	if (hwparams.rate < 2000 || hwparams.rate > 256000)
+		return -1;
+	hwparams.channels = BE_INT(ap->channels);
+	if (hwparams.channels < 1 || hwparams.channels > 256)
+		return -1;
+	if ((size_t)safe_read(fd, buffer + sizeof(AuHeader), BE_INT(ap->hdr_size) - sizeof(AuHeader)) != BE_INT(ap->hdr_size) - sizeof(AuHeader)) {
+		error(_("read error"));
+		prg_exit(EXIT_FAILURE);
+	}
+	return 0;
+}
+
+static void show_available_sample_formats(snd_pcm_hw_params_t* params)
+{
+	snd_pcm_format_t format;
+
+	fprintf(stderr, "Available formats:\n");
+	for (format = 0; format < SND_PCM_FORMAT_LAST; format++) {
+		if (snd_pcm_hw_params_test_format(handle, params, format) == 0)
+			fprintf(stderr, "- %s\n", snd_pcm_format_name(format));
+	}
+}
+
+static void set_params(void)
+{
+	snd_pcm_hw_params_t *params;
+	snd_pcm_sw_params_t *swparams;
+	snd_pcm_uframes_t buffer_size;
+	int err;
+	size_t n;
+	unsigned int rate;
+	snd_pcm_uframes_t start_threshold, stop_threshold;
+	snd_pcm_hw_params_alloca(&params);
+	snd_pcm_sw_params_alloca(&swparams);
+	err = snd_pcm_hw_params_any(handle, params);
+	if (err < 0) {
+		error(_("Broken configuration for this PCM: no configurations available"));
+		prg_exit(EXIT_FAILURE);
+	}
+	if (mmap_flag) {
+		snd_pcm_access_mask_t *mask = alloca(snd_pcm_access_mask_sizeof());
+		snd_pcm_access_mask_none(mask);
+		snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+		snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+		snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX);
+		err = snd_pcm_hw_params_set_access_mask(handle, params, mask);
+	} else if (interleaved)
+		err = snd_pcm_hw_params_set_access(handle, params,
+						   SND_PCM_ACCESS_RW_INTERLEAVED);
+	else
+		err = snd_pcm_hw_params_set_access(handle, params,
+						   SND_PCM_ACCESS_RW_NONINTERLEAVED);
+	if (err < 0) {
+		error(_("Access type not available"));
+		prg_exit(EXIT_FAILURE);
+	}
+	err = snd_pcm_hw_params_set_format(handle, params, hwparams.format);
+	if (err < 0) {
+		error(_("Sample format non available"));
+		show_available_sample_formats(params);
+		prg_exit(EXIT_FAILURE);
+	}
+	err = snd_pcm_hw_params_set_channels(handle, params, hwparams.channels);
+	if (err < 0) {
+		error(_("Channels count non available"));
+		prg_exit(EXIT_FAILURE);
+	}
+
+#if 0
+	err = snd_pcm_hw_params_set_periods_min(handle, params, 2);
+	assert(err >= 0);
+#endif
+	rate = hwparams.rate;
+	err = snd_pcm_hw_params_set_rate_near(handle, params, &hwparams.rate, 0);
+	assert(err >= 0);
+	if ((float)rate * 1.05 < hwparams.rate || (float)rate * 0.95 > hwparams.rate) {
+		if (!quiet_mode) {
+			char plugex[64];
+			const char *pcmname = snd_pcm_name(handle);
+			fprintf(stderr, _("Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"), rate, hwparams.rate);
+			if (! pcmname || strchr(snd_pcm_name(handle), ':'))
+				*plugex = 0;
+			else
+				snprintf(plugex, sizeof(plugex), "(-Dplug:%s)",
+					 snd_pcm_name(handle));
+			fprintf(stderr, _("         please, try the plug plugin %s\n"),
+				plugex);
+		}
+	}
+	rate = hwparams.rate;
+	if (buffer_time == 0 && buffer_frames == 0) {
+		err = snd_pcm_hw_params_get_buffer_time_max(params,
+							    &buffer_time, 0);
+		assert(err >= 0);
+		if (buffer_time > 500000)
+			buffer_time = 500000;
+	}
+	if (period_time == 0 && period_frames == 0) {
+		if (buffer_time > 0)
+			period_time = buffer_time / 4;
+		else
+			period_frames = buffer_frames / 4;
+	}
+	if (period_time > 0)
+		err = snd_pcm_hw_params_set_period_time_near(handle, params,
+							     &period_time, 0);
+	else
+		err = snd_pcm_hw_params_set_period_size_near(handle, params,
+							     &period_frames, 0);
+	assert(err >= 0);
+	if (buffer_time > 0) {
+		err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
+							     &buffer_time, 0);
+	} else {
+		err = snd_pcm_hw_params_set_buffer_size_near(handle, params,
+							     &buffer_frames);
+	}
+	assert(err >= 0);
+	monotonic = snd_pcm_hw_params_is_monotonic(params);
+	can_pause = snd_pcm_hw_params_can_pause(params);
+	err = snd_pcm_hw_params(handle, params);
+	if (err < 0) {
+		error(_("Unable to install hw params:"));
+		snd_pcm_hw_params_dump(params, log);
+		prg_exit(EXIT_FAILURE);
+	}
+	snd_pcm_hw_params_get_period_size(params, &chunk_size, 0);
+	snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
+	if (chunk_size == buffer_size) {
+		error(_("Can't use period equal to buffer size (%lu == %lu)"),
+		      chunk_size, buffer_size);
+		prg_exit(EXIT_FAILURE);
+	}
+	snd_pcm_sw_params_current(handle, swparams);
+	if (avail_min < 0)
+		n = chunk_size;
+	else
+		n = (double) rate * avail_min / 1000000;
+	err = snd_pcm_sw_params_set_avail_min(handle, swparams, n);
+
+	/* round up to closest transfer boundary */
+	n = buffer_size;
+	if (start_delay <= 0) {
+		start_threshold = n + (double) rate * start_delay / 1000000;
+	} else
+		start_threshold = (double) rate * start_delay / 1000000;
+	if (start_threshold < 1)
+		start_threshold = 1;
+	if (start_threshold > n)
+		start_threshold = n;
+	err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold);
+	assert(err >= 0);
+	if (stop_delay <= 0) 
+		stop_threshold = buffer_size + (double) rate * stop_delay / 1000000;
+	else
+		stop_threshold = (double) rate * stop_delay / 1000000;
+	err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold);
+	assert(err >= 0);
+
+	if (snd_pcm_sw_params(handle, swparams) < 0) {
+		error(_("unable to install sw params:"));
+		snd_pcm_sw_params_dump(swparams, log);
+		prg_exit(EXIT_FAILURE);
+	}
+
+	if (verbose)
+		snd_pcm_dump(handle, log);
+
+	bits_per_sample = snd_pcm_format_physical_width(hwparams.format);
+	bits_per_frame = bits_per_sample * hwparams.channels;
+	chunk_bytes = chunk_size * bits_per_frame / 8;
+	audiobuf = realloc(audiobuf, chunk_bytes);
+	if (audiobuf == NULL) {
+		error(_("not enough memory"));
+		prg_exit(EXIT_FAILURE);
+	}
+	// fprintf(stderr, "real chunk_size = %i, frags = %i, total = %i\n", chunk_size, setup.buf.block.frags, setup.buf.block.frags * chunk_size);
+
+	/* stereo VU-meter isn't always available... */
+	if (vumeter == VUMETER_STEREO) {
+		if (hwparams.channels != 2 || !interleaved || verbose > 2)
+			vumeter = VUMETER_MONO;
+	}
+
+	/* show mmap buffer arragment */
+	if (mmap_flag && verbose) {
+		const snd_pcm_channel_area_t *areas;
+		snd_pcm_uframes_t offset, size = chunk_size;
+		int i;
+		err = snd_pcm_mmap_begin(handle, &areas, &offset, &size);
+		if (err < 0) {
+			error(_("snd_pcm_mmap_begin problem: %s"), snd_strerror(err));
+			prg_exit(EXIT_FAILURE);
+		}
+		for (i = 0; i < hwparams.channels; i++)
+			fprintf(stderr, "mmap_area[%i] = %p,%u,%u (%u)\n", i, areas[i].addr, areas[i].first, areas[i].step, snd_pcm_format_physical_width(hwparams.format));
+		/* not required, but for sure */
+		snd_pcm_mmap_commit(handle, offset, 0);
+	}
+
+	buffer_frames = buffer_size;	/* for position test */
+}
+
+static void init_stdin(void)
+{
+	struct termios term;
+	long flags;
+
+	if (!interactive)
+		return;
+	tcgetattr(fileno(stdin), &term);
+	term_c_lflag = term.c_lflag;
+	if (fd == fileno(stdin))
+		return;
+	flags = fcntl(fileno(stdin), F_GETFL);
+	if (flags < 0 || fcntl(fileno(stdin), F_SETFL, flags|O_NONBLOCK) < 0)
+		fprintf(stderr, _("stdin O_NONBLOCK flag setup failed\n"));
+	term.c_lflag &= ~ICANON;
+	tcsetattr(fileno(stdin), TCSANOW, &term);
+}
+
+static void done_stdin(void)
+{
+	struct termios term;
+
+	if (!interactive)
+		return;
+	if (fd == fileno(stdin) || term_c_lflag == -1)
+		return;
+	tcgetattr(fileno(stdin), &term);
+	term.c_lflag = term_c_lflag;
+	tcsetattr(fileno(stdin), TCSANOW, &term);
+}
+
+static void do_pause(void)
+{
+	int err;
+	unsigned char b;
+
+	if (!can_pause) {
+		fprintf(stderr, _("\rPAUSE command ignored (no hw support)\n"));
+		return;
+	}
+	err = snd_pcm_pause(handle, 1);
+	if (err < 0) {
+		error(_("pause push error: %s"), snd_strerror(err));
+		return;
+	}
+	while (1) {
+		while (read(fileno(stdin), &b, 1) != 1);
+		if (b == ' ' || b == '\r') {
+			while (read(fileno(stdin), &b, 1) == 1);
+			err = snd_pcm_pause(handle, 0);
+			if (err < 0)
+				error(_("pause release error: %s"), snd_strerror(err));
+			return;
+		}
+	}
+}
+
+static void check_stdin(void)
+{
+	unsigned char b;
+
+	if (!interactive)
+		return;
+	if (fd != fileno(stdin)) {
+		while (read(fileno(stdin), &b, 1) == 1) {
+			if (b == ' ' || b == '\r') {
+				while (read(fileno(stdin), &b, 1) == 1);
+				fprintf(stderr, _("\r=== PAUSE ===                                                            "));
+				fflush(stderr);
+			do_pause();
+				fprintf(stderr, "                                                                          \r");
+				fflush(stderr);
+			}
+		}
+	}
+}
+
+#ifndef timersub
+#define	timersub(a, b, result) \
+do { \
+	(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+	(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+	if ((result)->tv_usec < 0) { \
+		--(result)->tv_sec; \
+		(result)->tv_usec += 1000000; \
+	} \
+} while (0)
+#endif
+
+#ifndef timermsub
+#define	timermsub(a, b, result) \
+do { \
+	(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+	(result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \
+	if ((result)->tv_nsec < 0) { \
+		--(result)->tv_sec; \
+		(result)->tv_nsec += 1000000000L; \
+	} \
+} while (0)
+#endif
+
+/* I/O error handler */
+static void xrun(void)
+{
+	snd_pcm_status_t *status;
+	int res;
+	
+	snd_pcm_status_alloca(&status);
+	if ((res = snd_pcm_status(handle, status))<0) {
+		error(_("status error: %s"), snd_strerror(res));
+		prg_exit(EXIT_FAILURE);
+	}
+	if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
+		if (monotonic) {
+#ifdef HAVE_CLOCK_GETTIME
+			struct timespec now, diff, tstamp;
+			clock_gettime(CLOCK_MONOTONIC, &now);
+			snd_pcm_status_get_trigger_htstamp(status, &tstamp);
+			timermsub(&now, &tstamp, &diff);
+			fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"),
+				stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"),
+				diff.tv_sec * 1000 + diff.tv_nsec / 10000000.0);
+#else
+			fprintf(stderr, "%s !!!\n", _("underrun"));
+#endif
+		} else {
+			struct timeval now, diff, tstamp;
+			gettimeofday(&now, 0);
+			snd_pcm_status_get_trigger_tstamp(status, &tstamp);
+			timersub(&now, &tstamp, &diff);
+			fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"),
+				stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"),
+				diff.tv_sec * 1000 + diff.tv_usec / 1000.0);
+		}
+		if (verbose) {
+			fprintf(stderr, _("Status:\n"));
+			snd_pcm_status_dump(status, log);
+		}
+		if ((res = snd_pcm_prepare(handle))<0) {
+			error(_("xrun: prepare error: %s"), snd_strerror(res));
+			prg_exit(EXIT_FAILURE);
+		}
+		return;		/* ok, data should be accepted again */
+	} if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING) {
+		if (verbose) {
+			fprintf(stderr, _("Status(DRAINING):\n"));
+			snd_pcm_status_dump(status, log);
+		}
+		if (stream == SND_PCM_STREAM_CAPTURE) {
+			fprintf(stderr, _("capture stream format change? attempting recover...\n"));
+			if ((res = snd_pcm_prepare(handle))<0) {
+				error(_("xrun(DRAINING): prepare error: %s"), snd_strerror(res));
+				prg_exit(EXIT_FAILURE);
+			}
+			return;
+		}
+	}
+	if (verbose) {
+		fprintf(stderr, _("Status(R/W):\n"));
+		snd_pcm_status_dump(status, log);
+	}
+	error(_("read/write error, state = %s"), snd_pcm_state_name(snd_pcm_status_get_state(status)));
+	prg_exit(EXIT_FAILURE);
+}
+
+/* I/O suspend handler */
+static void suspend(void)
+{
+	int res;
+
+	if (!quiet_mode)
+		fprintf(stderr, _("Suspended. Trying resume. ")); fflush(stderr);
+	while ((res = snd_pcm_resume(handle)) == -EAGAIN)
+		sleep(1);	/* wait until suspend flag is released */
+	if (res < 0) {
+		if (!quiet_mode)
+			fprintf(stderr, _("Failed. Restarting stream. ")); fflush(stderr);
+		if ((res = snd_pcm_prepare(handle)) < 0) {
+			error(_("suspend: prepare error: %s"), snd_strerror(res));
+			prg_exit(EXIT_FAILURE);
+		}
+	}
+	if (!quiet_mode)
+		fprintf(stderr, _("Done.\n"));
+}
+
+static void print_vu_meter_mono(int perc, int maxperc)
+{
+	const int bar_length = 50;
+	char line[80];
+	int val;
+
+	for (val = 0; val <= perc * bar_length / 100 && val < bar_length; val++)
+		line[val] = '#';
+	for (; val <= maxperc * bar_length / 100 && val < bar_length; val++)
+		line[val] = ' ';
+	line[val] = '+';
+	for (++val; val <= bar_length; val++)
+		line[val] = ' ';
+	if (maxperc > 99)
+		sprintf(line + val, "| MAX");
+	else
+		sprintf(line + val, "| %02i%%", maxperc);
+	fputs(line, stdout);
+	if (perc > 100)
+		printf(_(" !clip  "));
+}
+
+static void print_vu_meter_stereo(int *perc, int *maxperc)
+{
+	const int bar_length = 35;
+	char line[80];
+	int c;
+
+	memset(line, ' ', sizeof(line) - 1);
+	line[bar_length + 3] = '|';
+
+	for (c = 0; c < 2; c++) {
+		int p = perc[c] * bar_length / 100;
+		char tmp[4];
+		if (p > bar_length)
+			p = bar_length;
+		if (c)
+			memset(line + bar_length + 6 + 1, '#', p);
+		else
+			memset(line + bar_length - p - 1, '#', p);
+		p = maxperc[c] * bar_length / 100;
+		if (p > bar_length)
+			p = bar_length;
+		if (c)
+			line[bar_length + 6 + 1 + p] = '+';
+		else
+			line[bar_length - p - 1] = '+';
+		if (maxperc[c] > 99)
+			sprintf(tmp, "MAX");
+		else
+			sprintf(tmp, "%02d%%", maxperc[c]);
+		if (c)
+			memcpy(line + bar_length + 3 + 1, tmp, 3);
+		else
+			memcpy(line + bar_length, tmp, 3);
+	}
+	line[bar_length * 2 + 6 + 2] = 0;
+	fputs(line, stdout);
+}
+
+static void print_vu_meter(signed int *perc, signed int *maxperc)
+{
+	if (vumeter == VUMETER_STEREO)
+		print_vu_meter_stereo(perc, maxperc);
+	else
+		print_vu_meter_mono(*perc, *maxperc);
+}
+
+/* peak handler */
+static void compute_max_peak(u_char *data, size_t count)
+{
+	signed int val, max, perc[2], max_peak[2];
+	static	int	run = 0;
+	size_t ocount = count;
+	int	format_little_endian = snd_pcm_format_little_endian(hwparams.format);	
+	int ichans, c;
+
+	if (vumeter == VUMETER_STEREO)
+		ichans = 2;
+	else
+		ichans = 1;
+
+	memset(max_peak, 0, sizeof(max_peak));
+	switch (bits_per_sample) {
+	case 8: {
+		signed char *valp = (signed char *)data;
+		signed char mask = snd_pcm_format_silence(hwparams.format);
+		c = 0;
+		while (count-- > 0) {
+			val = *valp++ ^ mask;
+			val = abs(val);
+			if (max_peak[c] < val)
+				max_peak[c] = val;
+			if (vumeter == VUMETER_STEREO)
+				c = !c;
+		}
+		break;
+	}
+	case 16: {
+		signed short *valp = (signed short *)data;
+		signed short mask = snd_pcm_format_silence_16(hwparams.format);
+		signed short sval;
+
+		count /= 2;
+		c = 0;
+		while (count-- > 0) {
+			if (format_little_endian)
+				sval = le16toh(*valp);
+			else
+				sval = be16toh(*valp);
+			sval = abs(sval) ^ mask;
+			if (max_peak[c] < sval)
+				max_peak[c] = sval;
+			valp++;
+			if (vumeter == VUMETER_STEREO)
+				c = !c;
+		}
+		break;
+	}
+	case 24: {
+		unsigned char *valp = data;
+		signed int mask = snd_pcm_format_silence_32(hwparams.format);
+
+		count /= 3;
+		c = 0;
+		while (count-- > 0) {
+			if (format_little_endian) {
+				val = valp[0] | (valp[1]<<8) | (valp[2]<<16);
+			} else {
+				val = (valp[0]<<16) | (valp[1]<<8) | valp[2];
+			}
+			/* Correct signed bit in 32-bit value */
+			if (val & (1<<(bits_per_sample-1))) {
+				val |= 0xff<<24;	/* Negate upper bits too */
+			}
+			val = abs(val) ^ mask;
+			if (max_peak[c] < val)
+				max_peak[c] = val;
+			valp += 3;
+			if (vumeter == VUMETER_STEREO)
+				c = !c;
+		}
+		break;
+	}
+	case 32: {
+		signed int *valp = (signed int *)data;
+		signed int mask = snd_pcm_format_silence_32(hwparams.format);
+
+		count /= 4;
+		c = 0;
+		while (count-- > 0) {
+			if (format_little_endian)
+				val = le32toh(*valp);
+			else
+				val = be32toh(*valp);
+			val = abs(val) ^ mask;
+			if (max_peak[c] < val)
+				max_peak[c] = val;
+			valp++;
+			if (vumeter == VUMETER_STEREO)
+				c = !c;
+		}
+		break;
+	}
+	default:
+		if (run == 0) {
+			fprintf(stderr, _("Unsupported bit size %d.\n"), (int)bits_per_sample);
+			run = 1;
+		}
+		return;
+	}
+	max = 1 << (bits_per_sample-1);
+	if (max <= 0)
+		max = 0x7fffffff;
+
+	for (c = 0; c < ichans; c++) {
+		if (bits_per_sample > 16)
+			perc[c] = max_peak[c] / (max / 100);
+		else
+			perc[c] = max_peak[c] * 100 / max;
+	}
+
+	if (interleaved && verbose <= 2) {
+		static int maxperc[2];
+		static time_t t=0;
+		const time_t tt=time(NULL);
+		if(tt>t) {
+			t=tt;
+			maxperc[0] = 0;
+			maxperc[1] = 0;
+		}
+		for (c = 0; c < ichans; c++)
+			if (perc[c] > maxperc[c])
+				maxperc[c] = perc[c];
+
+		putchar('\r');
+		print_vu_meter(perc, maxperc);
+		fflush(stdout);
+	}
+	else if(verbose==3) {
+		printf(_("Max peak (%li samples): 0x%08x "), (long)ocount, max_peak[0]);
+		for (val = 0; val < 20; val++)
+			if (val <= perc[0] / 5)
+				putchar('#');
+			else
+				putchar(' ');
+		printf(" %i%%\n", perc[0]);
+		fflush(stdout);
+	}
+}
+
+static void do_test_position(void)
+{
+	static long counter = 0;
+	static time_t tmr = -1;
+	time_t now;
+	static float availsum, delaysum, samples;
+	static snd_pcm_sframes_t maxavail, maxdelay;
+	static snd_pcm_sframes_t minavail, mindelay;
+	static snd_pcm_sframes_t badavail = 0, baddelay = 0;
+	snd_pcm_sframes_t outofrange;
+	snd_pcm_sframes_t avail, delay;
+	int err;
+
+	err = snd_pcm_avail_delay(handle, &avail, &delay);
+	if (err < 0)
+		return;
+	outofrange = (test_coef * (snd_pcm_sframes_t)buffer_frames) / 2;
+	if (avail > outofrange || avail < -outofrange ||
+	    delay > outofrange || delay < -outofrange) {
+	  badavail = avail; baddelay = delay;
+	  availsum = delaysum = samples = 0;
+	  maxavail = maxdelay = 0;
+	  minavail = mindelay = buffer_frames * 16;
+	  fprintf(stderr, _("Suspicious buffer position (%li total): "
+	  	"avail = %li, delay = %li, buffer = %li\n"),
+	  	++counter, (long)avail, (long)delay, (long)buffer_frames);
+	} else if (verbose) {
+		time(&now);
+		if (tmr == (time_t) -1) {
+			tmr = now;
+			availsum = delaysum = samples = 0;
+			maxavail = maxdelay = 0;
+			minavail = mindelay = buffer_frames * 16;
+		}
+		if (avail > maxavail)
+			maxavail = avail;
+		if (delay > maxdelay)
+			maxdelay = delay;
+		if (avail < minavail)
+			minavail = avail;
+		if (delay < mindelay)
+			mindelay = delay;
+		availsum += avail;
+		delaysum += delay;
+		samples++;
+		if (avail != 0 && now != tmr) {
+			fprintf(stderr, "BUFPOS: avg%li/%li "
+				"min%li/%li max%li/%li (%li) (%li:%li/%li)\n",
+				(long)(availsum / samples),
+				(long)(delaysum / samples),
+				(long)minavail, (long)mindelay,
+				(long)maxavail, (long)maxdelay,
+				(long)buffer_frames,
+				counter, badavail, baddelay);
+			tmr = now;
+		}
+	}
+}
+
+/*
+ *  write function
+ */
+
+static ssize_t pcm_write(u_char *data, size_t count)
+{
+	ssize_t r;
+	ssize_t result = 0;
+
+	if (count < chunk_size) {
+		snd_pcm_format_set_silence(hwparams.format, data + count * bits_per_frame / 8, (chunk_size - count) * hwparams.channels);
+		count = chunk_size;
+	}
+	while (count > 0) {
+		if (test_position)
+			do_test_position();
+		check_stdin();
+		r = writei_func(handle, data, count);
+		if (test_position)
+			do_test_position();
+		if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) {
+			if (!test_nowait)
+				snd_pcm_wait(handle, 100);
+		} else if (r == -EPIPE) {
+			xrun();
+		} else if (r == -ESTRPIPE) {
+			suspend();
+		} else if (r < 0) {
+			error(_("write error: %s"), snd_strerror(r));
+			prg_exit(EXIT_FAILURE);
+		}
+		if (r > 0) {
+			if (vumeter)
+				compute_max_peak(data, r * hwparams.channels);
+			result += r;
+			count -= r;
+			data += r * bits_per_frame / 8;
+		}
+	}
+	return result;
+}
+
+static ssize_t pcm_writev(u_char **data, unsigned int channels, size_t count)
+{
+	ssize_t r;
+	size_t result = 0;
+
+	if (count != chunk_size) {
+		unsigned int channel;
+		size_t offset = count;
+		size_t remaining = chunk_size - count;
+		for (channel = 0; channel < channels; channel++)
+			snd_pcm_format_set_silence(hwparams.format, data[channel] + offset * bits_per_sample / 8, remaining);
+		count = chunk_size;
+	}
+	while (count > 0) {
+		unsigned int channel;
+		void *bufs[channels];
+		size_t offset = result;
+		for (channel = 0; channel < channels; channel++)
+			bufs[channel] = data[channel] + offset * bits_per_sample / 8;
+		if (test_position)
+			do_test_position();
+		check_stdin();
+		r = writen_func(handle, bufs, count);
+		if (test_position)
+			do_test_position();
+		if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) {
+			if (!test_nowait)
+				snd_pcm_wait(handle, 100);
+		} else if (r == -EPIPE) {
+			xrun();
+		} else if (r == -ESTRPIPE) {
+			suspend();
+		} else if (r < 0) {
+			error(_("writev error: %s"), snd_strerror(r));
+			prg_exit(EXIT_FAILURE);
+		}
+		if (r > 0) {
+			if (vumeter) {
+				for (channel = 0; channel < channels; channel++)
+					compute_max_peak(data[channel], r);
+			}
+			result += r;
+			count -= r;
+		}
+	}
+	return result;
+}
+
+/*
+ *  read function
+ */
+
+static ssize_t pcm_read(u_char *data, size_t rcount)
+{
+	ssize_t r;
+	size_t result = 0;
+	size_t count = rcount;
+
+	if (count != chunk_size) {
+		count = chunk_size;
+	}
+
+	while (count > 0) {
+		if (test_position)
+			do_test_position();
+		check_stdin();
+		r = readi_func(handle, data, count);
+		if (test_position)
+			do_test_position();
+		if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) {
+			if (!test_nowait)
+				snd_pcm_wait(handle, 100);
+		} else if (r == -EPIPE) {
+			xrun();
+		} else if (r == -ESTRPIPE) {
+			suspend();
+		} else if (r < 0) {
+			error(_("read error: %s"), snd_strerror(r));
+			prg_exit(EXIT_FAILURE);
+		}
+		if (r > 0) {
+			if (vumeter)
+				compute_max_peak(data, r * hwparams.channels);
+			result += r;
+			count -= r;
+			data += r * bits_per_frame / 8;
+		}
+	}
+	return rcount;
+}
+
+static ssize_t pcm_readv(u_char **data, unsigned int channels, size_t rcount)
+{
+	ssize_t r;
+	size_t result = 0;
+	size_t count = rcount;
+
+	if (count != chunk_size) {
+		count = chunk_size;
+	}
+
+	while (count > 0) {
+		unsigned int channel;
+		void *bufs[channels];
+		size_t offset = result;
+		for (channel = 0; channel < channels; channel++)
+			bufs[channel] = data[channel] + offset * bits_per_sample / 8;
+		if (test_position)
+			do_test_position();
+		check_stdin();
+		r = readn_func(handle, bufs, count);
+		if (test_position)
+			do_test_position();
+		if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) {
+			if (!test_nowait)
+				snd_pcm_wait(handle, 100);
+		} else if (r == -EPIPE) {
+			xrun();
+		} else if (r == -ESTRPIPE) {
+			suspend();
+		} else if (r < 0) {
+			error(_("readv error: %s"), snd_strerror(r));
+			prg_exit(EXIT_FAILURE);
+		}
+		if (r > 0) {
+			if (vumeter) {
+				for (channel = 0; channel < channels; channel++)
+					compute_max_peak(data[channel], r);
+			}
+			result += r;
+			count -= r;
+		}
+	}
+	return rcount;
+}
+
+/*
+ *  ok, let's play a .voc file
+ */
+
+static ssize_t voc_pcm_write(u_char *data, size_t count)
+{
+	ssize_t result = count, r;
+	size_t size;
+
+	while (count > 0) {
+		size = count;
+		if (size > chunk_bytes - buffer_pos)
+			size = chunk_bytes - buffer_pos;
+		memcpy(audiobuf + buffer_pos, data, size);
+		data += size;
+		count -= size;
+		buffer_pos += size;
+		if ((size_t)buffer_pos == chunk_bytes) {
+			if ((size_t)(r = pcm_write(audiobuf, chunk_size)) != chunk_size)
+				return r;
+			buffer_pos = 0;
+		}
+	}
+	return result;
+}
+
+static void voc_write_silence(unsigned x)
+{
+	unsigned l;
+	u_char *buf;
+
+	buf = (u_char *) malloc(chunk_bytes);
+	if (buf == NULL) {
+		error(_("can't allocate buffer for silence"));
+		return;		/* not fatal error */
+	}
+	snd_pcm_format_set_silence(hwparams.format, buf, chunk_size * hwparams.channels);
+	while (x > 0) {
+		l = x;
+		if (l > chunk_size)
+			l = chunk_size;
+		if (voc_pcm_write(buf, l) != (ssize_t)l) {
+			error(_("write error"));
+			prg_exit(EXIT_FAILURE);
+		}
+		x -= l;
+	}
+	free(buf);
+}
+
+static void voc_pcm_flush(void)
+{
+	if (buffer_pos > 0) {
+		size_t b;
+		if (snd_pcm_format_set_silence(hwparams.format, audiobuf + buffer_pos, chunk_bytes - buffer_pos * 8 / bits_per_sample) < 0)
+			fprintf(stderr, _("voc_pcm_flush - silence error"));
+		b = chunk_size;
+		if (pcm_write(audiobuf, b) != (ssize_t)b)
+			error(_("voc_pcm_flush error"));
+	}
+	snd_pcm_nonblock(handle, 0);
+	snd_pcm_drain(handle);
+	snd_pcm_nonblock(handle, nonblock);
+}
+
+static void voc_play(int fd, int ofs, char *name)
+{
+	int l;
+	VocBlockType *bp;
+	VocVoiceData *vd;
+	VocExtBlock *eb;
+	size_t nextblock, in_buffer;
+	u_char *data, *buf;
+	char was_extended = 0, output = 0;
+	u_short *sp, repeat = 0;
+	size_t silence;
+	off64_t filepos = 0;
+
+#define COUNT(x)	nextblock -= x; in_buffer -= x; data += x
+#define COUNT1(x)	in_buffer -= x; data += x
+
+	data = buf = (u_char *)malloc(64 * 1024);
+	buffer_pos = 0;
+	if (data == NULL) {
+		error(_("malloc error"));
+		prg_exit(EXIT_FAILURE);
+	}
+	if (!quiet_mode) {
+		fprintf(stderr, _("Playing Creative Labs Channel file '%s'...\n"), name);
+	}
+	/* first we waste the rest of header, ugly but we don't need seek */
+	while (ofs > (ssize_t)chunk_bytes) {
+		if ((size_t)safe_read(fd, buf, chunk_bytes) != chunk_bytes) {
+			error(_("read error"));
+			prg_exit(EXIT_FAILURE);
+		}
+		ofs -= chunk_bytes;
+	}
+	if (ofs) {
+		if (safe_read(fd, buf, ofs) != ofs) {
+			error(_("read error"));
+			prg_exit(EXIT_FAILURE);
+		}
+	}
+	hwparams.format = DEFAULT_FORMAT;
+	hwparams.channels = 1;
+	hwparams.rate = DEFAULT_SPEED;
+	set_params();
+
+	in_buffer = nextblock = 0;
+	while (1) {
+	      Fill_the_buffer:	/* need this for repeat */
+		if (in_buffer < 32) {
+			/* move the rest of buffer to pos 0 and fill the buf up */
+			if (in_buffer)
+				memcpy(buf, data, in_buffer);
+			data = buf;
+			if ((l = safe_read(fd, buf + in_buffer, chunk_bytes - in_buffer)) > 0)
+				in_buffer += l;
+			else if (!in_buffer) {
+				/* the file is truncated, so simulate 'Terminator' 
+				   and reduce the datablock for safe landing */
+				nextblock = buf[0] = 0;
+				if (l == -1) {
+					perror(name);
+					prg_exit(EXIT_FAILURE);
+				}
+			}
+		}
+		while (!nextblock) {	/* this is a new block */
+			if (in_buffer < sizeof(VocBlockType))
+				goto __end;
+			bp = (VocBlockType *) data;
+			COUNT1(sizeof(VocBlockType));
+			nextblock = VOC_DATALEN(bp);
+			if (output && !quiet_mode)
+				fprintf(stderr, "\n");	/* write /n after ASCII-out */
+			output = 0;
+			switch (bp->type) {
+			case 0:
+#if 0
+				d_printf("Terminator\n");
+#endif
+				return;		/* VOC-file stop */
+			case 1:
+				vd = (VocVoiceData *) data;
+				COUNT1(sizeof(VocVoiceData));
+				/* we need a SYNC, before we can set new SPEED, STEREO ... */
+
+				if (!was_extended) {
+					hwparams.rate = (int) (vd->tc);
+					hwparams.rate = 1000000 / (256 - hwparams.rate);
+#if 0
+					d_printf("Channel data %d Hz\n", dsp_speed);
+#endif
+					if (vd->pack) {		/* /dev/dsp can't it */
+						error(_("can't play packed .voc files"));
+						return;
+					}
+					if (hwparams.channels == 2)		/* if we are in Stereo-Mode, switch back */
+						hwparams.channels = 1;
+				} else {	/* there was extended block */
+					hwparams.channels = 2;
+					was_extended = 0;
+				}
+				set_params();
+				break;
+			case 2:	/* nothing to do, pure data */
+#if 0
+				d_printf("Channel continuation\n");
+#endif
+				break;
+			case 3:	/* a silence block, no data, only a count */
+				sp = (u_short *) data;
+				COUNT1(sizeof(u_short));
+				hwparams.rate = (int) (*data);
+				COUNT1(1);
+				hwparams.rate = 1000000 / (256 - hwparams.rate);
+				set_params();
+				silence = (((size_t) * sp) * 1000) / hwparams.rate;
+#if 0
+				d_printf("Silence for %d ms\n", (int) silence);
+#endif
+				voc_write_silence(*sp);
+				break;
+			case 4:	/* a marker for syncronisation, no effect */
+				sp = (u_short *) data;
+				COUNT1(sizeof(u_short));
+#if 0
+				d_printf("Marker %d\n", *sp);
+#endif
+				break;
+			case 5:	/* ASCII text, we copy to stderr */
+				output = 1;
+#if 0
+				d_printf("ASCII - text :\n");
+#endif
+				break;
+			case 6:	/* repeat marker, says repeatcount */
+				/* my specs don't say it: maybe this can be recursive, but
+				   I don't think somebody use it */
+				repeat = *(u_short *) data;
+				COUNT1(sizeof(u_short));
+#if 0
+				d_printf("Repeat loop %d times\n", repeat);
+#endif
+				if (filepos >= 0) {	/* if < 0, one seek fails, why test another */
+					if ((filepos = lseek64(fd, 0, 1)) < 0) {
+						error(_("can't play loops; %s isn't seekable\n"), name);
+						repeat = 0;
+					} else {
+						filepos -= in_buffer;	/* set filepos after repeat */
+					}
+				} else {
+					repeat = 0;
+				}
+				break;
+			case 7:	/* ok, lets repeat that be rewinding tape */
+				if (repeat) {
+					if (repeat != 0xFFFF) {
+#if 0
+						d_printf("Repeat loop %d\n", repeat);
+#endif
+						--repeat;
+					}
+#if 0
+					else
+						d_printf("Neverending loop\n");
+#endif
+					lseek64(fd, filepos, 0);
+					in_buffer = 0;	/* clear the buffer */
+					goto Fill_the_buffer;
+				}
+#if 0
+				else
+					d_printf("End repeat loop\n");
+#endif
+				break;
+			case 8:	/* the extension to play Stereo, I have SB 1.0 :-( */
+				was_extended = 1;
+				eb = (VocExtBlock *) data;
+				COUNT1(sizeof(VocExtBlock));
+				hwparams.rate = (int) (eb->tc);
+				hwparams.rate = 256000000L / (65536 - hwparams.rate);
+				hwparams.channels = eb->mode == VOC_MODE_STEREO ? 2 : 1;
+				if (hwparams.channels == 2)
+					hwparams.rate = hwparams.rate >> 1;
+				if (eb->pack) {		/* /dev/dsp can't it */
+					error(_("can't play packed .voc files"));
+					return;
+				}
+#if 0
+				d_printf("Extended block %s %d Hz\n",
+					 (eb->mode ? "Stereo" : "Mono"), dsp_speed);
+#endif
+				break;
+			default:
+				error(_("unknown blocktype %d. terminate."), bp->type);
+				return;
+			}	/* switch (bp->type) */
+		}		/* while (! nextblock)  */
+		/* put nextblock data bytes to dsp */
+		l = in_buffer;
+		if (nextblock < (size_t)l)
+			l = nextblock;
+		if (l) {
+			if (output && !quiet_mode) {
+				if (write(2, data, l) != l) {	/* to stderr */
+					error(_("write error"));
+					prg_exit(EXIT_FAILURE);
+				}
+			} else {
+				if (voc_pcm_write(data, l) != l) {
+					error(_("write error"));
+					prg_exit(EXIT_FAILURE);
+				}
+			}
+			COUNT(l);
+		}
+	}			/* while(1) */
+      __end:
+        voc_pcm_flush();
+        free(buf);
+}
+/* that was a big one, perhaps somebody split it :-) */
+
+/* setting the globals for playing raw data */
+static void init_raw_data(void)
+{
+	hwparams = rhwparams;
+}
+
+/* calculate the data count to read from/to dsp */
+static off64_t calc_count(void)
+{
+	off64_t count;
+
+	if (timelimit == 0) {
+		count = pbrec_count;
+	} else {
+		count = snd_pcm_format_size(hwparams.format, hwparams.rate * hwparams.channels);
+		count *= (off64_t)timelimit;
+	}
+	return count < pbrec_count ? count : pbrec_count;
+}
+
+/* write a .VOC-header */
+static void begin_voc(int fd, size_t cnt)
+{
+	VocHeader vh;
+	VocBlockType bt;
+	VocVoiceData vd;
+	VocExtBlock eb;
+
+	memcpy(vh.magic, VOC_MAGIC_STRING, 20);
+	vh.headerlen = LE_SHORT(sizeof(VocHeader));
+	vh.version = LE_SHORT(VOC_ACTUAL_VERSION);
+	vh.coded_ver = LE_SHORT(0x1233 - VOC_ACTUAL_VERSION);
+
+	if (write(fd, &vh, sizeof(VocHeader)) != sizeof(VocHeader)) {
+		error(_("write error"));
+		prg_exit(EXIT_FAILURE);
+	}
+	if (hwparams.channels > 1) {
+		/* write an extended block */
+		bt.type = 8;
+		bt.datalen = 4;
+		bt.datalen_m = bt.datalen_h = 0;
+		if (write(fd, &bt, sizeof(VocBlockType)) != sizeof(VocBlockType)) {
+			error(_("write error"));
+			prg_exit(EXIT_FAILURE);
+		}
+		eb.tc = LE_SHORT(65536 - 256000000L / (hwparams.rate << 1));
+		eb.pack = 0;
+		eb.mode = 1;
+		if (write(fd, &eb, sizeof(VocExtBlock)) != sizeof(VocExtBlock)) {
+			error(_("write error"));
+			prg_exit(EXIT_FAILURE);
+		}
+	}
+	bt.type = 1;
+	cnt += sizeof(VocVoiceData);	/* Channel_data block follows */
+	bt.datalen = (u_char) (cnt & 0xFF);
+	bt.datalen_m = (u_char) ((cnt & 0xFF00) >> 8);
+	bt.datalen_h = (u_char) ((cnt & 0xFF0000) >> 16);
+	if (write(fd, &bt, sizeof(VocBlockType)) != sizeof(VocBlockType)) {
+		error(_("write error"));
+		prg_exit(EXIT_FAILURE);
+	}
+	vd.tc = (u_char) (256 - (1000000 / hwparams.rate));
+	vd.pack = 0;
+	if (write(fd, &vd, sizeof(VocVoiceData)) != sizeof(VocVoiceData)) {
+		error(_("write error"));
+		prg_exit(EXIT_FAILURE);
+	}
+}
+
+/* write a WAVE-header */
+static void begin_wave(int fd, size_t cnt)
+{
+	WaveHeader h;
+	WaveFmtBody f;
+	WaveChunkHeader cf, cd;
+	int bits;
+	u_int tmp;
+	u_short tmp2;
+
+	/* WAVE cannot handle greater than 32bit (signed?) int */
+	if (cnt == (size_t)-2)
+		cnt = 0x7fffff00;
+
+	bits = 8;
+	switch ((unsigned long) hwparams.format) {
+	case SND_PCM_FORMAT_U8:
+		bits = 8;
+		break;
+	case SND_PCM_FORMAT_S16_LE:
+		bits = 16;
+		break;
+	case SND_PCM_FORMAT_S32_LE:
+        case SND_PCM_FORMAT_FLOAT_LE:
+		bits = 32;
+		break;
+	case SND_PCM_FORMAT_S24_LE:
+	case SND_PCM_FORMAT_S24_3LE:
+		bits = 24;
+		break;
+	default:
+		error(_("Wave doesn't support %s format..."), snd_pcm_format_name(hwparams.format));
+		prg_exit(EXIT_FAILURE);
+	}
+	h.magic = WAV_RIFF;
+	tmp = cnt + sizeof(WaveHeader) + sizeof(WaveChunkHeader) + sizeof(WaveFmtBody) + sizeof(WaveChunkHeader) - 8;
+	h.length = LE_INT(tmp);
+	h.type = WAV_WAVE;
+
+	cf.type = WAV_FMT;
+	cf.length = LE_INT(16);
+
+        if (hwparams.format == SND_PCM_FORMAT_FLOAT_LE)
+                f.format = LE_SHORT(WAV_FMT_IEEE_FLOAT);
+        else
+                f.format = LE_SHORT(WAV_FMT_PCM);
+	f.channels = LE_SHORT(hwparams.channels);
+	f.sample_fq = LE_INT(hwparams.rate);
+#if 0
+	tmp2 = (samplesize == 8) ? 1 : 2;
+	f.byte_p_spl = LE_SHORT(tmp2);
+	tmp = dsp_speed * hwparams.channels * (u_int) tmp2;
+#else
+	tmp2 = hwparams.channels * snd_pcm_format_physical_width(hwparams.format) / 8;
+	f.byte_p_spl = LE_SHORT(tmp2);
+	tmp = (u_int) tmp2 * hwparams.rate;
+#endif
+	f.byte_p_sec = LE_INT(tmp);
+	f.bit_p_spl = LE_SHORT(bits);
+
+	cd.type = WAV_DATA;
+	cd.length = LE_INT(cnt);
+
+	if (write(fd, &h, sizeof(WaveHeader)) != sizeof(WaveHeader) ||
+	    write(fd, &cf, sizeof(WaveChunkHeader)) != sizeof(WaveChunkHeader) ||
+	    write(fd, &f, sizeof(WaveFmtBody)) != sizeof(WaveFmtBody) ||
+	    write(fd, &cd, sizeof(WaveChunkHeader)) != sizeof(WaveChunkHeader)) {
+		error(_("write error"));
+		prg_exit(EXIT_FAILURE);
+	}
+}
+
+/* write a Au-header */
+static void begin_au(int fd, size_t cnt)
+{
+	AuHeader ah;
+
+	ah.magic = AU_MAGIC;
+	ah.hdr_size = BE_INT(24);
+	ah.data_size = BE_INT(cnt);
+	switch ((unsigned long) hwparams.format) {
+	case SND_PCM_FORMAT_MU_LAW:
+		ah.encoding = BE_INT(AU_FMT_ULAW);
+		break;
+	case SND_PCM_FORMAT_U8:
+		ah.encoding = BE_INT(AU_FMT_LIN8);
+		break;
+	case SND_PCM_FORMAT_S16_BE:
+		ah.encoding = BE_INT(AU_FMT_LIN16);
+		break;
+	default:
+		error(_("Sparc Audio doesn't support %s format..."), snd_pcm_format_name(hwparams.format));
+		prg_exit(EXIT_FAILURE);
+	}
+	ah.sample_rate = BE_INT(hwparams.rate);
+	ah.channels = BE_INT(hwparams.channels);
+	if (write(fd, &ah, sizeof(AuHeader)) != sizeof(AuHeader)) {
+		error(_("write error"));
+		prg_exit(EXIT_FAILURE);
+	}
+}
+
+/* closing .VOC */
+static void end_voc(int fd)
+{
+	off64_t length_seek;
+	VocBlockType bt;
+	size_t cnt;
+	char dummy = 0;		/* Write a Terminator */
+
+	if (write(fd, &dummy, 1) != 1) {
+		error(_("write error"));
+		prg_exit(EXIT_FAILURE);
+	}
+	length_seek = sizeof(VocHeader);
+	if (hwparams.channels > 1)
+		length_seek += sizeof(VocBlockType) + sizeof(VocExtBlock);
+	bt.type = 1;
+	cnt = fdcount;
+	cnt += sizeof(VocVoiceData);	/* Channel_data block follows */
+	if (cnt > 0x00ffffff)
+		cnt = 0x00ffffff;
+	bt.datalen = (u_char) (cnt & 0xFF);
+	bt.datalen_m = (u_char) ((cnt & 0xFF00) >> 8);
+	bt.datalen_h = (u_char) ((cnt & 0xFF0000) >> 16);
+	if (lseek64(fd, length_seek, SEEK_SET) == length_seek)
+		write(fd, &bt, sizeof(VocBlockType));
+	if (fd != 1)
+		close(fd);
+}
+
+static void end_wave(int fd)
+{				/* only close output */
+	WaveChunkHeader cd;
+	off64_t length_seek;
+	off64_t filelen;
+	u_int rifflen;
+	
+	length_seek = sizeof(WaveHeader) +
+		      sizeof(WaveChunkHeader) +
+		      sizeof(WaveFmtBody);
+	cd.type = WAV_DATA;
+	cd.length = fdcount > 0x7fffffff ? LE_INT(0x7fffffff) : LE_INT(fdcount);
+	filelen = fdcount + 2*sizeof(WaveChunkHeader) + sizeof(WaveFmtBody) + 4;
+	rifflen = filelen > 0x7fffffff ? LE_INT(0x7fffffff) : LE_INT(filelen);
+	if (lseek64(fd, 4, SEEK_SET) == 4)
+		write(fd, &rifflen, 4);
+	if (lseek64(fd, length_seek, SEEK_SET) == length_seek)
+		write(fd, &cd, sizeof(WaveChunkHeader));
+	if (fd != 1)
+		close(fd);
+}
+
+static void end_au(int fd)
+{				/* only close output */
+	AuHeader ah;
+	off64_t length_seek;
+	
+	length_seek = (char *)&ah.data_size - (char *)&ah;
+	ah.data_size = fdcount > 0xffffffff ? 0xffffffff : BE_INT(fdcount);
+	if (lseek64(fd, length_seek, SEEK_SET) == length_seek)
+		write(fd, &ah.data_size, sizeof(ah.data_size));
+	if (fd != 1)
+		close(fd);
+}
+
+static void header(int rtype, char *name)
+{
+	if (!quiet_mode) {
+		if (! name)
+			name = (stream == SND_PCM_STREAM_PLAYBACK) ? "stdout" : "stdin";
+		fprintf(stderr, "%s %s '%s' : ",
+			(stream == SND_PCM_STREAM_PLAYBACK) ? _("Playing") : _("Recording"),
+			gettext(fmt_rec_table[rtype].what),
+			name);
+		fprintf(stderr, "%s, ", snd_pcm_format_description(hwparams.format));
+		fprintf(stderr, _("Rate %d Hz, "), hwparams.rate);
+		if (hwparams.channels == 1)
+			fprintf(stderr, _("Mono"));
+		else if (hwparams.channels == 2)
+			fprintf(stderr, _("Stereo"));
+		else
+			fprintf(stderr, _("Channels %i"), hwparams.channels);
+		fprintf(stderr, "\n");
+	}
+}
+
+/* playing raw data */
+
+static void playback_go(int fd, size_t loaded, off64_t count, int rtype, char *name)
+{
+	int l, r;
+	off64_t written = 0;
+	off64_t c;
+
+	header(rtype, name);
+	set_params();
+
+	while (loaded > chunk_bytes && written < count) {
+		if (pcm_write(audiobuf + written, chunk_size) <= 0)
+			return;
+		written += chunk_bytes;
+		loaded -= chunk_bytes;
+	}
+	if (written > 0 && loaded > 0)
+		memmove(audiobuf, audiobuf + written, loaded);
+
+	l = loaded;
+	while (written < count) {
+		do {
+			c = count - written;
+			if (c > chunk_bytes)
+				c = chunk_bytes;
+			c -= l;
+
+			if (c == 0)
+				break;
+			r = safe_read(fd, audiobuf + l, c);
+			if (r < 0) {
+				perror(name);
+				prg_exit(EXIT_FAILURE);
+			}
+			fdcount += r;
+			if (r == 0)
+				break;
+			l += r;
+		} while ((size_t)l < chunk_bytes);
+		l = l * 8 / bits_per_frame;
+		r = pcm_write(audiobuf, l);
+		if (r != l)
+			break;
+		r = r * bits_per_frame / 8;
+		written += r;
+		l = 0;
+	}
+	snd_pcm_nonblock(handle, 0);
+	snd_pcm_drain(handle);
+	snd_pcm_nonblock(handle, nonblock);
+}
+
+
+/*
+ *  let's play or capture it (capture_type says VOC/WAVE/raw)
+ */
+
+static void playback(char *name)
+{
+	int ofs;
+	size_t dta;
+	ssize_t dtawave;
+
+	pbrec_count = LLONG_MAX;
+	fdcount = 0;
+	if (!name || !strcmp(name, "-")) {
+		fd = fileno(stdin);
+		name = "stdin";
+	} else {
+		init_stdin();
+		if ((fd = open64(name, O_RDONLY, 0)) == -1) {
+			perror(name);
+			prg_exit(EXIT_FAILURE);
+		}
+	}
+	/* read the file header */
+	dta = sizeof(AuHeader);
+	if ((size_t)safe_read(fd, audiobuf, dta) != dta) {
+		error(_("read error"));
+		prg_exit(EXIT_FAILURE);
+	}
+	if (test_au(fd, audiobuf) >= 0) {
+		rhwparams.format = hwparams.format;
+		pbrec_count = calc_count();
+		playback_go(fd, 0, pbrec_count, FORMAT_AU, name);
+		goto __end;
+	}
+	dta = sizeof(VocHeader);
+	if ((size_t)safe_read(fd, audiobuf + sizeof(AuHeader),
+		 dta - sizeof(AuHeader)) != dta - sizeof(AuHeader)) {
+		error(_("read error"));
+		prg_exit(EXIT_FAILURE);;
+	}
+	if ((ofs = test_vocfile(audiobuf)) >= 0) {
+		pbrec_count = calc_count();
+		voc_play(fd, ofs, name);
+		goto __end;
+	}
+	/* read bytes for WAVE-header */
+	if ((dtawave = test_wavefile(fd, audiobuf, dta)) >= 0) {
+		pbrec_count = calc_count();
+		playback_go(fd, dtawave, pbrec_count, FORMAT_WAVE, name);
+	} else {
+		/* should be raw data */
+		init_raw_data();
+		pbrec_count = calc_count();
+		playback_go(fd, dta, pbrec_count, FORMAT_RAW, name);
+	}
+      __end:
+	if (fd != 0)
+		close(fd);
+}
+
+/**
+ * mystrftime
+ *
+ *   Variant of strftime(3) that supports additional format
+ *   specifiers in the format string.
+ *
+ * Parameters:
+ *
+ *   s	  - destination string
+ *   max	- max number of bytes to write
+ *   userformat - format string
+ *   tm	 - time information
+ *   filenumber - the number of the file, starting at 1
+ *
+ * Returns: number of bytes written to the string s
+ */
+size_t mystrftime(char *s, size_t max, const char *userformat,
+		  const struct tm *tm, const int filenumber)
+{
+	char formatstring[PATH_MAX] = "";
+	char tempstring[PATH_MAX] = "";
+	char *format, *tempstr;
+	const char *pos_userformat;
+
+	format = formatstring;
+
+	/* if mystrftime is called with userformat = NULL we return a zero length string */
+	if (userformat == NULL) {
+		*s = '\0';
+		return 0;
+	}
+
+	for (pos_userformat = userformat; *pos_userformat; ++pos_userformat) {
+		if (*pos_userformat == '%') {
+			tempstr = tempstring;
+			tempstr[0] = '\0';
+			switch (*++pos_userformat) {
+
+				case '\0': // end of string
+					--pos_userformat;
+					break;
+
+				case 'v': // file number 
+					sprintf(tempstr, "%02d", filenumber);
+					break;
+
+				default: // All other codes will be handled by strftime
+					*format++ = '%';
+					*format++ = *pos_userformat;
+					continue;
+			}
+
+			/* If a format specifier was found and used, copy the result. */
+			if (tempstr[0]) {
+				while ((*format = *tempstr++) != '\0')
+					++format;
+				continue;
+			}
+		}
+
+		/* For any other character than % we simply copy the character */
+		*format++ = *pos_userformat;
+	}
+
+	*format = '\0';
+	format = formatstring;
+	return strftime(s, max, format, tm);
+}
+
+static int new_capture_file(char *name, char *namebuf, size_t namelen,
+			    int filecount)
+{
+	char *s;
+	char buf[PATH_MAX+1];
+	time_t t;
+	struct tm *tmp;
+
+	if (use_strftime) {
+		t = time(NULL);
+		tmp = localtime(&t);
+		if (tmp == NULL) {
+			perror("localtime");
+			prg_exit(EXIT_FAILURE);
+		}
+		if (mystrftime(namebuf, namelen, name, tmp, filecount+1) == 0) {
+			fprintf(stderr, "mystrftime returned 0");
+			prg_exit(EXIT_FAILURE);
+		}
+		return filecount;
+	}
+
+	/* get a copy of the original filename */
+	strncpy(buf, name, sizeof(buf));
+
+	/* separate extension from filename */
+	s = buf + strlen(buf);
+	while (s > buf && *s != '.' && *s != '/')
+		--s;
+	if (*s == '.')
+		*s++ = 0;
+	else if (*s == '/')
+		s = buf + strlen(buf);
+
+	/* upon first jump to this if block rename the first file */
+	if (filecount == 1) {
+		if (*s)
+			snprintf(namebuf, namelen, "%s-01.%s", buf, s);
+		else
+			snprintf(namebuf, namelen, "%s-01", buf);
+		remove(namebuf);
+		rename(name, namebuf);
+		filecount = 2;
+	}
+
+	/* name of the current file */
+	if (*s)
+		snprintf(namebuf, namelen, "%s-%02i.%s", buf, filecount, s);
+	else
+		snprintf(namebuf, namelen, "%s-%02i", buf, filecount);
+
+	return filecount;
+}
+
+/**
+ * create_path
+ *
+ *   This function creates a file path, like mkdir -p. 
+ *
+ * Parameters:
+ *
+ *   path - the path to create
+ *
+ * Returns: 0 on success, -1 on failure
+ * On failure, a message has been printed to stderr.
+ */
+int create_path(const char *path)
+{
+	char *start;
+	mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+
+	if (path[0] == '/')
+		start = strchr(path + 1, '/');
+	else
+		start = strchr(path, '/');
+
+	while (start) {
+		char *buffer = strdup(path);
+		buffer[start-path] = 0x00;
+
+		if (mkdir(buffer, mode) == -1 && errno != EEXIST) {
+			fprintf(stderr, "Problem creating directory %s", buffer);
+			perror(" ");
+			free(buffer);
+			return -1;
+		}
+		free(buffer);
+		start = strchr(start + 1, '/');
+	}
+	return 0;
+}
+
+static int safe_open(const char *name)
+{
+	int fd;
+
+	fd = open64(name, O_WRONLY | O_CREAT, 0644);
+	if (fd == -1) {
+		if (errno != ENOENT || !use_strftime)
+			return -1;
+		if (create_path(name) == 0)
+			fd = open64(name, O_WRONLY | O_CREAT, 0644);
+	}
+	return fd;
+}
+
+static void capture(char *orig_name)
+{
+	int tostdout=0;		/* boolean which describes output stream */
+	int filecount=0;	/* number of files written */
+	char *name = orig_name;	/* current filename */
+	char namebuf[PATH_MAX+1];
+	off64_t count, rest;		/* number of bytes to capture */
+
+	/* get number of bytes to capture */
+	count = calc_count();
+	if (count == 0)
+		count = LLONG_MAX;
+	/* compute the number of bytes per file */
+	max_file_size = max_file_time *
+		snd_pcm_format_size(hwparams.format,
+				    hwparams.rate * hwparams.channels);
+	/* WAVE-file should be even (I'm not sure), but wasting one byte
+	   isn't a problem (this can only be in 8 bit mono) */
+	if (count < LLONG_MAX)
+		count += count % 2;
+	else
+		count -= count % 2;
+
+	/* display verbose output to console */
+	header(file_type, name);
+
+	/* setup sound hardware */
+	set_params();
+
+	/* write to stdout? */
+	if (!name || !strcmp(name, "-")) {
+		fd = fileno(stdout);
+		name = "stdout";
+		tostdout=1;
+		if (count > fmt_rec_table[file_type].max_filesize)
+			count = fmt_rec_table[file_type].max_filesize;
+	}
+	init_stdin();
+
+	do {
+		/* open a file to write */
+		if(!tostdout) {
+			/* upon the second file we start the numbering scheme */
+			if (filecount || use_strftime) {
+				filecount = new_capture_file(orig_name, namebuf,
+							     sizeof(namebuf),
+							     filecount);
+				name = namebuf;
+			}
+			
+			/* open a new file */
+			remove(name);
+			fd = safe_open(name);
+			if (fd < 0) {
+				perror(name);
+				prg_exit(EXIT_FAILURE);
+			}
+			filecount++;
+		}
+
+		rest = count;
+		if (rest > fmt_rec_table[file_type].max_filesize)
+			rest = fmt_rec_table[file_type].max_filesize;
+		if (max_file_size && (rest > max_file_size)) 
+			rest = max_file_size;
+
+		/* setup sample header */
+		if (fmt_rec_table[file_type].start)
+			fmt_rec_table[file_type].start(fd, rest);
+
+		/* capture */
+		fdcount = 0;
+		while (rest > 0 && recycle_capture_file == 0) {
+			size_t c = (rest <= (off64_t)chunk_bytes) ?
+				(size_t)rest : chunk_bytes;
+			size_t f = c * 8 / bits_per_frame;
+			if (pcm_read(audiobuf, f) != f)
+				break;
+			if (write(fd, audiobuf, c) != c) {
+				perror(name);
+				prg_exit(EXIT_FAILURE);
+			}
+			count -= c;
+			rest -= c;
+			fdcount += c;
+		}
+
+		/* re-enable SIGUSR1 signal */
+		if (recycle_capture_file) {
+			recycle_capture_file = 0;
+			signal(SIGUSR1, signal_handler_recycle);
+		}
+
+		/* finish sample container */
+		if (fmt_rec_table[file_type].end && !tostdout) {
+			fmt_rec_table[file_type].end(fd);
+			fd = -1;
+		}
+
+		/* repeat the loop when format is raw without timelimit or
+		 * requested counts of data are recorded
+		 */
+	} while ((file_type == FORMAT_RAW && !timelimit) || count > 0);
+}
+
+static void playbackv_go(int* fds, unsigned int channels, size_t loaded, off64_t count, int rtype, char **names)
+{
+	int r;
+	size_t vsize;
+
+	unsigned int channel;
+	u_char *bufs[channels];
+
+	header(rtype, names[0]);
+	set_params();
+
+	vsize = chunk_bytes / channels;
+
+	// Not yet implemented
+	assert(loaded == 0);
+
+	for (channel = 0; channel < channels; ++channel)
+		bufs[channel] = audiobuf + vsize * channel;
+
+	while (count > 0) {
+		size_t c = 0;
+		size_t expected = count / channels;
+		if (expected > vsize)
+			expected = vsize;
+		do {
+			r = safe_read(fds[0], bufs[0], expected);
+			if (r < 0) {
+				perror(names[channel]);
+				prg_exit(EXIT_FAILURE);
+			}
+			for (channel = 1; channel < channels; ++channel) {
+				if (safe_read(fds[channel], bufs[channel], r) != r) {
+					perror(names[channel]);
+					prg_exit(EXIT_FAILURE);
+				}
+			}
+			if (r == 0)
+				break;
+			c += r;
+		} while (c < expected);
+		c = c * 8 / bits_per_sample;
+		r = pcm_writev(bufs, channels, c);
+		if ((size_t)r != c)
+			break;
+		r = r * bits_per_frame / 8;
+		count -= r;
+	}
+	snd_pcm_nonblock(handle, 0);
+	snd_pcm_drain(handle);
+	snd_pcm_nonblock(handle, nonblock);
+}
+
+static void capturev_go(int* fds, unsigned int channels, off64_t count, int rtype, char **names)
+{
+	size_t c;
+	ssize_t r;
+	unsigned int channel;
+	size_t vsize;
+	u_char *bufs[channels];
+
+	header(rtype, names[0]);
+	set_params();
+
+	vsize = chunk_bytes / channels;
+
+	for (channel = 0; channel < channels; ++channel)
+		bufs[channel] = audiobuf + vsize * channel;
+
+	while (count > 0) {
+		size_t rv;
+		c = count;
+		if (c > chunk_bytes)
+			c = chunk_bytes;
+		c = c * 8 / bits_per_frame;
+		if ((size_t)(r = pcm_readv(bufs, channels, c)) != c)
+			break;
+		rv = r * bits_per_sample / 8;
+		for (channel = 0; channel < channels; ++channel) {
+			if ((size_t)write(fds[channel], bufs[channel], rv) != rv) {
+				perror(names[channel]);
+				prg_exit(EXIT_FAILURE);
+			}
+		}
+		r = r * bits_per_frame / 8;
+		count -= r;
+		fdcount += r;
+	}
+}
+
+static void playbackv(char **names, unsigned int count)
+{
+	int ret = 0;
+	unsigned int channel;
+	unsigned int channels = rhwparams.channels;
+	int alloced = 0;
+	int fds[channels];
+	for (channel = 0; channel < channels; ++channel)
+		fds[channel] = -1;
+
+	if (count == 1 && channels > 1) {
+		size_t len = strlen(names[0]);
+		char format[1024];
+		memcpy(format, names[0], len);
+		strcpy(format + len, ".%d");
+		len += 4;
+		names = malloc(sizeof(*names) * channels);
+		for (channel = 0; channel < channels; ++channel) {
+			names[channel] = malloc(len);
+			sprintf(names[channel], format, channel);
+		}
+		alloced = 1;
+	} else if (count != channels) {
+		error(_("You need to specify %d files"), channels);
+		prg_exit(EXIT_FAILURE);
+	}
+
+	for (channel = 0; channel < channels; ++channel) {
+		fds[channel] = open(names[channel], O_RDONLY, 0);
+		if (fds[channel] < 0) {
+			perror(names[channel]);
+			ret = EXIT_FAILURE;
+			goto __end;
+		}
+	}
+	/* should be raw data */
+	init_raw_data();
+	pbrec_count = calc_count();
+	playbackv_go(fds, channels, 0, pbrec_count, FORMAT_RAW, names);
+
+      __end:
+	for (channel = 0; channel < channels; ++channel) {
+		if (fds[channel] >= 0)
+			close(fds[channel]);
+		if (alloced)
+			free(names[channel]);
+	}
+	if (alloced)
+		free(names);
+	if (ret)
+		prg_exit(ret);
+}
+
+static void capturev(char **names, unsigned int count)
+{
+	int ret = 0;
+	unsigned int channel;
+	unsigned int channels = rhwparams.channels;
+	int alloced = 0;
+	int fds[channels];
+	for (channel = 0; channel < channels; ++channel)
+		fds[channel] = -1;
+
+	if (count == 1) {
+		size_t len = strlen(names[0]);
+		char format[1024];
+		memcpy(format, names[0], len);
+		strcpy(format + len, ".%d");
+		len += 4;
+		names = malloc(sizeof(*names) * channels);
+		for (channel = 0; channel < channels; ++channel) {
+			names[channel] = malloc(len);
+			sprintf(names[channel], format, channel);
+		}
+		alloced = 1;
+	} else if (count != channels) {
+		error(_("You need to specify %d files"), channels);
+		prg_exit(EXIT_FAILURE);
+	}
+
+	for (channel = 0; channel < channels; ++channel) {
+		fds[channel] = open(names[channel], O_WRONLY + O_CREAT, 0644);
+		if (fds[channel] < 0) {
+			perror(names[channel]);
+			ret = EXIT_FAILURE;
+			goto __end;
+		}
+	}
+	/* should be raw data */
+	init_raw_data();
+	pbrec_count = calc_count();
+	capturev_go(fds, channels, pbrec_count, FORMAT_RAW, names);
+
+      __end:
+	for (channel = 0; channel < channels; ++channel) {
+		if (fds[channel] >= 0)
+			close(fds[channel]);
+		if (alloced)
+			free(names[channel]);
+	}
+	if (alloced)
+		free(names);
+	if (ret)
+		prg_exit(ret);
+}
diff --git a/aplay/formats.h b/aplay/formats.h
new file mode 100644
index 0000000..b5314f9
--- /dev/null
+++ b/aplay/formats.h
@@ -0,0 +1,127 @@
+#ifndef FORMATS_H
+#define FORMATS_H		1
+
+#include <endian.h>
+#include <byteswap.h>
+
+/* Definitions for .VOC files */
+
+#define VOC_MAGIC_STRING	"Creative Voice File\x1A"
+#define VOC_ACTUAL_VERSION	0x010A
+#define VOC_SAMPLESIZE		8
+
+#define VOC_MODE_MONO		0
+#define VOC_MODE_STEREO		1
+
+#define VOC_DATALEN(bp)		((u_long)(bp->datalen) | \
+                         	((u_long)(bp->datalen_m) << 8) | \
+                         	((u_long)(bp->datalen_h) << 16) )
+
+typedef struct voc_header {
+	u_char magic[20];	/* must be MAGIC_STRING */
+	u_short headerlen;	/* Headerlength, should be 0x1A */
+	u_short version;	/* VOC-file version */
+	u_short coded_ver;	/* 0x1233-version */
+} VocHeader;
+
+typedef struct voc_blocktype {
+	u_char type;
+	u_char datalen;		/* low-byte    */
+	u_char datalen_m;	/* medium-byte */
+	u_char datalen_h;	/* high-byte   */
+} VocBlockType;
+
+typedef struct voc_voice_data {
+	u_char tc;
+	u_char pack;
+} VocVoiceData;
+
+typedef struct voc_ext_block {
+	u_short tc;
+	u_char pack;
+	u_char mode;
+} VocExtBlock;
+
+/* Definitions for Microsoft WAVE format */
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define COMPOSE_ID(a,b,c,d)	((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
+#define LE_SHORT(v)		(v)
+#define LE_INT(v)		(v)
+#define BE_SHORT(v)		bswap_16(v)
+#define BE_INT(v)		bswap_32(v)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define COMPOSE_ID(a,b,c,d)	((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
+#define LE_SHORT(v)		bswap_16(v)
+#define LE_INT(v)		bswap_32(v)
+#define BE_SHORT(v)		(v)
+#define BE_INT(v)		(v)
+#else
+#error "Wrong endian"
+#endif
+
+#define WAV_RIFF		COMPOSE_ID('R','I','F','F')
+#define WAV_WAVE		COMPOSE_ID('W','A','V','E')
+#define WAV_FMT			COMPOSE_ID('f','m','t',' ')
+#define WAV_DATA		COMPOSE_ID('d','a','t','a')
+
+/* WAVE fmt block constants from Microsoft mmreg.h header */
+#define WAV_FMT_PCM             0x0001
+#define WAV_FMT_IEEE_FLOAT      0x0003
+#define WAV_FMT_DOLBY_AC3_SPDIF 0x0092
+#define WAV_FMT_EXTENSIBLE      0xfffe
+
+/* Used with WAV_FMT_EXTENSIBLE format */
+#define WAV_GUID_TAG		"\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71"
+
+/* it's in chunks like .voc and AMIGA iff, but my source say there
+   are in only in this combination, so I combined them in one header;
+   it works on all WAVE-file I have
+ */
+typedef struct {
+	u_int magic;		/* 'RIFF' */
+	u_int length;		/* filelen */
+	u_int type;		/* 'WAVE' */
+} WaveHeader;
+
+typedef struct {
+	u_short format;		/* see WAV_FMT_* */
+	u_short channels;
+	u_int sample_fq;	/* frequence of sample */
+	u_int byte_p_sec;
+	u_short byte_p_spl;	/* samplesize; 1 or 2 bytes */
+	u_short bit_p_spl;	/* 8, 12 or 16 bit */
+} WaveFmtBody;
+
+typedef struct {
+	WaveFmtBody format;
+	u_short ext_size;
+	u_short bit_p_spl;
+	u_int channel_mask;
+	u_short guid_format;	/* WAV_FMT_* */
+	u_char guid_tag[14];	/* WAV_GUID_TAG */
+} WaveFmtExtensibleBody;
+
+typedef struct {
+	u_int type;		/* 'data' */
+	u_int length;		/* samplecount */
+} WaveChunkHeader;
+
+/* Definitions for Sparc .au header */
+
+#define AU_MAGIC		COMPOSE_ID('.','s','n','d')
+
+#define AU_FMT_ULAW		1
+#define AU_FMT_LIN8		2
+#define AU_FMT_LIN16		3
+
+typedef struct au_header {
+	u_int magic;		/* '.snd' */
+	u_int hdr_size;		/* size of header (min 24) */
+	u_int data_size;	/* size of data */
+	u_int encoding;		/* see to AU_FMT_XXXX */
+	u_int sample_rate;	/* sample rate */
+	u_int channels;		/* number of channels (voices) */
+} AuHeader;
+
+#endif				/* FORMATS */
diff --git a/configure.in b/configure.in
new file mode 100644
index 0000000..2208a95
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,315 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.59)
+AC_INIT(aplay/aplay.c)
+AC_PREFIX_DEFAULT(/usr)
+AM_INIT_AUTOMAKE(alsa-utils, 1.0.25)
+
+AM_GNU_GETTEXT([external])
+AM_GNU_GETTEXT_VERSION([0.15])
+
+dnl Checks for programs.
+
+dnl try to gues cross-compiler if not set
+if test "x$target" != "x$host" -a -z "`echo $CC | grep -e '-gcc'`";
+then
+  AC_MSG_CHECKING(for cross-compiler)
+
+  which ${program_prefix}gcc >/dev/null 2>&1 && CC=${program_prefix}gcc
+  which ${target_cpu}-${target_os}-gcc >/dev/null 2>&1 \
+  && CC=${target_cpu}-${target-os}-gcc
+  which ${target_cpu}-${target_vendor}-${target_os}-gcc >/dev/null 2>&1 \
+  && CC=${target_cpu}-${target_vendor}-${target_os}-gcc
+
+  AC_MSG_RESULT($CC)
+fi
+
+AC_PROG_CC
+dnl AC_PROG_CXX
+AC_PROG_INSTALL
+AC_PROG_MKDIR_P
+AC_PROG_LN_S
+AC_PROG_SED
+AM_PATH_ALSA(1.0.24)
+AC_CHECK_FUNC([snd_ctl_elem_add_enumerated],
+	      , [AC_ERROR([No user enum control support in alsa-lib])])
+
+dnl Check components
+AC_CHECK_HEADERS([alsa/pcm.h], [have_pcm="yes"], [have_pcm="no"],
+  [#include <alsa/asoundlib.h>])
+AC_CHECK_HEADERS([alsa/mixer.h], [have_mixer="yes"], [have_mixer="no"],
+  [#include <alsa/asoundlib.h>])
+AC_CHECK_HEADERS([alsa/rawmidi.h], [have_rawmidi="yes"], [have_rawmidi="no"],
+  [#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=""
+AC_MSG_CHECKING(for librt)
+AC_ARG_WITH(librt,
+  AS_HELP_STRING([--with-librt], [Use librt for monotonic clock (default = yes)]),
+  [ have_librt="$withval" ], [ have_librt="yes" ])
+if test "$have_librt" = "yes"; then
+  AC_CHECK_LIB([rt], [clock_gettime], [HAVE_LIBRT="yes"])
+  if test "$HAVE_LIBRT" = "yes" ; then
+    LIBRT="-lrt"
+    AC_DEFINE([HAVE_LIBRT], 1, [Have librt])
+    AC_DEFINE([HAVE_CLOCK_GETTIME], 1, [Have clock gettime])
+  fi
+else
+  AC_MSG_RESULT(no)
+fi
+
+dnl Disable alsamixer
+CURSESINC=""
+CURSESLIB=""
+CURSES_CFLAGS=""
+alsamixer=
+if test "$have_mixer" = "yes"; then
+AC_ARG_ENABLE(alsamixer,
+     [  --disable-alsamixer     Disable alsamixer compilation],
+     [case "${enableval}" in
+       yes) alsamixer=true ;;
+       no)  alsamixer=false ;;
+       *) AC_MSG_ERROR(bad value ${enableval} for --enable-alsamixer) ;;
+     esac],[alsamixer=true])
+fi
+AM_CONDITIONAL(ALSAMIXER, test x$alsamixer = xtrue)
+
+dnl Disable alsaconf
+AC_ARG_ENABLE(alsaconf,
+     [  --disable-alsaconf      Disable alsaconf packaging],
+     [case "${enableval}" in
+       yes) alsaconf=true ;;
+       no)  alsaconf=false ;;
+       *) AC_MSG_ERROR(bad value ${enableval} for --enable-alsaconf) ;;
+     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=""
+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
+  AC_ARG_WITH(curses,
+    [  --with-curses libname   Specify the curses library to use (default=auto)],
+    curseslib="$withval",
+    curseslib="auto")
+  CURSESLIBDIR=""
+  NCURSESLIBSUFFIX=""
+  CURSES_NLS="no"
+  if test "$curseslib" = "ncursesw" -o \( "$curseslib" = "auto" -a "$USE_NLS" = "yes" \); then
+    AC_CHECK_PROG([ncursesw5_config], [ncursesw5-config], [yes])
+    if test "$ncursesw5_config" = "yes"; then
+      CURSESINC="<ncurses.h>"
+      CURSESLIB=`ncursesw5-config --libs`
+      CURSESLIBDIR=`ncursesw5-config --libdir`
+      CURSES_CFLAGS=`ncursesw5-config --cflags`
+      curseslib="ncursesw"
+    else
+      AC_CHECK_LIB(ncursesw, initscr,
+                 [ CURSESINC='<ncurses.h>'; CURSESLIB='-lncursesw'; curseslib="ncursesw"])
+    fi
+    if test -n "$CURSESINC"; then
+      NCURSESLIBSUFFIX="w"
+      CURSES_NLS="yes"
+    fi
+  fi
+  if test "$curseslib" = "ncurses" -o "$curseslib" = "auto"; then
+    AC_CHECK_PROG([ncurses5_config], [ncurses5-config], [yes])
+    if test "$ncurses5_config" = "yes"; then
+      CURSESINC="<ncurses.h>"
+      CURSESLIB=`ncurses5-config --libs`
+      CURSESLIBDIR=`ncurses5-config --libdir`
+      CURSES_CFLAGS=`ncurses5-config --cflags`
+      curseslib="ncurses"
+    else
+      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,
+                 [ CURSESINC='<curses.h>'; CURSESLIB='-lcurses'; curseslib="curses"])
+  fi
+  if test -z "$CURSESINC"; then
+     AC_MSG_ERROR(this packages requires a curses library)
+  fi
+
+  AC_MSG_CHECKING([for curses library])
+  AC_MSG_RESULT([$curseslib])
+  AC_MSG_CHECKING([for curses header name])
+  AC_MSG_RESULT([$CURSESINC])
+  AC_MSG_CHECKING([for curses compiler flags])
+  AC_MSG_RESULT([$CURSES_CFLAGS])
+
+  dnl CURSESLIBS might have the library path at the beginning.  If so, we cut it
+  dnl off so that we can insert the other curses libraries before the ncurses
+  dnl library but after the library path (which is later again prepended below).
+  if test -n "$CURSESLIBDIR"; then
+    if test "-L$CURSESLIBDIR " = "$(echo $CURSESLIB | cut -c-$((${#CURSESLIBDIR}+3)) )"; then
+      CURSESLIB="$(echo $CURSESLIB | cut -c$((${#CURSESLIBDIR}+4))-)"
+    fi
+  fi
+
+  saved_CFLAGS="$CFLAGS"
+  saved_LDFLAGS="$LDFLAGS"
+  saved_LIBS="$LIBS"
+  CFLAGS="$CFLAGS $CURSES_CFLAGS"
+  if test -n "$CURSESLIBDIR"; then
+    LDFLAGS="$LDFLAGS -L$CURSESLIBDIR"
+  fi
+  LIBS="$CURSESLIB $LIBS"
+
+  AC_TRY_LINK([#include <panel.h>], [set_escdelay(100);],[HAVE_CURSES_ESCDELAY="yes"])
+  if test "$HAVE_CURSES_ESCDELAY" = "yes"; then
+    AC_DEFINE([HAVE_CURSES_ESCDELAY], 1, [Have curses set_escdelay])
+  fi
+
+  if test "$USE_NLS" = "yes"; then
+    AC_MSG_CHECKING([for curses NLS support])
+    dnl In theory, a single-byte curses works just fine in ISO 8859-* locales.
+    dnl In practice, however, everybody uses UTF-8 nowadays, so we'd better
+    dnl check for wide-character support.
+    dnl For ncurses/ncursesw, CURSES_NLS was already set above.
+    if test "$curseslib" = "curses"; then
+      AC_TRY_LINK([
+          #define _XOPEN_SOURCE 1
+          #define _XOPEN_SOURCE_EXTENDED 1
+          #include <curses.h>
+        ], [
+          cchar_t wc;
+          setcchar(&wc, L"x", A_NORMAL, 0, 0);
+        ],
+        [CURSES_NLS="yes"])
+    fi
+    AC_MSG_RESULT([$CURSES_NLS])
+    if test "$CURSES_NLS" = "yes"; then
+      AC_DEFINE([ENABLE_NLS_IN_CURSES], [1],
+                [Define if curses-based programs can show translated messages.])
+    fi
+  fi
+
+  AC_CHECK_HEADERS([panel.h menu.h form.h], [],
+                   [AC_MSG_ERROR([required curses helper header not found])])
+  AC_CHECK_LIB([panel$NCURSESLIBSUFFIX], [new_panel],
+               [CURSESLIB="-lpanel$NCURSESLIBSUFFIX $CURSESLIB"],
+               [AC_MSG_ERROR([panel$NCURSESLIBSUFFIX library not found])])
+  AC_CHECK_LIB([menu$NCURSESLIBSUFFIX], [new_menu],
+               [CURSESLIB="-lmenu$NCURSESLIBSUFFIX $CURSESLIB"],
+               [AC_MSG_ERROR([menu$NCURSESLIBSUFFIX library not found])])
+  AC_CHECK_LIB([form$NCURSESLIBSUFFIX], [new_form],
+               [CURSESLIB="-lform$NCURSESLIBSUFFIX $CURSESLIB"],
+               [AC_MSG_ERROR([form$NCURSESLIBSUFFIX library not found])])
+
+  CFLAGS="$saved_CFLAGS"
+  LDFLAGS="$saved_LDFLAGS"
+  LIBS="$saved_LIBS"
+
+  if test -n "$CURSESLIBDIR"; then
+    CURSESLIB="-L$CURSESLIBDIR $CURSESLIB"
+  fi
+
+  AC_MSG_CHECKING([for curses linker flags])
+  AC_MSG_RESULT([$CURSESLIB])
+fi
+
+AC_SUBST(CURSESINC)
+AC_SUBST(CURSESLIB)
+AC_SUBST(CURSES_CFLAGS)
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+
+eval dir="$datadir"
+case "$dir" in
+/*) ;;
+*) dir="$prefix/share"
+esac
+
+soundsdir="$dir/sounds/alsa"
+AC_DEFINE_UNQUOTED(SOUNDSDIR, "$soundsdir", [directory containing sample data])
+
+mydatadir="$dir/alsa"
+AC_DEFINE_UNQUOTED(DATADIR, "$mydatadir", [directory containing alsa configuration])
+
+AC_ARG_WITH(testsound,
+  [  --with-testsound=file        give the path of test sound file for alsaconf],
+  TESTSOUND="$withval",
+  TESTSOUND="$dir/test.wav")
+AC_SUBST(TESTSOUND)
+
+AM_CONFIG_HEADER(include/aconfig.h)
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_C_INLINE
+AC_HEADER_TIME
+
+dnl Checks for library functions.
+AC_PROG_GCC_TRADITIONAL
+
+dnl Enable largefile support
+AC_SYS_LARGEFILE
+
+SAVE_UTIL_VERSION
+
+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 \
+	  alsaconf/alsaconf alsaconf/Makefile \
+	  alsaconf/po/Makefile \
+	  alsaucm/Makefile \
+	  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 \
+	  alsaloop/Makefile)
diff --git a/cvscompile b/cvscompile
new file mode 100755
index 0000000..64ade4f
--- /dev/null
+++ b/cvscompile
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+if test -d ../alsa-lib/utils && ! test -r `aclocal --print-ac-dir`/alsa.m4; then
+  alsa_m4_flags="-I ../alsa-lib/utils"
+fi
+aclocal $alsa_m4_flags $ACLOCAL_FLAGS
+# save original files to avoid stupid modifications by gettextize
+cp Makefile.am Makefile.am.ok
+cp configure.in configure.in.ok
+gettextize -c -f --no-changelog
+echo "EXTRA_DIST = gettext.m4" > m4/Makefile.am
+cp Makefile.am.ok Makefile.am
+cp configure.in.ok configure.in
+autoheader
+automake --foreign --copy --add-missing
+touch depcomp		# for older automake
+autoconf
+export CFLAGS='-O2 -Wall -pipe -g'
+echo "CFLAGS=$CFLAGS"
+echo "./configure $@"
+./configure $@ || exit 1
+unset CFLAGS
+if [ -z "$CVSCOMPILE_NO_MAKE" ]; then
+  make
+fi
diff --git a/gitcompile b/gitcompile
new file mode 100755
index 0000000..513ef72
--- /dev/null
+++ b/gitcompile
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+if test -d ../alsa-lib/utils && ! test -r `aclocal --print-ac-dir`/alsa.m4; then
+  alsa_m4_flags="-I ../alsa-lib/utils"
+fi
+aclocal $alsa_m4_flags $ACLOCAL_FLAGS
+# save original files to avoid stupid modifications by gettextize
+cp Makefile.am Makefile.am.ok
+cp configure.in configure.in.ok
+gettextize -c -f --no-changelog
+echo "EXTRA_DIST = gettext.m4" > m4/Makefile.am
+cp Makefile.am.ok Makefile.am
+cp configure.in.ok configure.in
+autoheader
+automake --foreign --copy --add-missing
+touch depcomp		# for older automake
+autoconf
+export CFLAGS='-O2 -Wall -pipe -g'
+echo "CFLAGS=$CFLAGS"
+echo "./configure $@"
+./configure $@ || exit 1
+unset CFLAGS
+if [ -z "$GITCOMPILE_NO_MAKE" ]; then
+  make
+fi
diff --git a/iecset/Makefile.am b/iecset/Makefile.am
new file mode 100644
index 0000000..c03a7f1
--- /dev/null
+++ b/iecset/Makefile.am
@@ -0,0 +1,9 @@
+INCLUDES = -I$(top_srcdir)/include
+LDADD = -lm
+# LDFLAGS = -static
+# CFLAGS += -g -Wall
+
+bin_PROGRAMS = iecset
+iecset_SOURCES = iecset.c iecbits.c
+man_MANS = iecset.1
+EXTRA_DIST = iecset.1
diff --git a/iecset/iecbits.c b/iecset/iecbits.c
new file mode 100644
index 0000000..edea932
--- /dev/null
+++ b/iecset/iecbits.c
@@ -0,0 +1,259 @@
+/*
+   iecdump - dump IEC958 status bits on ALSA
+   Copyright (C) 2003 by Takashi Iwai <tiwai@suse.de>
+
+   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 <alsa/asoundlib.h>
+
+struct category_str {
+	int val;
+	const char *name;
+};
+
+static const struct category_str con_category[] = {
+	{ IEC958_AES1_CON_GENERAL, "general" },
+
+	{ IEC958_AES1_CON_IEC908_CD, "CD" },
+	{ IEC958_AES1_CON_NON_IEC908_CD, "non-IEC908 CD" },
+	{ IEC958_AES1_CON_MINI_DISC, "Mini-Disc" },
+	{ IEC958_AES1_CON_DVD, "DVD" },
+
+	{ IEC958_AES1_CON_PCM_CODER, "PCM coder" },
+	{ IEC958_AES1_CON_MIXER, "digital signal mixer" },
+	{ IEC958_AES1_CON_RATE_CONVERTER, "rate converter" },
+	{ IEC958_AES1_CON_SAMPLER, "sampler" },
+	{ IEC958_AES1_CON_DSP, "digital sound processor" },
+
+	{ IEC958_AES1_CON_DAT, "DAT" },
+	{ IEC958_AES1_CON_VCR, "VCR" },
+	{ IEC958_AES1_CON_DCC, "DCC" },
+	{ IEC958_AES1_CON_MAGNETIC_DISC, "magnetic disc" },
+
+	{ IEC958_AES1_CON_DAB_JAPAN, "digital audio broadcast (Japan)" },
+	{ IEC958_AES1_CON_DAB_EUROPE, "digital audio broadcast (Europe)" },
+	{ IEC958_AES1_CON_DAB_USA, "digital audio broadcast (USA)" },
+	{ IEC958_AES1_CON_SOFTWARE, "software delivery" },
+
+	{ IEC958_AES1_CON_SYNTHESIZER, "synthesizer" },
+	{ IEC958_AES1_CON_MICROPHONE, "microphone" },
+
+	{ IEC958_AES1_CON_ADC, "ADC without copyright information" },
+
+	{ IEC958_AES1_CON_ADC_COPYRIGHT, "ADC with copyright information" },
+
+	{ IEC958_AES1_CON_SOLIDMEM_DIGITAL_RECORDER_PLAYER, "flash memory recorder/player" },
+
+	{ IEC958_AES1_CON_EXPERIMENTAL, "experimental" },
+};
+
+
+#define ARRAY_SIZE(x) (int)(sizeof(x)/sizeof(x[0]))
+
+void dump_iec958(snd_aes_iec958_t *iec)
+{
+	int i;
+
+	if (! (iec->status[0] & IEC958_AES0_PROFESSIONAL)) {
+		/* consumer */
+		printf("Mode: consumer\n");
+		printf("Data: ");
+		if (!(iec->status[0] & IEC958_AES0_NONAUDIO)) {
+			printf("audio\n");
+		} else {
+			printf("non-audio\n");
+		}
+		printf("Rate: ");
+		switch (iec->status[3] & IEC958_AES3_CON_FS) {
+		case IEC958_AES3_CON_FS_22050:
+			printf("22050 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_24000:
+			printf("24000 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_32000:
+			printf("32000 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_44100:
+			printf("44100 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_48000:
+			printf("48000 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_88200:
+			printf("88200 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_96000:
+			printf("96000 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_176400:
+			printf("176400 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_192000:
+			printf("192000 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_768000:
+			printf("768000 Hz\n");
+			break;
+		case IEC958_AES3_CON_FS_NOTID:
+			printf("not indicated\n");
+			break;
+		default:
+			printf("unknown\n");
+			break;
+		}
+		printf("Copyright: ");
+		if (iec->status[0] & IEC958_AES0_CON_NOT_COPYRIGHT) {
+			printf("permitted\n");
+		} else {
+			printf("protected\n");
+		}
+		printf("Emphasis: ");
+		if ((iec->status[0] & IEC958_AES0_CON_EMPHASIS) != IEC958_AES0_CON_EMPHASIS_5015) {
+			printf("none\n");
+		} else {
+			printf("50/15us\n");
+		}
+		printf("Category: ");
+		for (i = 0; i < ARRAY_SIZE(con_category); i++) {
+			if ((iec->status[1] & IEC958_AES1_CON_CATEGORY) == con_category[i].val) {
+				printf("%s\n", con_category[i].name);
+				break;
+			}
+		}
+		if (i >= ARRAY_SIZE(con_category)) {
+			printf("unknown 0x%x\n", iec->status[1] & IEC958_AES1_CON_CATEGORY);
+		}
+		printf("Original: ");
+		if (iec->status[1] & IEC958_AES1_CON_ORIGINAL) {
+			printf("original\n");
+		} else {
+			printf("1st generation\n");
+		}
+		printf("Clock: ");
+		switch (iec->status[3] & IEC958_AES3_CON_CLOCK) {
+		case IEC958_AES3_CON_CLOCK_1000PPM:
+			printf("1000 ppm\n");
+			break;
+		case IEC958_AES3_CON_CLOCK_50PPM:
+			printf("50 ppm\n");
+			break;
+		case IEC958_AES3_CON_CLOCK_VARIABLE:
+			printf("variable pitch\n");
+			break;
+		default:
+			printf("unknown\n");
+			break;
+		}
+	} else {
+		printf("Mode: professional\n");
+		printf("Data: ");
+		if (!(iec->status[0] & IEC958_AES0_NONAUDIO)) {
+			printf("audio\n");
+		} else {
+			printf("non-audio\n");
+		}
+		printf("Rate: ");
+		switch (iec->status[0] & IEC958_AES0_PRO_FS) {
+		case IEC958_AES0_PRO_FS_44100:
+			printf("44100 Hz\n");
+			break;
+		case IEC958_AES0_PRO_FS_48000:
+			printf("48000 Hz\n");
+			break;
+		case IEC958_AES0_PRO_FS_32000:
+			printf("32000 Hz\n");
+			break;
+		default:
+			printf("unknown\n");
+			break;
+		}
+		printf("Rate Locked: ");
+		if (iec->status[0] & IEC958_AES0_PRO_FREQ_UNLOCKED)
+			printf("no\n");
+		else
+			printf("yes\n");
+		printf("Emphasis: ");
+		switch (iec->status[0] & IEC958_AES0_PRO_EMPHASIS) {
+		case IEC958_AES0_PRO_EMPHASIS_CCITT:
+			printf("CCITT J.17\n");
+			break;
+		case IEC958_AES0_PRO_EMPHASIS_NONE:
+			printf("none\n");
+			break;
+		case IEC958_AES0_PRO_EMPHASIS_5015:
+			printf("50/15us\n");
+			break;
+		case IEC958_AES0_PRO_EMPHASIS_NOTID:
+		default:
+			printf("unknown\n");
+			break;
+		}
+		printf("Stereophonic: ");
+		if ((iec->status[1] & IEC958_AES1_PRO_MODE) == IEC958_AES1_PRO_MODE_STEREOPHONIC) {
+			printf("stereo\n");
+		} else {
+			printf("not indicated\n");
+		}
+		printf("Userbits: ");
+		switch (iec->status[1] & IEC958_AES1_PRO_USERBITS) {
+		case IEC958_AES1_PRO_USERBITS_192:
+			printf("192bit\n");
+			break;
+		case IEC958_AES1_PRO_USERBITS_UDEF:
+			printf("user-defined\n");
+			break;
+		default:
+			printf("unkown\n");
+			break;
+		}
+		printf("Sample Bits: ");
+		switch (iec->status[2] & IEC958_AES2_PRO_SBITS) {
+		case IEC958_AES2_PRO_SBITS_20:
+			printf("20 bit\n");
+			break;
+		case IEC958_AES2_PRO_SBITS_24:
+			printf("24 bit\n");
+			break;
+		case IEC958_AES2_PRO_SBITS_UDEF:
+			printf("user defined\n");
+			break;
+		default:
+			printf("unknown\n");
+			break;
+		}
+		printf("Word Length: ");
+		switch (iec->status[2] & IEC958_AES2_PRO_WORDLEN) {
+		case IEC958_AES2_PRO_WORDLEN_22_18:
+			printf("22 bit or 18 bit\n");
+			break;
+		case IEC958_AES2_PRO_WORDLEN_23_19:
+			printf("23 bit or 19 bit\n");
+			break;
+		case IEC958_AES2_PRO_WORDLEN_24_20:
+			printf("24 bit or 20 bit\n");
+			break;
+		case IEC958_AES2_PRO_WORDLEN_20_16:
+			printf("20 bit or 16 bit\n");
+			break;
+		default:
+			printf("unknown\n");
+			break;
+		}
+	}
+}
+
diff --git a/iecset/iecset.1 b/iecset/iecset.1
new file mode 100644
index 0000000..dec4ac0
--- /dev/null
+++ b/iecset/iecset.1
@@ -0,0 +1,104 @@
+.TH iecset 1 "23 Oct 2003"
+.SH NAME
+iecset \- Set or dump IEC958 status bits
+
+.SH SYNOPSIS
+\fBiecset\fP [\fIoptions\fP] [\fIcmd\fP \fIarg\fP...]
+
+.SH DESCRIPTION
+\fBiecset\fP is a small utility to set or dump the IEC958 (or so-called
+"S/PDIF") status bits of the specified sound card via ALSA control API.
+
+When \fBiecset\fP is started without arguments except for options,
+it will show the current IEC958 status in a human-readable form.
+When the commands are given in the arguments, they are parsed
+and the IEC958 status bits are updated.  The resultant status is
+shown as well.
+
+The commands consist of the command directive and the argument.
+As the boolean argument, \fIyes\fP, \fIno\fP, \fItrue\fP, \fIfalse\fP,
+or a digit number is allowed.
+
+.SH EXAMPLES
+.TP
+.BI iecset\ \-Dhw:1
+Displays the current IEC958 status bits on the second card.
+This is equivalent with \fI\-c 1\fP.
+.TP
+.BI iecset\ \-x
+Displays the current IEC958 status bits in a style of the arguments
+for the PCM stream.  The output string can be passed to the \fIiec958\fP
+(or \fIspdif\fP) PCM type as the optional argument.
+.TP
+.BI iecset\ pro\ off\ audio\ off
+Sets the current status to the consumer-mode and turns on the
+non-audio bit.  The modified status will be shown, too.
+
+.SH OPTIONS
+.TP
+\fI\-D\fP device
+Specifies the device name of the control to open
+.TP
+\fI\-c\fP card
+Specifies the card index to open.  Equivalent with \fI\-Dhw:x\fP.
+.TP
+\fI\-n\fP index
+Specifies the IEC958 control element index, in case you have multiple
+IEC958 devices and need to choose one of them.
+.TP
+\fI\-x\fP
+Dumps the status in the form of AESx bytes.
+.TP
+\fI\-i\fP
+Reads the command sequences from stdin.
+Each line has single command.
+
+.SH COMMANDS
+.TP
+\fIprofessional\fP <bool>
+The professional mode (true) or consumer mode (false).
+
+.TP
+\fIaudio\fP <bool>
+The audio mode (true) or non-audio mode (false).
+
+.TP
+\fIrate\fP <int>
+The sample rate in Hz.
+
+.TP
+\fIemphasis\fP <int>
+The emphasis: 0 = none, 1 = 50/15us, 2 = CCITT.
+
+.TP
+\fIlock\fP <bool>
+Rate lock: locked (true), unlocked (false).
+This command is for the professional mode only.
+
+.TP
+\fIsbits\fP <int>
+Sample bits:  2 = 20bit, 4 = 24bit, 6 = undefined.
+This command is for the professional mode only.
+
+.TP
+\fIwordlength\fP <int>
+Wordlength: 0 = No, 2 = 22-18 bit, 4 = 23-19 bit, 5 = 24-20 bit, 6 = 20-16 bit.
+This command is for the professional mode only.
+
+.TP
+\fIcategory\fP <int>
+Category: the value is from 0 to 0x7f.
+This command is for the consumer mode only.
+
+.TP
+\fIcopyright\fP <bool>
+Copyright: copyrighted (true), non-copyrighted (false).
+This command is for the consumer mode only.
+
+.TP
+\fIoriginal\fP <boo>
+Original flag: original (true), 1st generation (false).
+This command is for the consumer mode only.
+
+.SH AUTHOR
+Takashi Iwai <tiwai@suse.de>
diff --git a/iecset/iecset.c b/iecset/iecset.c
new file mode 100644
index 0000000..92a93e8
--- /dev/null
+++ b/iecset/iecset.c
@@ -0,0 +1,432 @@
+/*
+   iecset - change IEC958 status bits on ALSA
+   Copyright (C) 2003 by Takashi Iwai <tiwai@suse.de>
+
+   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 <ctype.h>
+#include <alsa/asoundlib.h>
+
+void dump_iec958(snd_aes_iec958_t *iec);
+
+static int get_bool(const char *str)
+{
+	if (strncmp(str, "yes", 3) == 0 ||
+	    strncmp(str, "YES", 3) == 0 ||
+	    strncmp(str, "on", 2) == 0 ||
+	    strncmp(str, "ON", 2) == 0 ||
+	    strncmp(str, "true", 4) == 0 ||
+	    strncmp(str, "TRUE", 4) == 0 ||
+	    *str == '1')
+		return 1;
+	return 0;
+}
+
+enum {
+	CMD_BOOL, CMD_BOOL_INV, CMD_INT
+};
+
+enum {
+	IDX_PRO, IDX_NOAUDIO, IDX_RATE, IDX_UNLOCK, IDX_SBITS, IDX_WORD, IDX_EMP, IDX_CAT, IDX_NOCOPY, IDX_ORIG,
+	IDX_LAST
+};
+
+struct cmdtbl {
+	const char *name;
+	int idx;
+	int type;
+	const char *desc;
+};
+
+static const struct cmdtbl cmds[] = {
+	{ "pro", IDX_PRO, CMD_BOOL,
+	  "professional (common)\n\toff = consumer mode, on = professional mode" },
+	{ "aud", IDX_NOAUDIO, CMD_BOOL_INV,
+	  "audio (common)\n\ton = audio mode, off = non-audio mode" },
+	{ "rat", IDX_RATE, CMD_INT,
+	  "rate (common)\n\tsample rate in Hz (0 = not indicated)" },
+	{ "emp", IDX_EMP, CMD_INT,
+	  "emphasis (common)\n\t0 = none, 1 = 50/15us, 2 = CCITT" },
+	{ "loc", IDX_UNLOCK, CMD_BOOL_INV,
+	  "lock (prof.)\n\toff = rate unlocked, on = rate locked" },
+	{ "sbi", IDX_SBITS, CMD_INT,
+	  "sbits (prof.)\n\tsample bits 2 = 20bit, 4 = 24bit, 6 = undef" },
+	{ "wor", IDX_WORD, CMD_INT,
+	  "wordlength (prof.)\n\t0=no, 2=22-18bit, 4=23-19bit, 5=24-20bit, 6=20-16bit" },
+	{ "cat", IDX_CAT, CMD_INT,
+	  "category (consumer)\n\t0-0x7f" },
+	{ "cop", IDX_NOCOPY, CMD_BOOL_INV,
+	  "copyright (consumer)\n\toff = non-copyright, on = copyright" },
+	{ "ori", IDX_ORIG, CMD_BOOL,
+	  "original (consumer)\n\toff = 1st-gen, on = original" },
+};
+
+
+static void error(const char *s, int err)
+{
+	fprintf(stderr, "%s: %s\n", s, snd_strerror(err));
+}
+
+
+static void usage(void)
+{
+	int i;
+
+	printf("Usage: iecset [options] [cmd arg...]\n");
+	printf("Options:\n");
+	printf("    -D device   specifies the control device to use\n");
+	printf("    -c card     specifies the card number to use (equiv. with -Dhw:#)\n");
+	printf("    -n number   specifies the control index number (default = 0)\n");
+	printf("    -x          dump the dump the AESx hex code for IEC958 PCM parameters\n");
+	printf("    -i          read commands from stdin\n");
+	printf("Commands:\n");
+	for (i = 0; i < (int)(sizeof(cmds)/sizeof(cmds[0])); i++) {
+		printf("    %s\n", cmds[i].desc);
+	}
+}
+
+
+/*
+ * parse iecset commands
+ */
+static void parse_command(int *parms, const char *c, const char *arg)
+{
+	int i;
+
+	for (i = 0; i < (int)(sizeof(cmds)/sizeof(cmds[0])); i++) {
+		if (strncmp(c, cmds[i].name, strlen(cmds[i].name)) == 0) {
+			int val;
+			switch (cmds[i].type) {
+			case CMD_BOOL:
+				val = get_bool(arg);
+				break;
+			case CMD_BOOL_INV:
+				val = !get_bool(arg);
+				break;
+			case CMD_INT:
+			default:
+				val = (int)strtol(arg, NULL, 0);
+				break;
+			}
+			parms[cmds[i].idx] = val;
+			return;
+		}
+	}
+}
+
+static char *skipspace(char *line)
+{
+	char *p;
+	for (p = line; *p && isspace(*p); p++)
+		;
+	return p;
+}
+
+/*
+ * parse iecset commands from the file
+ */
+static void parse_file(int *parms, FILE *fp)
+{
+	char line[1024], *cmd, *arg;
+	while (fgets(line, sizeof(line), fp) != NULL) {
+		cmd = skipspace(line);
+		if (*cmd == '#' || ! *cmd)
+			continue;
+		for (arg = cmd; *arg && !isspace(*arg); arg++)
+			;
+		if (! *arg)
+			continue;
+		*arg++ = 0;
+		arg = skipspace(arg);
+		if (! *arg)
+			continue;
+		parse_command(parms, cmd, arg);
+	}
+}
+
+/* update iec958 status values
+ * return non-zero if the values are modified
+ */
+static int update_iec958_status(snd_aes_iec958_t *iec958, int *parms)
+{
+	int changed = 0;
+	if (parms[IDX_PRO] >= 0) {
+		if (parms[IDX_PRO])
+			iec958->status[0] |= IEC958_AES0_PROFESSIONAL;
+		else
+			iec958->status[0] &= ~IEC958_AES0_PROFESSIONAL;
+		changed = 1;
+	}
+	if (parms[IDX_NOAUDIO] >= 0) {
+		if (parms[IDX_NOAUDIO])
+			iec958->status[0] |= IEC958_AES0_NONAUDIO;
+		else
+			iec958->status[0] &= ~IEC958_AES0_NONAUDIO;
+		changed = 1;
+	}
+	if (parms[IDX_RATE] >= 0) {
+		if (iec958->status[0] & IEC958_AES0_PROFESSIONAL) {
+			iec958->status[0] &= ~IEC958_AES0_PRO_FS;
+			switch (parms[IDX_RATE]) {
+			case 44100:
+				iec958->status[0] |= IEC958_AES0_PRO_FS_44100;
+				break;
+			case 48000:
+				iec958->status[0] |= IEC958_AES0_PRO_FS_48000;
+				break;
+			case 32000:
+				iec958->status[0] |= IEC958_AES0_PRO_FS_32000;
+				break;
+			}
+		} else {
+			iec958->status[3] &= ~IEC958_AES3_CON_FS;
+			switch (parms[IDX_RATE]) {
+			case 22050:
+				iec958->status[3] |= IEC958_AES3_CON_FS_22050;
+				break;
+			case 24000:
+				iec958->status[3] |= IEC958_AES3_CON_FS_24000;
+				break;
+			case 32000:
+				iec958->status[3] |= IEC958_AES3_CON_FS_32000;
+				break;
+			case 44100:
+				iec958->status[3] |= IEC958_AES3_CON_FS_44100;
+				break;
+			case 48000:
+				iec958->status[3] |= IEC958_AES3_CON_FS_48000;
+				break;
+			case 88200:
+				iec958->status[3] |= IEC958_AES3_CON_FS_88200;;
+				break;
+			case 96000:
+				iec958->status[3] |= IEC958_AES3_CON_FS_96000;
+				break;
+			case 176400:
+				iec958->status[3] |= IEC958_AES3_CON_FS_176400;
+				break;
+			case 192000:
+				iec958->status[3] |= IEC958_AES3_CON_FS_192000;
+				break;
+			case 768000:
+				iec958->status[3] |= IEC958_AES3_CON_FS_768000;
+				break;
+			default:
+				iec958->status[3] |= IEC958_AES3_CON_FS_NOTID;
+				break;
+			}
+		}
+		changed = 1;
+	}
+	if (parms[IDX_NOCOPY] >= 0) {
+		if (! (iec958->status[0] & IEC958_AES0_PROFESSIONAL)) {
+			if (parms[IDX_NOCOPY])
+				iec958->status[0] |= IEC958_AES0_CON_NOT_COPYRIGHT;
+			else
+				iec958->status[0] &= ~IEC958_AES0_CON_NOT_COPYRIGHT;
+		}
+		changed = 1;
+	}
+	if (parms[IDX_ORIG] >= 0) {
+		if (! (iec958->status[0] & IEC958_AES0_PROFESSIONAL)) {
+			if (parms[IDX_ORIG])
+				iec958->status[1] |= IEC958_AES1_CON_ORIGINAL;
+			else
+				iec958->status[1] &= ~IEC958_AES1_CON_ORIGINAL;
+		}
+		changed = 1;
+	}
+	if (parms[IDX_EMP] >= 0) {
+		if (iec958->status[0] & IEC958_AES0_PROFESSIONAL) {
+			iec958->status[0] &= ~IEC958_AES0_PRO_EMPHASIS;
+			switch (parms[IDX_EMP]) {
+			case 0:
+				iec958->status[0] |= IEC958_AES0_PRO_EMPHASIS_NONE;
+				break;
+			case 1:
+				iec958->status[0] |= IEC958_AES0_PRO_EMPHASIS_5015;
+				break;
+			case 2:
+				iec958->status[0] |= IEC958_AES0_PRO_EMPHASIS_CCITT;
+				break;
+			}
+		} else {
+			if (parms[IDX_EMP])
+				iec958->status[0] |= IEC958_AES0_CON_EMPHASIS_5015;
+			else
+				iec958->status[0] &= ~IEC958_AES0_CON_EMPHASIS_5015;
+		}
+		changed = 1;
+	}
+	if (parms[IDX_UNLOCK] >= 0) {
+		if (iec958->status[0] & IEC958_AES0_PROFESSIONAL) {
+			if (parms[IDX_UNLOCK])
+				iec958->status[0] |= IEC958_AES0_PRO_FREQ_UNLOCKED;
+			else
+				iec958->status[0] &= ~IEC958_AES0_PRO_FREQ_UNLOCKED;
+		}
+		changed = 1;
+	}
+	if (parms[IDX_SBITS] >= 0) {
+		if (iec958->status[0] & IEC958_AES0_PROFESSIONAL) {
+			iec958->status[2] &= ~IEC958_AES2_PRO_SBITS;
+			iec958->status[2] |= parms[IDX_SBITS] & 7;
+		}
+		changed = 1;
+	}
+	if (parms[IDX_WORD] >= 0) {
+		if (iec958->status[0] & IEC958_AES0_PROFESSIONAL) {
+			iec958->status[2] &= ~IEC958_AES2_PRO_WORDLEN;
+			iec958->status[2] |= (parms[IDX_WORD] & 7) << 3;
+		}
+		changed = 1;
+	}
+	if (parms[IDX_CAT] >= 0) {
+		if (! (iec958->status[0] & IEC958_AES0_PROFESSIONAL)) {
+			iec958->status[1] &= ~IEC958_AES1_CON_CATEGORY;
+			iec958->status[1] |= parms[IDX_CAT] & 0x7f;
+		}
+		changed = 1;
+	}
+
+	return changed;
+}
+		
+
+int main(int argc, char **argv)
+{
+	const char *dev = "default";
+	const char *spdif_str = SND_CTL_NAME_IEC958("", PLAYBACK, DEFAULT);
+	int spdif_index = -1;
+	snd_ctl_t *ctl;
+	snd_ctl_elem_list_t *clist;
+	snd_ctl_elem_id_t *cid;
+	snd_ctl_elem_value_t *cval;
+	snd_aes_iec958_t iec958;
+	int from_stdin = 0;
+	int dumphex = 0;
+	int i, c, err;
+	unsigned int controls, cidx;
+	char tmpname[32];
+	int parms[IDX_LAST];
+
+	for (i = 0; i < IDX_LAST; i++)
+		parms[i] = -1; /* not set */
+
+	while ((c = getopt(argc, argv, "D:c:n:xhi")) != -1) {
+		switch (c) {
+		case 'D':
+			dev = optarg;
+			break;
+		case 'c':
+			i = atoi(optarg);
+			if (i < 0 || i >= 32) {
+				fprintf(stderr, "invalid card index %d\n", i);
+				return 1;
+			}
+			sprintf(tmpname, "hw:%d", i);
+			dev = tmpname;
+			break;
+		case 'n':
+			spdif_index = atoi(optarg);
+			break;
+		case 'x':
+			dumphex = 1;
+			break;
+		case 'i':
+			from_stdin = 1;
+			break;
+		default:
+			usage();
+			return 1;
+		}
+	}
+
+	if ((err = snd_ctl_open(&ctl, dev, 0)) < 0) {
+		error("snd_ctl_open", err);
+		return 1;
+	}
+
+	snd_ctl_elem_list_alloca(&clist);
+	if ((err = snd_ctl_elem_list(ctl, clist)) < 0) {
+		error("snd_ctl_elem_list", err);
+		return 1;
+	}
+	if ((err = snd_ctl_elem_list_alloc_space(clist, snd_ctl_elem_list_get_count(clist))) < 0) {
+		error("snd_ctl_elem_list_alloc_space", err);
+		return 1;
+	}
+	if ((err = snd_ctl_elem_list(ctl, clist)) < 0) {
+		error("snd_ctl_elem_list", err);
+		return 1;
+	}
+
+	controls = snd_ctl_elem_list_get_used(clist);
+	for (cidx = 0; cidx < controls; cidx++) {
+		if (!strcmp(snd_ctl_elem_list_get_name(clist, cidx), spdif_str))
+			if (spdif_index < 0 ||
+			    snd_ctl_elem_list_get_index(clist, cidx) == spdif_index)
+				break;
+	}
+	if (cidx >= controls) {
+		fprintf(stderr, "control \"%s\" (index %d) not found\n",
+			spdif_str, spdif_index);
+		return 1;
+	}
+
+	snd_ctl_elem_id_alloca(&cid);
+	snd_ctl_elem_list_get_id(clist, cidx, cid);
+	snd_ctl_elem_value_alloca(&cval);
+	snd_ctl_elem_value_set_id(cval, cid);
+	if ((err = snd_ctl_elem_read(ctl, cval)) < 0) {
+		error("snd_ctl_elem_read", err);
+		return 1;
+	}
+
+	snd_ctl_elem_value_get_iec958(cval, &iec958);
+
+	/* parse from stdin */
+	if (from_stdin)
+		parse_file(parms, stdin);
+
+	/* parse commands */
+	for (c = optind; c < argc - 1; c += 2)
+		parse_command(parms, argv[c], argv[c + 1]);
+
+	if (update_iec958_status(&iec958, parms)) {
+		/* store the values */
+		snd_ctl_elem_value_set_iec958(cval, &iec958);
+		if ((err = snd_ctl_elem_write(ctl, cval)) < 0) {
+			error("snd_ctl_elem_write", err);
+			return 1;
+		}
+		if ((err = snd_ctl_elem_read(ctl, cval)) < 0) {
+			error("snd_ctl_elem_write", err);
+			return 1;
+		}
+		snd_ctl_elem_value_get_iec958(cval, &iec958);
+	}
+
+	if (dumphex)
+		printf("AES0=0x%02x,AES1=0x%02x,AES2=0x%02x,AES3=0x%02x\n",
+		       iec958.status[0], iec958.status[1], iec958.status[2], iec958.status[3]);
+	else
+		dump_iec958(&iec958);
+
+	snd_ctl_close(ctl);
+	return 0;
+}
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..7a3968d
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,25 @@
+noinst_HEADERS=version.h gettext.h gettext_curses.h
+
+version.h: stamp-vh
+	@:
+
+stamp-vh: $(top_builddir)/configure.in
+	@echo "/*" > ver.tmp
+	@echo " *  version.h" >> ver.tmp
+	@echo " */" >> ver.tmp
+	@echo "" >> ver.tmp
+	@echo "#define SND_UTIL_MAJOR		$(SND_UTIL_MAJOR)" >> ver.tmp
+	@echo "#define SND_UTIL_MINOR		$(SND_UTIL_MINOR)" >> ver.tmp
+	@echo "#define SND_UTIL_SUBMINOR	$(SND_UTIL_SUBMINOR)" >> ver.tmp
+	@echo "#define SND_UTIL_VERSION		((SND_UTIL_MAJOR<<16)|\\" >> ver.tmp
+	@echo "				 (SND_UTIL_MINOR<<8)|\\" >> ver.tmp
+	@echo "				  SND_UTIL_SUBMINOR)" >> ver.tmp
+	@echo "#define SND_UTIL_VERSION_STR	\"$(SND_UTIL_VERSION)\"" >> ver.tmp
+	@echo >> ver.tmp
+	@cmp -s version.h ver.tmp \
+          || (echo "Updating version.h"; \
+              cp ver.tmp version.h; \
+              echo timestamp > stamp-vh)
+	-@rm -f ver.tmp
+
+INCLUDES=-I$(top_srcdir)/include
diff --git a/include/gettext.h b/include/gettext.h
new file mode 100644
index 0000000..d8a1467
--- /dev/null
+++ b/include/gettext.h
@@ -0,0 +1,22 @@
+#ifndef __MY_GETTEXT_H
+#define __MY_GETTEXT_H
+
+#ifdef USES_CURSES
+#define ENABLE_NLS_TEST ENABLE_NLS_IN_CURSES
+#else
+#define ENABLE_NLS_TEST ENABLE_NLS
+#endif
+
+#if ENABLE_NLS_TEST
+# include <libintl.h>
+#else
+# define gettext(msgid) (msgid)
+# define textdomain(domain)
+# define bindtextdomain(domain, dir)
+#endif
+
+#define _(msgid) gettext (msgid)
+#define gettext_noop(msgid) msgid
+#define N_(msgid) gettext_noop (msgid)
+
+#endif /* __MY_GETTEXT_H */
diff --git a/include/gettext_curses.h b/include/gettext_curses.h
new file mode 100644
index 0000000..f1c4041
--- /dev/null
+++ b/include/gettext_curses.h
@@ -0,0 +1,2 @@
+#define USES_CURSES
+#include "gettext.h"
diff --git a/lib/udev/rules.d/90-alsa-restore.rules b/lib/udev/rules.d/90-alsa-restore.rules
deleted file mode 100644
index 51ee299..0000000
--- a/lib/udev/rules.d/90-alsa-restore.rules
+++ /dev/null
@@ -1,2 +0,0 @@
-ACTION=="add", SUBSYSTEM=="sound", KERNEL=="controlC*", KERNELS=="card*", \
-        RUN+="/usr/sbin/alsactl restore $attr{number}"
diff --git a/m4/Makefile.am b/m4/Makefile.am
new file mode 100644
index 0000000..0498b21
--- /dev/null
+++ b/m4/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST = gettext.m4
diff --git a/po/LINGUAS b/po/LINGUAS
new file mode 100644
index 0000000..2027386
--- /dev/null
+++ b/po/LINGUAS
@@ -0,0 +1 @@
+de fr ja
diff --git a/po/Makevars b/po/Makevars
new file mode 100644
index 0000000..779d8ac
--- /dev/null
+++ b/po/Makevars
@@ -0,0 +1,41 @@
+# Makefile variables for PO directory in any package using GNU gettext.
+
+# Usually the message domain is the same as the package name.
+DOMAIN = $(PACKAGE)
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+# These options get passed to xgettext.
+XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
+
+# This is the copyright holder that gets inserted into the header of the
+# $(DOMAIN).pot file.  Set this to the copyright holder of the surrounding
+# package.  (Note that the msgstr strings, extracted from the package's
+# sources, belong to the copyright holder of the package.)  Translators are
+# expected to transfer the copyright for their translations to this person
+# or entity, or to disclaim their copyright.  The empty string stands for
+# the public domain; in this case the translators are expected to disclaim
+# their copyright.
+COPYRIGHT_HOLDER = The ALSA Team
+
+# This is the email address or URL to which the translators shall report
+# bugs in the untranslated strings:
+# - Strings which are not entire sentences, see the maintainer guidelines
+#   in the GNU gettext documentation, section 'Preparing Strings'.
+# - Strings which use unclear terms or require additional context to be
+#   understood.
+# - Strings which make invalid assumptions about notation of date, time or
+#   money.
+# - Pluralisation problems.
+# - Incorrect English spelling.
+# - Incorrect formatting.
+# It can be your email address, or a mailing list address where translators
+# can write to without being subscribed, or the URL of a web page through
+# which the translators can contact you.
+MSGID_BUGS_ADDRESS =
+
+# This is the list of locale categories, beyond LC_MESSAGES, for which the
+# message catalogs shall be used.  It is usually empty.
+EXTRA_LOCALE_CATEGORIES =
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..3d1b8ea
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,12 @@
+alsamixer/card_select.c
+alsamixer/cli.c
+alsamixer/device_name.c
+alsamixer/die.c
+alsamixer/mixer_display.c
+alsamixer/mixer_widget.c
+alsamixer/proc_files.c
+alsamixer/textbox.c
+aplay/aplay.c
+seq/aconnect/aconnect.c
+seq/aseqnet/aseqnet.c
+speaker-test/speaker-test.c
diff --git a/po/de.po b/po/de.po
new file mode 100644
index 0000000..0c47c65
--- /dev/null
+++ b/po/de.po
@@ -0,0 +1,1577 @@
+# German translations for the alsa-utils package.
+# Copyright (C) 2009 The ALSA Team
+# This file is distributed under the same license as the alsa-utils package.
+# Clemens Ladisch <clemens@ladisch.de>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: alsa-utils 1.0.23\n"
+"Report-Msgid-Bugs-To: \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"
+"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 "Soundkarte"
+
+#: alsamixer/card_select.c:181
+msgid "(default)"
+msgstr "(Standard)"
+
+#: alsamixer/card_select.c:191
+msgid "cannot enumerate sound cards"
+msgstr "Fehler beim Aufzählen der Soundkarten"
+
+#: alsamixer/card_select.c:215
+msgid "enter device name..."
+msgstr "Gerätenamen eingeben..."
+
+#: alsamixer/cli.c:40
+msgid "Usage: alsamixer [options]"
+msgstr "Verwendung: alsamixer [Optionen]"
+
+#: 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 ""
+"Optionen:\n"
+"  -h, --help              Hilfe\n"
+"  -c, --card=NUMMER       Soundkarten-Nummer oder -ID\n"
+"  -D, --device=NAME       Mixer-Gerätename\n"
+"  -V, --view=MODUS        Ansicht beim Starten: playback=Wiedergabe, "
+"capture=Aufnahme, all=alle"
+
+#: 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 ""
+"Debugging-Optionen:\n"
+"  -g, --no-color          keine Farben\n"
+"  -a, --abstraction=NAME  Mixer-Abstraktion: none/basic"
+
+#: alsamixer/cli.c:77
+#, c-format
+msgid "invalid card index: %s\n"
+msgstr "ungültige Karten-Nummer: %s\n"
+
+#: alsamixer/cli.c:103
+#, c-format
+msgid "unknown abstraction level: %s\n"
+msgstr "unbekannte Abstraktion: %s\n"
+
+#: alsamixer/cli.c:108
+#, c-format
+msgid "unknown option: %c\n"
+msgstr "unbekannte Option: %c\n"
+
+#: alsamixer/cli.c:110
+msgid "try `alsamixer --help' for more information\n"
+msgstr "siehe `alsamixer --help' für mehr Informationen\n"
+
+#: alsamixer/device_name.c:177
+msgid "Device name:"
+msgstr "Gerätename:"
+
+#: alsamixer/die.c:37
+#, c-format
+msgid "%s: %s\n"
+msgstr "%s: %s\n"
+
+#: alsamixer/mixer_display.c:95
+msgid "Card:"
+msgstr "Gerät:"
+
+#: alsamixer/mixer_display.c:96
+msgid "Chip:"
+msgstr "Chip:"
+
+#: alsamixer/mixer_display.c:97
+msgid "View:"
+msgstr "Ansicht:"
+
+#: alsamixer/mixer_display.c:98
+msgid "Item:"
+msgstr "Element:"
+
+#: alsamixer/mixer_display.c:101
+msgid "F1:  Help"
+msgstr "F1:  Hilfe"
+
+#: alsamixer/mixer_display.c:102
+msgid "F2:  System information"
+msgstr "F2:  System-Informationen"
+
+#: alsamixer/mixer_display.c:103
+msgid "F6:  Select sound card"
+msgstr "F6:  Soundkarte auswählen"
+
+#: alsamixer/mixer_display.c:104
+msgid "Esc: Exit"
+msgstr "Esc: Beenden"
+
+#: alsamixer/mixer_display.c:171
+msgid "(unplugged)"
+msgstr "(entfernt)"
+
+#: alsamixer/mixer_display.c:189
+msgid "Playback"
+msgstr "Wiedergabe"
+
+#: alsamixer/mixer_display.c:190
+msgid "Capture"
+msgstr "Aufnahme"
+
+#: alsamixer/mixer_display.c:191
+msgid "All"
+msgstr "Alle"
+
+#: alsamixer/mixer_display.c:231
+msgid "mute"
+msgstr "stumm"
+
+#: alsamixer/mixer_display.c:272 alsamixer/mixer_display.c:282
+msgid "dB gain:"
+msgstr "dB-Änderung:"
+
+#: 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 "Aus"
+
+#: alsamixer/mixer_display.c:297 alsamixer/mixer_display.c:309
+msgid "On"
+msgstr "An"
+
+#: alsamixer/mixer_display.c:360
+msgid "The sound device was unplugged."
+msgstr "Das Gerät wurde entfernt."
+
+#: alsamixer/mixer_display.c:361
+msgid "Press F6 to select another sound card."
+msgstr "Drücken Sie F6, um eine andere Soundkarte auszuwählen."
+
+#: alsamixer/mixer_display.c:376
+msgid "This sound device does not have any playback controls."
+msgstr "Dieses Gerät hat keine Wiedergabe-Regler."
+
+#: alsamixer/mixer_display.c:378
+msgid "This sound device does not have any capture controls."
+msgstr "Dieses Gerät hat keine Aufnahme-Regler."
+
+#: alsamixer/mixer_display.c:380
+msgid "This sound device does not have any controls."
+msgstr "Dieses Gerät hat keine Regler."
+
+#. 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 "L"
+
+#. TRANSLATORS: "right"; no more than two characters
+#: alsamixer/mixer_display.c:561
+msgid "R"
+msgstr "R"
+
+#. TRANSLATORS: no more than eight characters
+#: alsamixer/mixer_display.c:563
+msgid "CAPTURE"
+msgstr "AUFNAHME"
+
+#: alsamixer/mixer_display.c:613
+msgid "Front"
+msgstr "Vorne"
+
+#: alsamixer/mixer_display.c:616
+msgid "Rear"
+msgstr "Hinten"
+
+#: alsamixer/mixer_display.c:619 speaker-test/speaker-test.c:108
+msgid "Center"
+msgstr "Mitte"
+
+#: alsamixer/mixer_display.c:622
+msgid "Woofer"
+msgstr "Bass"
+
+#: alsamixer/mixer_display.c:625
+msgid "Side"
+msgstr "Seiten"
+
+#: alsamixer/mixer_widget.c:83 alsamixer/mixer_widget.c:88
+msgid "cannot open mixer"
+msgstr "Fehler beim Öffen des Mixer-Gerätes"
+
+#: alsamixer/mixer_widget.c:94 alsamixer/mixer_widget.c:171
+msgid "cannot load mixer controls"
+msgstr "Fehler beim Laden der Mixer-Regler"
+
+#: alsamixer/mixer_widget.c:161
+#, c-format
+msgid "Cannot open mixer device '%s'."
+msgstr "Fehler beim Öffnen des Mixer-Gerätes '%s'."
+
+#: alsamixer/mixer_widget.c:182
+msgid "Esc     Exit"
+msgstr "Esc     Beenden"
+
+#: alsamixer/mixer_widget.c:183
+msgid "F1 ? H  Help"
+msgstr "F1 ? H  Hilfe"
+
+#: alsamixer/mixer_widget.c:184
+msgid "F2 /    System information"
+msgstr "F2 /    System-Informationen"
+
+#: alsamixer/mixer_widget.c:185
+msgid "F3      Show playback controls"
+msgstr "F3      Ansicht Wiedergabe-Regler"
+
+#: alsamixer/mixer_widget.c:186
+msgid "F4      Show capture controls"
+msgstr "F4      Ansicht Aufnahme-Regler"
+
+#: alsamixer/mixer_widget.c:187
+msgid "F5      Show all controls"
+msgstr "F5      Ansicht alle Regler"
+
+#: alsamixer/mixer_widget.c:188
+msgid "Tab     Toggle view mode (F3/F4/F5)"
+msgstr "Tab     Ansichts-Modus umschalten (F3/F4/F5)"
+
+#: alsamixer/mixer_widget.c:189
+msgid "F6 S    Select sound card"
+msgstr "F6 S    Soundkarte auswählen"
+
+#: alsamixer/mixer_widget.c:190
+msgid "L       Redraw screen"
+msgstr "L       Bildschirm neu darstellen"
+
+#: alsamixer/mixer_widget.c:192
+msgid "Left    Move to the previous control"
+msgstr "Links   gehe zum vorherigen Regler"
+
+#: alsamixer/mixer_widget.c:193
+msgid "Right   Move to the next control"
+msgstr "Rechts  gehe zum nächsten Regler"
+
+#: alsamixer/mixer_widget.c:195
+msgid "Up/Down    Change volume"
+msgstr "Oben/Unten  Lautstärke ändern"
+
+#: alsamixer/mixer_widget.c:196
+msgid "+ -        Change volume"
+msgstr "+ -         Lautstärke ändern"
+
+#: alsamixer/mixer_widget.c:197
+msgid "Page Up/Dn Change volume in big steps"
+msgstr "Bild ^/v    Lautstärke in großen Schritten ändern"
+
+#: alsamixer/mixer_widget.c:198
+msgid "End        Set volume to 0%"
+msgstr "Ende        Lautstärke auf 0% setzen"
+
+#: alsamixer/mixer_widget.c:199
+msgid "0-9        Set volume to 0%-90%"
+msgstr "0-9         Lautstärke auf 0%-90% setzen"
+
+#: alsamixer/mixer_widget.c:200
+msgid "Q W E      Increase left/both/right volumes"
+msgstr "Q W E       linke/beide/rechte Lautstärke erhöhen"
+
+#. TRANSLATORS: or Y instead of Z
+#: alsamixer/mixer_widget.c:202
+msgid "Z X C      Decrease left/both/right volumes"
+msgstr "Y X C       linke/beide/rechte Lautstärke verringern"
+
+#: alsamixer/mixer_widget.c:203
+msgid "B          Balance left and right volumes"
+msgstr "B           linke und rechte Lautstärke angleichen"
+
+#: alsamixer/mixer_widget.c:205
+msgid "M          Toggle mute"
+msgstr "M           stumm umschalten"
+
+#. TRANSLATORS: or , .
+#: alsamixer/mixer_widget.c:207
+msgid "< >        Toggle left/right mute"
+msgstr ", .         stumm links/rechts umschalten"
+
+#: alsamixer/mixer_widget.c:209
+msgid "Space      Toggle capture"
+msgstr "Leertaste   Aufnahme umschalten"
+
+#. TRANSLATORS: or Insert Delete
+#: alsamixer/mixer_widget.c:211
+msgid "; '        Toggle left/right capture"
+msgstr "Einfg Entf  Aufnahme links/rechts umschalten"
+
+#: alsamixer/mixer_widget.c:213
+msgid "Authors:"
+msgstr "Autoren:"
+
+#: alsamixer/mixer_widget.c:214
+msgid "  Tim Janik"
+msgstr "  Tim Janik"
+
+#: alsamixer/mixer_widget.c:215
+msgid "  Jaroslav Kysela <perex@perex.cz>"
+msgstr "  Jaroslav Kysela <perex@perex.cz>"
+
+#: alsamixer/mixer_widget.c:216
+msgid "  Clemens Ladisch <clemens@ladisch.de>"
+msgstr "  Clemens Ladisch <clemens@ladisch.de>"
+
+#: alsamixer/mixer_widget.c:218
+msgid "Help"
+msgstr "Hilfe"
+
+#: alsamixer/proc_files.c:103
+msgid "Select File"
+msgstr "Datei wählen"
+
+#: alsamixer/textbox.c:52 alsamixer/textbox.c:66
+msgid "Error"
+msgstr "Fehler"
+
+#: alsamixer/textbox.c:80
+#, c-format
+msgid "Cannot open file \"%s\"."
+msgstr "Fehler beim Öffnen der Datei \"%s\"."
+
+#: aplay/aplay.c:152
+msgid "raw data"
+msgstr "Rohdaten"
+
+#: aplay/aplay.c:153
+msgid "VOC"
+msgstr "VOC"
+
+#: aplay/aplay.c:155
+msgid "WAVE"
+msgstr "WAVE"
+
+#: aplay/aplay.c:156
+msgid "Sparc Audio"
+msgstr "Sparc-Audio"
+
+#: aplay/aplay.c:177
+#, 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 ""
+"Verwendung: %s [Option]... [Datei]...\n"
+"\n"
+"-h, --help              Hilfe\n"
+"    --version           Version anzeigen\n"
+"-l, --list-devices      alle Soundkarten und -Geräte auflisten\n"
+"-L, --list-pcms         ALSA-Gerätenamen auflisten\n"
+"-D, --device=NAME       PCM-Gerät wählen\n"
+"-q, --quiet             weniger Programmausgaben\n"
+"-t, --file-type TYP     Dateityp (voc, wav, raw oder au)\n"
+"-c, --channels=#        Kanäle\n"
+"-f, --format=FORMAT     Sample-Format\n"
+"-r, --rate=#            Sample-Rate\n"
+"-d, --duration=#        Beenden nach # Sekunden\n"
+"-M, --mmap              mmap-Modus\n"
+"-N, --nonblock          nonblocking-Modus\n"
+"-F, --period-time=#     Abstand zwischen Interrupts ist # µs\n"
+"-B, --buffer-time=#     Puffer-Länge ist # µs\n"
+"    --period-size=#     Abstand zwischen Interrupts ist # Frames\n"
+"    --buffer-size=#     Puffer-Länge ist # Frames\n"
+"-A, --avail-min=#       freier Pufferspeicher für Wakeup ist # µs\n"
+"-R, --start-delay=#     Puffer-Füllgrad zum automatischen PCM-Start, in µs\n"
+"                        (wenn <= 0, freier Puffer-Speicher)\n"
+"-T, --stop-delay=#      Zeit vor xrun zum automatischen PCM-Stop, in µs\n"
+"-v, --verbose           zeige PCM-Struktur und -Konfiguration (akkumulativ)\n"
+"-V, --vumeter=TYP       VU-Anzeige (TYP: mono oder stereo)\n"
+"-I, --separate-channels eine Datei pro Kanal\n"
+"    --disable-resample  keine automatische Sample-Rate-Anpassung\n"
+"    --disable-channels  keine automatische Kanal-Anzahl-Anpassung\n"
+"    --disable-format    keine automatische Format-Anpassung\n"
+"    --disable-softvol   kein Software-Lautstärke-Regler (softvol)\n"
+"    --test-position     überprüfe Position im Ring-Puffer\n"
+"    --test-coef=#       Test-Koeffizient für Positionsprüfung (Standard 8)\n"
+"                        Formel für Prüfung ist: Koeffizient * (Puffergröße / "
+"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:216 speaker-test/speaker-test.c:819
+#, c-format
+msgid "Recognized sample formats are:"
+msgstr "Unterstützte Sample-Formate:"
+
+#: aplay/aplay.c:222
+#, c-format
+msgid ""
+"\n"
+"Some of these may not be available on selected hardware\n"
+msgstr ""
+"\n"
+"Nicht alle davon sind auf jeder Hardware verfügbar.\n"
+
+#: aplay/aplay.c:223
+#, c-format
+msgid "The availabled format shortcuts are:\n"
+msgstr "Unterstütze Format-Abkürzungen:\n"
+
+#: 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: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: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:240
+msgid "no soundcards found..."
+msgstr "keine Soundkarten gefunden ..."
+
+#: aplay/aplay.c:243
+#, c-format
+msgid "**** List of %s Hardware Devices ****\n"
+msgstr "**** Liste der Hardware-Geräte (%s) ****\n"
+
+#: 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:278
+#, c-format
+msgid "  Subdevices: %i/%i\n"
+msgstr "  Sub-Geräte: %i/%i\n"
+
+#: aplay/aplay.c:285
+#, c-format
+msgid "  Subdevice #%i: %s\n"
+msgstr "  Sub-Gerät #%i: %s\n"
+
+#: aplay/aplay.c:362
+#, c-format
+msgid "Aborted by signal %s...\n"
+msgstr "Abbruch durch Signal %s ...\n"
+
+#: aplay/aplay.c:473
+msgid "command should be named either arecord or aplay"
+msgstr "Befehl sollte arecord oder aplay sein"
+
+#: aplay/aplay.c:512
+#, c-format
+msgid "unrecognized file format %s"
+msgstr "unbekanntes Dateiformat %s"
+
+#: aplay/aplay.c:519
+#, c-format
+msgid "value %i for channels is invalid"
+msgstr "Kanalanzahl %i ist ungültig"
+
+#: aplay/aplay.c:538
+#, c-format
+msgid "wrong extended format '%s'"
+msgstr "erweitertes Format '%s' ist ungültig"
+
+#: aplay/aplay.c:549
+#, c-format
+msgid "bad speed value %i"
+msgstr "ungültige Rate %i"
+
+#: 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:660
+#, c-format
+msgid "audio open error: %s"
+msgstr "Fehler beim Öffnen des Gerätes: %s"
+
+#: aplay/aplay.c:665
+#, c-format
+msgid "info error: %s"
+msgstr "Fehler beim Lesen der Geräteinformationen: %s"
+
+#: aplay/aplay.c:672
+#, c-format
+msgid "nonblock setting error: %s"
+msgstr "Fehler beim Setzen des nonblock-Modus: %s"
+
+#: aplay/aplay.c:682 aplay/aplay.c:806 aplay/aplay.c:1174
+msgid "not enough memory"
+msgstr "nicht genug Speicher"
+
+#: 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: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:864
+#, c-format
+msgid ""
+"unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)"
+msgstr ""
+"unbekannte Länge des erweiterten 'fmt '-Blocks (gelesen: %u, sollte "
+"mindestens %u sein)"
+
+#: aplay/aplay.c:869
+msgid "wrong format tag in extensible 'fmt ' chunk"
+msgstr "ungültiger Format-Wert im erweiterten 'fmt '-Block"
+
+#: 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: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: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:894
+#, c-format
+msgid "Warning: format is changed to S16_LE\n"
+msgstr "Warnung: benutztes Format ist S16_LE\n"
+
+#: 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:908
+#, c-format
+msgid "Warning: format is changed to S24_LE\n"
+msgstr "Warnung: benutztes Format ist S24_LE\n"
+
+#: 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: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:982
+#, c-format
+msgid "Warning: format is changed to MU_LAW\n"
+msgstr "Warnung: benutztes Format ist MU_LAW\n"
+
+#: 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: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: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:1054
+msgid "Access type not available"
+msgstr "Zugriffs-Modus nicht unterstützt"
+
+#: aplay/aplay.c:1059
+msgid "Sample format non available"
+msgstr "Sample-Format nicht unterstützt"
+
+#: aplay/aplay.c:1065
+msgid "Channels count non available"
+msgstr "Kanalanzahl nicht unterstützt"
+
+#: 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:1086
+#, c-format
+msgid "         please, try the plug plugin %s\n"
+msgstr "         probieren Sie bitte das plug-Plugin: %s\n"
+
+#: aplay/aplay.c:1123
+msgid "Unable to install hw params:"
+msgstr "Fehler beim Setzen der Hardware-Parameter:"
+
+#: 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:1161
+msgid "unable to install sw params:"
+msgstr "Fehler beim Setzen der Software-Parameter:"
+
+#: 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:1317 aplay/aplay.c:1328
+#, c-format
+msgid "%s!!! (at least %.3f ms long)\n"
+msgstr "%s!!! (mindestens %.3f ms)\n"
+
+#: aplay/aplay.c:1318 aplay/aplay.c:1321 aplay/aplay.c:1329
+msgid "underrun"
+msgstr "Unterlauf"
+
+#: aplay/aplay.c:1318 aplay/aplay.c:1329
+msgid "overrun"
+msgstr "Überlauf"
+
+#: aplay/aplay.c:1333
+#, c-format
+msgid "Status:\n"
+msgstr "Status:\n"
+
+#: 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:1343
+#, c-format
+msgid "Status(DRAINING):\n"
+msgstr "Status (DRAINING):\n"
+
+#: 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:1349
+#, c-format
+msgid "xrun(DRAINING): prepare error: %s"
+msgstr "XRUN (DRAINING): Fehler beim Re-Initialisieren des Gerätes: %s"
+
+#: aplay/aplay.c:1356
+#, c-format
+msgid "Status(R/W):\n"
+msgstr "Status (R/W):\n"
+
+#: aplay/aplay.c:1359
+#, c-format
+msgid "read/write error, state = %s"
+msgstr "Lese-/Schreibfehler, Status = %s"
+
+#: aplay/aplay.c:1369
+#, c-format
+msgid "Suspended. Trying resume. "
+msgstr "Ruhezustand. Versuche, aufzuwecken. "
+
+#: aplay/aplay.c:1374
+#, c-format
+msgid "Failed. Restarting stream. "
+msgstr "Fehlgeschlagen. Re-Initialisierung. "
+
+#: aplay/aplay.c:1376
+#, c-format
+msgid "suspend: prepare error: %s"
+msgstr "Ruhezustand: Fehler beim Re-Initialisieren: %s"
+
+#: aplay/aplay.c:1381
+#, c-format
+msgid "Done.\n"
+msgstr "Fertig.\n"
+
+#: aplay/aplay.c:1403
+#, c-format
+msgid " !clip  "
+msgstr " !clip  "
+
+#: aplay/aplay.c:1550
+#, c-format
+msgid "Unsupported bit size %d.\n"
+msgstr "%d-Bit-Samples werden nicht unterstützt.\n"
+
+#: aplay/aplay.c:1584
+#, c-format
+msgid "Max peak (%li samples): 0x%08x "
+msgstr "Höchstwert (%li Samples): 0x%08x "
+
+#: aplay/aplay.c:1618
+#, c-format
+msgid ""
+"Suspicious buffer position (%li total): avail = %li, delay = %li, buffer = %"
+"li\n"
+msgstr ""
+"verdächtige Puffer-Position (total %li): avail = %li, delay = %li, buffer = %"
+"li\n"
+
+#: aplay/aplay.c:1682
+#, c-format
+msgid "write error: %s"
+msgstr "Schreibfehler: %s"
+
+#: aplay/aplay.c:1729
+#, c-format
+msgid "writev error: %s"
+msgstr "Vektor-Schreib-Fehler: %s"
+
+#: aplay/aplay.c:1773
+#, c-format
+msgid "read error: %s"
+msgstr "Lesefehler: %s"
+
+#: aplay/aplay.c:1817
+#, c-format
+msgid "readv error: %s"
+msgstr "Vektor-Lese-Fehler: %s"
+
+#: aplay/aplay.c:1865
+msgid "can't allocate buffer for silence"
+msgstr "nicht genug Speicher für Stille-Block"
+
+#: 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:1887
+#, c-format
+msgid "voc_pcm_flush - silence error"
+msgstr "voc_pcm_flush - Fehler in set_silence"
+
+#: aplay/aplay.c:1890
+msgid "voc_pcm_flush error"
+msgstr "Schreibfehler"
+
+#: aplay/aplay.c:1916
+msgid "malloc error"
+msgstr "nicht genug Speicher"
+
+#: 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:1988 aplay/aplay.c:2080
+msgid "can't play packed .voc files"
+msgstr "kann komprimierte .voc-Dateien nicht abspielen"
+
+#: 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:2089
+#, c-format
+msgid "unknown blocktype %d. terminate."
+msgstr "Unbekannter Block-Typ %d. Abbruch."
+
+#: 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:2280
+#, c-format
+msgid "Sparc Audio doesn't support %s format..."
+msgstr "Format %s wird in Sparc-Audio nicht unterstützt ..."
+
+#: aplay/aplay.c:2361
+msgid "Playing"
+msgstr "Wiedergabe:"
+
+#: aplay/aplay.c:2361
+msgid "Recording"
+msgstr "Aufnahme:"
+
+#: aplay/aplay.c:2365
+#, c-format
+msgid "Rate %d Hz, "
+msgstr "Rate: %d Hz, "
+
+#: aplay/aplay.c:2367
+#, c-format
+msgid "Mono"
+msgstr "mono"
+
+#: aplay/aplay.c:2369
+#, c-format
+msgid "Stereo"
+msgstr "stereo"
+
+#: aplay/aplay.c:2371
+#, c-format
+msgid "Channels %i"
+msgstr "%i Kanäle"
+
+#: aplay/aplay.c:2882 aplay/aplay.c:2935
+#, c-format
+msgid "You need to specify %d files"
+msgstr "Es werden %d Dateien benötigt."
+
+#: seq/aconnect/aconnect.c:49
+#, c-format
+msgid "aconnect - ALSA sequencer connection manager\n"
+msgstr "aconnect - ALSA Sequenzer Verbindungs-Manager\n"
+
+#: seq/aconnect/aconnect.c:50
+#, c-format
+msgid "Copyright (C) 1999-2000 Takashi Iwai\n"
+msgstr "Copyright © 1999-2000 Takashi Iwai\n"
+
+#: seq/aconnect/aconnect.c:51
+#, c-format
+msgid "Usage:\n"
+msgstr "Verwendung:\n"
+
+#: seq/aconnect/aconnect.c:52
+#, c-format
+msgid " * Connection/disconnection between two ports\n"
+msgstr " * Verbindung zwischen zwei Ports herstellen/trennen\n"
+
+#: seq/aconnect/aconnect.c:53
+#, c-format
+msgid "   aconnect [-options] sender receiver\n"
+msgstr "   aconnect [Optionen] Sender Empfänger\n"
+
+#: seq/aconnect/aconnect.c:54
+#, c-format
+msgid "     sender, receiver = client:port pair\n"
+msgstr "     Sender, Empfänger = Client:Port\n"
+
+#: seq/aconnect/aconnect.c:55
+#, c-format
+msgid "     -d,--disconnect     disconnect\n"
+msgstr "     -d,--disconnect     Verbindung trennen\n"
+
+#: seq/aconnect/aconnect.c:56
+#, c-format
+msgid "     -e,--exclusive      exclusive connection\n"
+msgstr "     -e,--exclusive      exklusive Verbindung\n"
+
+#: seq/aconnect/aconnect.c:57
+#, c-format
+msgid "     -r,--real #         convert real-time-stamp on queue\n"
+msgstr "     -r,--real #         benutze Zeitstempel der Queue #\n"
+
+#: seq/aconnect/aconnect.c:58
+#, c-format
+msgid "     -t,--tick #         convert tick-time-stamp on queue\n"
+msgstr "     -t,--tick #         benutze Tick-Zeitstempel der Queue #\n"
+
+#: seq/aconnect/aconnect.c:59
+#, c-format
+msgid " * List connected ports (no subscription action)\n"
+msgstr " * Ports und Verbindungen auflisten\n"
+
+#: seq/aconnect/aconnect.c:60
+#, c-format
+msgid "   aconnect -i|-o [-options]\n"
+msgstr "   aconnect -i|-o [Optionen]\n"
+
+#: seq/aconnect/aconnect.c:61
+#, c-format
+msgid "     -i,--input          list input (readable) ports\n"
+msgstr "     -i,--input          Eingabe-Ports (lesbar) auflisten\n"
+
+#: seq/aconnect/aconnect.c:62
+#, c-format
+msgid "     -o,--output         list output (writable) ports\n"
+msgstr "     -o,--output         Ausgabe-Ports (schreibbar) auflisten\n"
+
+#: seq/aconnect/aconnect.c:63
+#, c-format
+msgid "     -l,--list           list current connections of each port\n"
+msgstr "     -l,--list           Verbindungen der Ports auflisten\n"
+
+#: seq/aconnect/aconnect.c:64
+#, c-format
+msgid " * Remove all exported connections\n"
+msgstr " * alle Verbindungen trennen\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 "verbunden zu"
+
+#: seq/aconnect/aconnect.c:133
+msgid "Connected From"
+msgstr "verbunden von"
+
+#: seq/aconnect/aconnect.c:169
+#, c-format
+msgid "client %d: '%s' [type=%s]\n"
+msgstr "Client %d: '%s' [Typ=%s]\n"
+
+#: seq/aconnect/aconnect.c:173
+msgid "user"
+msgstr "User"
+
+#: seq/aconnect/aconnect.c:173
+msgid "kernel"
+msgstr "Kernel"
+
+#: seq/aconnect/aconnect.c:307
+#, c-format
+msgid "can't open sequencer\n"
+msgstr "Fehler beim Öffnen des Sequenzers\n"
+
+#: 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:342
+#, c-format
+msgid "can't set client info\n"
+msgstr "Fehler beim Setzen des Client-Namens\n"
+
+#: seq/aconnect/aconnect.c:349
+#, c-format
+msgid "invalid sender address %s\n"
+msgstr "ungültige Sender-Adresse %s\n"
+
+#: 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:368
+#, c-format
+msgid "No subscription is found\n"
+msgstr "keine Verbindung gefunden\n"
+
+#: seq/aconnect/aconnect.c:373
+#, c-format
+msgid "Disconnection failed (%s)\n"
+msgstr "Verbindungs-Trennung fehlgeschlagen (%s)\n"
+
+#: seq/aconnect/aconnect.c:379
+#, c-format
+msgid "Connection is already subscribed\n"
+msgstr "Verbindung ist bereits vorhanden\n"
+
+#: seq/aconnect/aconnect.c:384
+#, c-format
+msgid "Connection failed (%s)\n"
+msgstr "Verbindung fehlgeschlagen (%s)\n"
+
+#: seq/aseqnet/aseqnet.c:164
+#, c-format
+msgid "aseqnet - network client/server on ALSA sequencer\n"
+msgstr "aseqnet - Netzwerk-Client/Server für ALSA Sequenzer\n"
+
+#: seq/aseqnet/aseqnet.c:165
+#, c-format
+msgid "  Copyright (C) 1999 Takashi Iwai\n"
+msgstr "  Copyright © 1999 Takashi Iwai\n"
+
+#: seq/aseqnet/aseqnet.c:166
+#, c-format
+msgid "usage:\n"
+msgstr "Verwendung:\n"
+
+#: seq/aseqnet/aseqnet.c:167
+#, c-format
+msgid "  server mode: aseqnet [-options]\n"
+msgstr "  Server-Modus: aseqnet [Optionen]\n"
+
+#: seq/aseqnet/aseqnet.c:168
+#, c-format
+msgid "  client mode: aseqnet [-options] server_host\n"
+msgstr "  Client-Modus: aseqnet [Optionen] ServerHost\n"
+
+#: seq/aseqnet/aseqnet.c:169
+#, c-format
+msgid "options:\n"
+msgstr "Optionen:\n"
+
+#: seq/aseqnet/aseqnet.c:170
+#, c-format
+msgid "  -p,--port # : sepcify TCP port (digit or service name)\n"
+msgstr "  -p,--port # : TCP-Port (Zahl oder Service-Name)\n"
+
+#: seq/aseqnet/aseqnet.c:171
+#, c-format
+msgid "  -s,--source addr : read from given addr (client:port)\n"
+msgstr "  -s,--source # : lese von Sequenzer-Port (Client:Port)\n"
+
+#: seq/aseqnet/aseqnet.c:172
+#, c-format
+msgid "  -d,--dest addr : write to given addr (client:port)\n"
+msgstr "  -d,--dest # : schreibe auf Sequenzer-Port (Client:Port)\n"
+
+#: seq/aseqnet/aseqnet.c:173
+#, c-format
+msgid "  -v, --verbose : print verbose messages\n"
+msgstr "  -v,--verbose : ausführliche Meldungen\n"
+
+#: seq/aseqnet/aseqnet.c:174
+#, c-format
+msgid "  -i, --info : print certain received events\n"
+msgstr "  -i,--info : Ausgabe bestimmter empfangener Ereignisse\n"
+
+#: seq/aseqnet/aseqnet.c:188
+#, c-format
+msgid "can't malloc\n"
+msgstr "nicht genug Speicher\n"
+
+#: seq/aseqnet/aseqnet.c:213
+#, c-format
+msgid "closing files..\n"
+msgstr "schließe Dateien ...\n"
+
+#: seq/aseqnet/aseqnet.c:272
+#, c-format
+msgid "sequencer opened: %d:%d\n"
+msgstr "Sequenzer geöffnet: %d:%d\n"
+
+#: seq/aseqnet/aseqnet.c:279
+#, c-format
+msgid "invalid source address %s\n"
+msgstr "ungültige Quell-Adresse %s\n"
+
+#: seq/aseqnet/aseqnet.c:309
+#, c-format
+msgid "service '%s' is not found in /etc/services\n"
+msgstr "Service '%s' in /etc/services nicht gefunden\n"
+
+#: seq/aseqnet/aseqnet.c:377
+#, c-format
+msgid "too many connections!\n"
+msgstr "zu viele Verbindungen\n"
+
+#: seq/aseqnet/aseqnet.c:388
+#, c-format
+msgid "accepted[%d]\n"
+msgstr "angenommen[%d]\n"
+
+#: seq/aseqnet/aseqnet.c:411
+#, c-format
+msgid "can't get address %s\n"
+msgstr "kann Adresse für %s nicht bestimmen\n"
+
+#: seq/aseqnet/aseqnet.c:422
+#, c-format
+msgid "ok.. connected\n"
+msgstr "OK ... verbunden\n"
+
+#: seq/aseqnet/aseqnet.c:518
+#, c-format
+msgid "Channel %2d: Control event : %5d\n"
+msgstr "Channel %2d: Control event : %5d\n"
+
+#: seq/aseqnet/aseqnet.c:522
+#, c-format
+msgid "Channel %2d: Pitchbender   : %5d\n"
+msgstr "Channel %2d: Pitchbender   : %5d\n"
+
+#: seq/aseqnet/aseqnet.c:526
+#, c-format
+msgid "Channel %2d: Note On event : %5d\n"
+msgstr "Channel %2d: Note On evenet : %5d\n"
+
+#: seq/aseqnet/aseqnet.c:530
+#, c-format
+msgid "Channel %2d: Note Off event: %5d\n"
+msgstr "Channel %2d: Note Off event: %5d\n"
+
+#: seq/aseqnet/aseqnet.c:585
+#, c-format
+msgid "disconnected\n"
+msgstr "Verbindung getrennt\n"
+
+#: speaker-test/speaker-test.c:104
+msgid "Front Left"
+msgstr "Vorne links"
+
+#: speaker-test/speaker-test.c:105
+msgid "Front Right"
+msgstr "Vorne rechts"
+
+#: speaker-test/speaker-test.c:106
+msgid "Rear Left"
+msgstr "Hinten links"
+
+#: speaker-test/speaker-test.c:107
+msgid "Rear Right"
+msgstr "Hinten rechts"
+
+#: speaker-test/speaker-test.c:109
+msgid "LFE"
+msgstr "Bass"
+
+#: speaker-test/speaker-test.c:110
+msgid "Side Left"
+msgstr "Seitlich links"
+
+#: speaker-test/speaker-test.c:111
+msgid "Side Right"
+msgstr "Seitlich rechts"
+
+#: speaker-test/speaker-test.c:112
+msgid "Channel 9"
+msgstr "Kanal 9"
+
+#: speaker-test/speaker-test.c:113
+msgid "Channel 10"
+msgstr "Kanal 10"
+
+#: speaker-test/speaker-test.c:114
+msgid "Channel 11"
+msgstr "Kanal 11"
+
+#: speaker-test/speaker-test.c:115
+msgid "Channel 12"
+msgstr "Kanal 12"
+
+#: speaker-test/speaker-test.c:116
+msgid "Channel 13"
+msgstr "Kanal 13"
+
+#: speaker-test/speaker-test.c:117
+msgid "Channel 14"
+msgstr "Kanal 14"
+
+#: speaker-test/speaker-test.c:118
+msgid "Channel 15"
+msgstr "Kanal 15"
+
+#: speaker-test/speaker-test.c:119
+msgid "Channel 16"
+msgstr "Kanal 16"
+
+#: 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:390
+#, c-format
+msgid "Access type not available for playback: %s\n"
+msgstr "Zugriffsmodus nicht unterstützt: %s\n"
+
+#: 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: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: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: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: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: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: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:430
+#, c-format
+msgid "Requested period time %u us\n"
+msgstr "Angeforderte Periodenzeit %u µs\n"
+
+#: 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:439
+#, c-format
+msgid "Requested buffer time %u us\n"
+msgstr "Angeforderte Pufferlänge %u µs\n"
+
+#: 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:451
+#, c-format
+msgid "Using max buffer size %lu\n"
+msgstr "Verwende maximale Puffergröße %lu\n"
+
+#: 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:460
+#, c-format
+msgid "Periods = %u\n"
+msgstr "Perioden = %u\n"
+
+#: 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: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:478
+#, c-format
+msgid "was set period_size = %lu\n"
+msgstr "gesetzt: period_size = %lu\n"
+
+#: 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: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: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: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: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: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: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: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:605 speaker-test/speaker-test.c:1025
+#, c-format
+msgid "No enough memory\n"
+msgstr "Nicht genug Speicher\n"
+
+#: 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: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:619
+#, c-format
+msgid "Not a WAV file: %s\n"
+msgstr "Keine WAV-Datei: %s\n"
+
+#: 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: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: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: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:688
+#, c-format
+msgid "Undefined channel %d\n"
+msgstr "Kanal %d nicht definiert\n"
+
+#: speaker-test/speaker-test.c:739
+#, c-format
+msgid "Write error: %d,%s\n"
+msgstr "Schreibfehler: %d, %s\n"
+
+#: 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:803
+#, 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 ""
+"Verwendung: speaker-test [Option]...\n"
+"-h,--help       Hilfe\n"
+"-D,--device     Wiedergabe-Gerät\n"
+"-r,--rate       Sample-Rate in Hz\n"
+"-c,--channels   Anzahl der Kanäle\n"
+"-f,--frequency  Frequenz der Sinuswelle in Hz\n"
+"-F,--format     Sample-Format\n"
+"-b,--buffer     Ringpufferlänge in µs\n"
+"-p,--period     Periodenlänge in µs\n"
+"-P,--nperiods   Anzahl der Perioden\n"
+"-t,--test       pink=rosa Rauschen, sine=Sinuswelle, wav=WAV-Datei\n"
+"-l,--nloops     Anzahl der Wiederholungen, 0 = unendlich\n"
+"-s,--speaker    teste einen einzelnen Lautsprecher; 1=links, 2=rechts, usw.\n"
+"-w,--wavfile    benutze WAV-Datei als Testton\n"
+"-W,--wavdir     benutze Verzeichnis mit darin enthaltenen WAV-Dateien\n"
+"\n"
+
+#: 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: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:953
+#, c-format
+msgid "Invalid parameter for -s option.\n"
+msgstr "Ungültiger Wert für Option -s\n"
+
+#: speaker-test/speaker-test.c:967
+#, c-format
+msgid "Unknown option '%c'\n"
+msgstr "Unbekannte Options '%c'\n"
+
+#: 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: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:985
+#, c-format
+msgid "Using 16 octaves of pink noise\n"
+msgstr "Verwende 16 Oktaven rosa Rauschen\n"
+
+#: 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:991
+#, c-format
+msgid "WAV file(s)\n"
+msgstr "WAV-Datei(en)\n"
+
+#: 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:1002
+#, c-format
+msgid "Setting of hwparams failed: %s\n"
+msgstr "Fehler beim Setzen der Hardware-Parameter: %s\n"
+
+#: 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:1056 speaker-test/speaker-test.c:1078
+#, c-format
+msgid "Transfer failed: %s\n"
+msgstr "Schreibfehler: %s\n"
+
+#: 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/po/ja.po b/po/ja.po
new file mode 100644
index 0000000..2c234f4
--- /dev/null
+++ b/po/ja.po
@@ -0,0 +1,1520 @@
+# Japanese translations for alsa-utils package
+# alsa-utils パッケージに対する英訳.
+# Copyright (C) 2005 Free Software Foundation, Inc.
+# This file is distributed under the same license as the alsa-utils package.
+# Takashi Iwai <tiwai@suse.de>, 2005.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: alsa-utils 1.0.9a\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-05-24 19:56+0200\n"
+"PO-Revision-Date: 2009-05-27 15:08+0200\n"
+"Last-Translator: Takashi Iwai <tiwai@suse.de>\n"
+"Language-Team: Japanese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: alsamixer/card_select.c:126 alsamixer/device_name.c:126
+msgid "Sound Card"
+msgstr "サウンドカード"
+
+#: alsamixer/card_select.c:181
+msgid "(default)"
+msgstr "(デフォルト)"
+
+#: alsamixer/card_select.c:191
+msgid "cannot enumerate sound cards"
+msgstr "サウンドカードを検出できません"
+
+#: alsamixer/card_select.c:215
+msgid "enter device name..."
+msgstr "デバイス名入力..."
+
+#: alsamixer/cli.c:40
+msgid "Usage: alsamixer [options]"
+msgstr "使用法: alsamixer [オプション]"
+
+#: 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 ""
+"主なオプション:\n"
+"  -h, --help               このヘルプ画面\n"
+"  -c, --card=番号          サウンドカード番号またはID\n"
+"  -D, --device=名前        ミキサーデバイス名\n"
+"  -V, --view=表示モード    開始時の表示モード: playback/capture/all"
+
+#: 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 ""
+"デバッグオプション:\n"
+"  -g, --no-color           カラー/モノクロ表示\n"
+"  -a, --abstraction=名前   ミキサー抽象レベル: none/basic"
+
+#: alsamixer/cli.c:77
+#, c-format
+msgid "invalid card index: %s\n"
+msgstr "不正なカード番号: %s\n"
+
+#: alsamixer/cli.c:103
+#, c-format
+msgid "unknown abstraction level: %s\n"
+msgstr "未知の抽象レベル: %s\n"
+
+#: alsamixer/cli.c:108
+#, c-format
+msgid "unknown option: %c\n"
+msgstr "未知のオプション '%c'\n"
+
+#: alsamixer/cli.c:110
+msgid "try `alsamixer --help' for more information\n"
+msgstr "より詳しい情報は「alsamixer --help」を実行してください\n"
+
+#: alsamixer/device_name.c:177
+msgid "Device name:"
+msgstr "デバイス名:"
+
+#: alsamixer/die.c:37
+#, c-format
+msgid "%s: %s\n"
+msgstr ""
+
+#: alsamixer/mixer_display.c:95
+msgid "Card:"
+msgstr "カード:"
+
+#: alsamixer/mixer_display.c:96
+msgid "Chip:"
+msgstr "チップ:"
+
+#: alsamixer/mixer_display.c:97
+msgid "View:"
+msgstr "表示:"
+
+#: alsamixer/mixer_display.c:98
+msgid "Item:"
+msgstr "項目:"
+
+#: alsamixer/mixer_display.c:101
+msgid "F1:  Help"
+msgstr "F1:  ヘルプ"
+
+#: alsamixer/mixer_display.c:102
+msgid "F2:  System information"
+msgstr "F2:  システム情報"
+
+#: alsamixer/mixer_display.c:103
+msgid "F6:  Select sound card"
+msgstr "F6:  サウンドカード選択"
+
+#: alsamixer/mixer_display.c:104
+msgid "Esc: Exit"
+msgstr "Esc: 終了"
+
+#: alsamixer/mixer_display.c:171
+msgid "(unplugged)"
+msgstr ""
+
+#: alsamixer/mixer_display.c:189
+msgid "Playback"
+msgstr "再生"
+
+#: alsamixer/mixer_display.c:190
+msgid "Capture"
+msgstr "録音"
+
+#: alsamixer/mixer_display.c:191
+msgid "All"
+msgstr "全て"
+
+#: alsamixer/mixer_display.c:231
+msgid "mute"
+msgstr "ミュート"
+
+#: alsamixer/mixer_display.c:272 alsamixer/mixer_display.c:282
+msgid "dB gain:"
+msgstr "dBゲイン:"
+
+#: alsamixer/mixer_display.c:282
+#, c-format
+msgid " [%s %s, %s]"
+msgstr ""
+
+#: alsamixer/mixer_display.c:291 alsamixer/mixer_display.c:297
+#: alsamixer/mixer_display.c:303 alsamixer/mixer_display.c:309
+msgid "Off"
+msgstr "オフ"
+
+#: alsamixer/mixer_display.c:297 alsamixer/mixer_display.c:309
+msgid "On"
+msgstr "オン"
+
+#: alsamixer/mixer_display.c:360
+msgid "The sound device was unplugged."
+msgstr "デバイスが接続されていません"
+
+#: alsamixer/mixer_display.c:361
+msgid "Press F6 to select another sound card."
+msgstr "他のカードを選択するにはF6を押して下さい"
+
+#: alsamixer/mixer_display.c:376
+msgid "This sound device does not have any playback controls."
+msgstr "このカードには再生ミキサーがありません"
+
+#: alsamixer/mixer_display.c:378
+msgid "This sound device does not have any capture controls."
+msgstr "このカードには録音ミキサーがありません"
+
+#: alsamixer/mixer_display.c:380
+msgid "This sound device does not have any controls."
+msgstr "このカードには制御可能なミキサーがありません"
+
+#. TRANSLATORS: playback on; one character
+#: alsamixer/mixer_display.c:512 alsamixer/mixer_display.c:517
+msgid "O"
+msgstr ""
+
+#. TRANSLATORS: playback muted; one character
+#: alsamixer/mixer_display.c:514 alsamixer/mixer_display.c:518
+msgid "M"
+msgstr ""
+
+#. TRANSLATORS: "left"; no more than two characters
+#: alsamixer/mixer_display.c:532
+msgid "L"
+msgstr ""
+
+#. TRANSLATORS: "right"; no more than two characters
+#: alsamixer/mixer_display.c:536
+msgid "R"
+msgstr ""
+
+#. TRANSLATORS: no more than eight characters
+#: alsamixer/mixer_display.c:538
+msgid "CAPTURE"
+msgstr "録音"
+
+#: alsamixer/mixer_display.c:588
+msgid "Front"
+msgstr "フロント"
+
+#: alsamixer/mixer_display.c:591
+msgid "Rear"
+msgstr "リア"
+
+#: alsamixer/mixer_display.c:594 speaker-test/speaker-test.c:106
+msgid "Center"
+msgstr "センター"
+
+#: alsamixer/mixer_display.c:597
+msgid "Woofer"
+msgstr "低音"
+
+#: alsamixer/mixer_display.c:600
+msgid "Side"
+msgstr "サイド"
+
+#: alsamixer/mixer_widget.c:83 alsamixer/mixer_widget.c:88
+msgid "cannot open mixer"
+msgstr "ミキサーを開けません"
+
+#: alsamixer/mixer_widget.c:94 alsamixer/mixer_widget.c:171
+msgid "cannot load mixer controls"
+msgstr "ミキサーをロードできません"
+
+#: alsamixer/mixer_widget.c:161
+#, c-format
+msgid "Cannot open mixer device '%s'."
+msgstr "ミキサーデバイス'%s'を開けません"
+
+#: alsamixer/mixer_widget.c:182
+msgid "Esc     Exit"
+msgstr "Esc    終了"
+
+#: alsamixer/mixer_widget.c:183
+msgid "F1 ? H  Help"
+msgstr "F1 ?   H ヘルプ"
+
+#: alsamixer/mixer_widget.c:184
+msgid "F2 /    System information"
+msgstr "F2 /   システム情報"
+
+#: alsamixer/mixer_widget.c:185
+msgid "F3      Show playback controls"
+msgstr "F3     再生ミキサー表示"
+
+#: alsamixer/mixer_widget.c:186
+msgid "F4      Show capture controls"
+msgstr "F4     録音ミキサー表示"
+
+#: alsamixer/mixer_widget.c:187
+msgid "F5      Show all controls"
+msgstr "F5     全て表示"
+
+#: alsamixer/mixer_widget.c:188
+msgid "Tab     Toggle view mode (F3/F4/F5)"
+msgstr "Tab    表示モード変更 (F3/F4/F5)"
+
+#: alsamixer/mixer_widget.c:189
+msgid "F6 S    Select sound card"
+msgstr "F5 S   サウンドカード選択"
+
+#: alsamixer/mixer_widget.c:190
+msgid "L       Redraw screen"
+msgstr "L      画面再描画"
+
+#: alsamixer/mixer_widget.c:192
+msgid "Left    Move to the previous control"
+msgstr "←     前の項目に移る"
+
+#: alsamixer/mixer_widget.c:193
+msgid "Right   Move to the next control"
+msgstr "→     次の項目に移る"
+
+# ↑
+#: alsamixer/mixer_widget.c:195
+msgid "Up/Down    Change volume"
+msgstr "↑/↓     音量変更"
+
+#: alsamixer/mixer_widget.c:196
+msgid "+ -        Change volume"
+msgstr "+ -       音量変更"
+
+#: alsamixer/mixer_widget.c:197
+msgid "Page Up/Dn Change volume in big steps"
+msgstr "Page Up/dn 音量変更"
+
+#: alsamixer/mixer_widget.c:198
+msgid "End        Set volume to 0%"
+msgstr "End       音量 0%"
+
+#: alsamixer/mixer_widget.c:199
+msgid "0-9        Set volume to 0%-90%"
+msgstr "0-9       音量 0%-90%"
+
+#: alsamixer/mixer_widget.c:200
+msgid "Q W E      Increase left/both/right volumes"
+msgstr "Q W E     左/両/右音量アップ"
+
+#. TRANSLATORS: or Y instead of Z
+#: alsamixer/mixer_widget.c:202
+msgid "Z X C      Decrease left/both/right volumes"
+msgstr "Z X C     左/両/右音量ダウン"
+
+#: alsamixer/mixer_widget.c:203
+msgid "B          Balance left and right volumes"
+msgstr "B         左右音量を平均化する"
+
+#: alsamixer/mixer_widget.c:205
+msgid "M          Toggle mute"
+msgstr "M         ミュートをトグル"
+
+#. TRANSLATORS: or , .
+#: alsamixer/mixer_widget.c:207
+msgid "< >        Toggle left/right mute"
+msgstr "< >       左/右ミュートをトグル"
+
+#: alsamixer/mixer_widget.c:209
+msgid "Space      Toggle capture"
+msgstr "スペース  録音をトグル"
+
+#. TRANSLATORS: or Insert Delete
+#: alsamixer/mixer_widget.c:211
+msgid "; '        Toggle left/right capture"
+msgstr "; '       左/右録音をトグル"
+
+#: alsamixer/mixer_widget.c:213
+msgid "Authors:"
+msgstr "作者:"
+
+#: 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 "ヘルプ"
+
+#: alsamixer/proc_files.c:103
+msgid "Select File"
+msgstr "ファイル選択"
+
+#: alsamixer/textbox.c:52 alsamixer/textbox.c:66
+msgid "Error"
+msgstr "エラー"
+
+#: alsamixer/textbox.c:80
+#, c-format
+msgid "Cannot open file \"%s\"."
+msgstr "\"%s\"をオープンできません"
+
+#: aplay/aplay.c:139
+msgid "raw data"
+msgstr "raw データ"
+
+#: aplay/aplay.c:140
+msgid "VOC"
+msgstr "VOC"
+
+#: aplay/aplay.c:142
+msgid "WAVE"
+msgstr "WAVE"
+
+#: aplay/aplay.c:143
+msgid "Sparc Audio"
+msgstr "Sparc オーディオ"
+
+#: aplay/aplay.c:164
+#, 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"
+msgstr ""
+"使用法: %s [オプション]... [ファイル]...\n"
+"\n"
+"-h, --help              ヘルプ\n"
+"    --version           現在のバージョンを表示\n"
+"-l, --list-devices      全てのサウンドカードとオーディオデバイスを表示\n"
+"-L, --list-pcms         全ての定義された PCM を表示\n"
+"-D, --device=NAME       使用する PCM を指定\n"
+"-q, --quiet             quiet モード\n"
+"-t, --file-type TYPE    ファイルタイプ (voc, wav, raw または au)\n"
+"-c, --channels=#        チャネル数\n"
+"-f, --format=FORMAT     サンプルフォーマット (大/小文字区別)\n"
+"-r, --rate=#            サンプルレート\n"
+"-d, --duration=#        指定の秒数後に終了\n"
+"-M, --mmap              mmap ストリーム\n"
+"-N, --nonblock          非ブロックモード\n"
+"-F, --period-time=#     割り込み間隔をμ秒で指定\n"
+"-B, --buffer-time=#     バッファの大きさをμ秒で指定\n"
+"    --period-size=#     割り込み間隔をフレーム数で指定\n"
+"    --buffer-size=#     バッファの大きさをフレーム数で指定\n"
+"-A, --avail-min=#       wakeup の最少スペースをμ秒で指定\n"
+"-R, --start-delay=#     指定のμ秒後に PCM を自動スタート\n"
+"                        (0 以下の場合はバッファサイズより)\n"
+"-T, --stop-delay=#      XRUN から指定のμ秒後に PCM の自動停止\n"
+"-v, --verbose           PCM の設定を表示 (複数指定可能)\n"
+"-V, --vumeter=TYPE      enable VU meter (TYPE: mono or stereo)\n"
+"-I, --separate-channels 各チャネルに一つのファイルを用いる\n"
+"    --disable-resample  自動レート変換を禁止する\n"
+"    --disable-channels  自動チャネル変換を禁止する\n"
+"    --disable-format    自動フォーマット変換を禁止する\n"
+"    --disable-softvol   ソフト音量制御(softvol)を禁止する\n"
+"    --test-position     リングバッファ位置をテストする\n"
+"    --test-coef=#       リングバッファ位置テストの係数 (デフォルト 8)\n"
+"                        テスト範囲: coef * (buffer_size / 2)\n"
+"    --test-nowait       リングバッファのウエイトを禁止 - 注意:高CPU負荷\n"
+
+#: aplay/aplay.c:199 speaker-test/speaker-test.c:740
+#, c-format
+msgid "Recognized sample formats are:"
+msgstr "認識されるサンプルフォーマット:"
+
+#: aplay/aplay.c:205
+#, c-format
+msgid ""
+"\n"
+"Some of these may not be available on selected hardware\n"
+msgstr ""
+"\n"
+"これらのいくつかは指定のハードウェアで使用不可能な場合があります\n"
+
+#: aplay/aplay.c:206
+#, c-format
+msgid "The availabled format shortcuts are:\n"
+msgstr "可能なフォーマットの省略形:\n"
+
+#: aplay/aplay.c:207
+#, c-format
+msgid "-f cd (16 bit little endian, 44100, stereo)\n"
+msgstr "-f cd (16 ビット、リトルエンディアン、44100、ステレオ)\n"
+
+#: aplay/aplay.c:208
+#, c-format
+msgid "-f cdr (16 bit big endian, 44100, stereo)\n"
+msgstr "-f cdr (16 ビット、ビッグエンディアン、44100、ステレオ)\n"
+
+#: aplay/aplay.c:209
+#, c-format
+msgid "-f dat (16 bit little endian, 48000, stereo)\n"
+msgstr "-f dat (16 ビット、リトルエンディアン、48000、ステレオ)\n"
+
+#: aplay/aplay.c:223
+msgid "no soundcards found..."
+msgstr "サウンドカードが見つかりません..."
+
+#: aplay/aplay.c:226
+#, c-format
+msgid "**** List of %s Hardware Devices ****\n"
+msgstr "**** ハードウェアデバイス %s のリスト ****\n"
+
+#: aplay/aplay.c:255
+#, c-format
+msgid "card %i: %s [%s], device %i: %s [%s]\n"
+msgstr "カード %i: %s [%s], デバイス %i: %s [%s]\n"
+
+#: aplay/aplay.c:261
+#, c-format
+msgid "  Subdevices: %i/%i\n"
+msgstr "  サブデバイス: %i/%i\n"
+
+#: aplay/aplay.c:268
+#, c-format
+msgid "  Subdevice #%i: %s\n"
+msgstr "  サブデバイス #%i: %s\n"
+
+#: aplay/aplay.c:332
+#, c-format
+msgid "Aborted by signal %s...\n"
+msgstr "シグナル %s で中断...\n"
+
+#: aplay/aplay.c:430
+msgid "command should be named either arecord or aplay"
+msgstr "arecord または aplay コマンドのみ可能"
+
+#: aplay/aplay.c:469
+#, c-format
+msgid "unrecognized file format %s"
+msgstr "不正なファイルフォーマット %s"
+
+#: aplay/aplay.c:476
+#, c-format
+msgid "value %i for channels is invalid"
+msgstr "不正なチャネル数 %i"
+
+#: aplay/aplay.c:495
+#, c-format
+msgid "wrong extended format '%s'"
+msgstr "不正な拡張フォーマット '%s'"
+
+#: aplay/aplay.c:506
+#, c-format
+msgid "bad speed value %i"
+msgstr "不正なレート値 %i"
+
+#: aplay/aplay.c:592
+#, c-format
+msgid "Try `%s --help' for more information.\n"
+msgstr "より詳しい情報は「%s --help」を実行してください\n"
+
+#: aplay/aplay.c:608
+#, c-format
+msgid "audio open error: %s"
+msgstr ""
+
+#: aplay/aplay.c:613
+#, c-format
+msgid "info error: %s"
+msgstr ""
+
+#: aplay/aplay.c:620
+#, c-format
+msgid "nonblock setting error: %s"
+msgstr ""
+
+#: aplay/aplay.c:630 aplay/aplay.c:737 aplay/aplay.c:1092
+msgid "not enough memory"
+msgstr "メモリが足りません"
+
+#: aplay/aplay.c:727
+#, c-format
+msgid "read error (called from line %i)"
+msgstr "リードエラー (%i 行)"
+
+#: aplay/aplay.c:785
+#, c-format
+msgid "unknown length of 'fmt ' chunk (read %u, should be %u at least)"
+msgstr ""
+
+#: aplay/aplay.c:795
+#, c-format
+msgid ""
+"unknown length of extensible 'fmt ' chunk (read %u, should be %u at least)"
+msgstr ""
+
+#: aplay/aplay.c:800
+msgid "wrong format tag in extensible 'fmt ' chunk"
+msgstr ""
+
+#: aplay/aplay.c:807
+#, c-format
+msgid "can't play WAVE-file format 0x%04x which is not PCM or FLOAT encoded"
+msgstr ""
+
+#: aplay/aplay.c:811
+#, c-format
+msgid "can't play WAVE-files with %d tracks"
+msgstr "%d トラックを含む WAVE ファイルは再生できません"
+
+#: aplay/aplay.c:819 aplay/aplay.c:919
+#, c-format
+msgid "Warning: format is changed to U8\n"
+msgstr "警告: フォーマットは U8 に変更されます\n"
+
+#: aplay/aplay.c:825
+#, c-format
+msgid "Warning: format is changed to S16_LE\n"
+msgstr "警告: フォーマットは S16_LE に変更されます\n"
+
+#: aplay/aplay.c:833
+#, c-format
+msgid "Warning: format is changed to S24_3LE\n"
+msgstr "警告: フォーマットは S24_3LE に変更されます\n"
+
+#: aplay/aplay.c:839
+#, c-format
+msgid "Warning: format is changed to S24_LE\n"
+msgstr "警告: フォーマットは S24_LE に変更されます\n"
+
+#: aplay/aplay.c:843
+#, c-format
+msgid ""
+" can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)"
+msgstr ""
+"%2$d バイト長 %1$d サンプルビット (%3$d チャネル) の WAVE ファイルは再生でき"
+"ません"
+
+#: aplay/aplay.c:855
+#, c-format
+msgid " can't play WAVE-files with sample %d bits wide"
+msgstr "%d ビット長のサンプルの WAVE ファイルは再生できません"
+
+#: aplay/aplay.c:913
+#, c-format
+msgid "Warning: format is changed to MU_LAW\n"
+msgstr "警告: フォーマットは MU_LAW に変更されます\n"
+
+#: aplay/aplay.c:925
+#, c-format
+msgid "Warning: format is changed to S16_BE\n"
+msgstr "警告: フォーマットは S16_BE に変更されます\n"
+
+#: aplay/aplay.c:938 aplay/aplay.c:1768 aplay/aplay.c:1775 aplay/aplay.c:2297
+#: aplay/aplay.c:2309
+msgid "read error"
+msgstr "リードエラー"
+
+#: aplay/aplay.c:957
+msgid "Broken configuration for this PCM: no configurations available"
+msgstr "指定の PCM を使用できません: 設定がありません"
+
+#: aplay/aplay.c:974
+msgid "Access type not available"
+msgstr "アクセスタイプが使用不可能"
+
+#: aplay/aplay.c:979
+msgid "Sample format non available"
+msgstr "サンプルフォーマットが使用不可能"
+
+#: aplay/aplay.c:984
+msgid "Channels count non available"
+msgstr "チャネル数が使用不可能"
+
+#: aplay/aplay.c:999
+#, c-format
+msgid "Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"
+msgstr "警告: レートが不正確です (要求値 = %iHz, 使用値 = %iHz)\n"
+
+#: aplay/aplay.c:1005
+#, c-format
+msgid "         please, try the plug plugin %s\n"
+msgstr "        plug プラグイン%s を使用してください\n"
+
+#: aplay/aplay.c:1041
+msgid "Unable to install hw params:"
+msgstr "hw params のインストールに失敗しました:"
+
+#: aplay/aplay.c:1048
+#, c-format
+msgid "Can't use period equal to buffer size (%lu == %lu)"
+msgstr "period と buffer サイズには同じ値を使用できません (%lu == %lu)"
+
+#: aplay/aplay.c:1079
+msgid "unable to install sw params:"
+msgstr "sw params のインストールに失敗しました:"
+
+#: aplay/aplay.c:1154
+#, c-format
+msgid "status error: %s"
+msgstr "ステータスエラー: %s"
+
+#: aplay/aplay.c:1164 aplay/aplay.c:1175
+#, c-format
+msgid "%s!!! (at least %.3f ms long)\n"
+msgstr "%s!!! (少なくとも %.3f ms)\n"
+
+#: aplay/aplay.c:1165 aplay/aplay.c:1168 aplay/aplay.c:1176
+msgid "underrun"
+msgstr "アンダーラン"
+
+#: aplay/aplay.c:1165 aplay/aplay.c:1176
+msgid "overrun"
+msgstr "オーバーラン"
+
+#: aplay/aplay.c:1180
+#, c-format
+msgid "Status:\n"
+msgstr "ステータス:\n"
+
+#: aplay/aplay.c:1184
+#, c-format
+msgid "xrun: prepare error: %s"
+msgstr ""
+
+#: aplay/aplay.c:1190
+#, c-format
+msgid "Status(DRAINING):\n"
+msgstr "ステータス(DRAINING):\n"
+
+#: aplay/aplay.c:1194
+#, c-format
+msgid "capture stream format change? attempting recover...\n"
+msgstr "録音ストリームのフォーマットが変更? 修復を試みます...\n"
+
+#: aplay/aplay.c:1196
+#, c-format
+msgid "xrun(DRAINING): prepare error: %s"
+msgstr ""
+
+#: aplay/aplay.c:1203
+#, c-format
+msgid "Status(R/W):\n"
+msgstr "ステータス(R/W):\n"
+
+#: aplay/aplay.c:1206
+#, c-format
+msgid "read/write error, state = %s"
+msgstr "読み書きエラー, ステータス = %s"
+
+#: aplay/aplay.c:1216
+#, c-format
+msgid "Suspended. Trying resume. "
+msgstr "サスペンド中です。レジュームします。"
+
+#: aplay/aplay.c:1221
+#, c-format
+msgid "Failed. Restarting stream. "
+msgstr "失敗しました。ストリームを再スタートします。"
+
+#: aplay/aplay.c:1223
+#, c-format
+msgid "suspend: prepare error: %s"
+msgstr "サスペンド: prepare エラー: %s"
+
+#: aplay/aplay.c:1228
+#, c-format
+msgid "Done.\n"
+msgstr "終了\n"
+
+#: aplay/aplay.c:1250
+#, c-format
+msgid " !clip  "
+msgstr ""
+
+#: aplay/aplay.c:1397
+#, c-format
+msgid "Unsupported bit size %d.\n"
+msgstr ""
+
+#: aplay/aplay.c:1431
+#, c-format
+msgid "Max peak (%li samples): 0x%08x "
+msgstr "最大ピーク (%li サンプル): 0x%08x "
+
+#: aplay/aplay.c:1465
+#, c-format
+msgid ""
+"Suspicious buffer position (%li total): avail = %li, delay = %li, buffer = %"
+"li\n"
+msgstr ""
+
+#: aplay/aplay.c:1528
+#, c-format
+msgid "write error: %s"
+msgstr "書込エラー: %s"
+
+#: aplay/aplay.c:1574
+#, c-format
+msgid "writev error: %s"
+msgstr "書込(writev)エラー: %s"
+
+#: aplay/aplay.c:1617
+#, c-format
+msgid "read error: %s"
+msgstr "読込エラー: %s"
+
+#: aplay/aplay.c:1660
+#, c-format
+msgid "readv error: %s"
+msgstr "読込(readv)エラー: %s"
+
+#: aplay/aplay.c:1708
+msgid "can't allocate buffer for silence"
+msgstr "サイレンス用のバッファの取得に失敗しました"
+
+#: 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
+msgid "write error"
+msgstr "書込エラー"
+
+#: aplay/aplay.c:1730
+#, c-format
+msgid "voc_pcm_flush - silence error"
+msgstr ""
+
+#: aplay/aplay.c:1733
+msgid "voc_pcm_flush error"
+msgstr ""
+
+#: aplay/aplay.c:1759
+msgid "malloc error"
+msgstr "malloc エラー"
+
+#: aplay/aplay.c:1763
+#, c-format
+msgid "Playing Creative Labs Channel file '%s'...\n"
+msgstr "Creative Labs Channel ファイル '%s' を演奏中...\n"
+
+#: aplay/aplay.c:1831 aplay/aplay.c:1923
+msgid "can't play packed .voc files"
+msgstr "packed .voc ファイルは演奏できません"
+
+#: aplay/aplay.c:1883
+#, c-format
+msgid "can't play loops; %s isn't seekable\n"
+msgstr "ループ演奏できません。%s はシーク不可能です\n"
+
+#: aplay/aplay.c:1932
+#, c-format
+msgid "unknown blocktype %d. terminate."
+msgstr "未知のブロックタイプ %d: 終了します。"
+
+#: aplay/aplay.c:2063
+#, c-format
+msgid "Wave doesn't support %s format..."
+msgstr "WAVE は %s フォーマットをサポートしません..."
+
+#: aplay/aplay.c:2123
+#, c-format
+msgid "Sparc Audio doesn't support %s format..."
+msgstr "Sparc オーディオは %s フォーマットをサポートしません..."
+
+#: aplay/aplay.c:2204
+msgid "Playing"
+msgstr "再生中"
+
+#: aplay/aplay.c:2204
+msgid "Recording"
+msgstr "録音中"
+
+#: aplay/aplay.c:2208
+#, c-format
+msgid "Rate %d Hz, "
+msgstr "レート %d Hz, "
+
+#: aplay/aplay.c:2210
+#, c-format
+msgid "Mono"
+msgstr "モノラル"
+
+#: aplay/aplay.c:2212
+#, c-format
+msgid "Stereo"
+msgstr "ステレオ"
+
+#: aplay/aplay.c:2214
+#, c-format
+msgid "Channels %i"
+msgstr "チャネル数 %i"
+
+#: aplay/aplay.c:2573 aplay/aplay.c:2626
+#, c-format
+msgid "You need to specify %d files"
+msgstr "%d 個のファイルを指定してください"
+
+#: seq/aconnect/aconnect.c:49
+#, c-format
+msgid "aconnect - ALSA sequencer connection manager\n"
+msgstr "aconnect - ALSA sequencer 接続マネージャ\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 "使用法:\n"
+
+#: seq/aconnect/aconnect.c:52
+#, c-format
+msgid " * Connection/disconnection between two ports\n"
+msgstr " * 二つのポートの接続/切り離し\n"
+
+#: seq/aconnect/aconnect.c:53
+#, c-format
+msgid "   aconnect [-options] sender receiver\n"
+msgstr "   aconnect [-オプション] 送信 受信\n"
+
+#: seq/aconnect/aconnect.c:54
+#, c-format
+msgid "     sender, receiver = client:port pair\n"
+msgstr "     送信, 受信 = クライアント:ポートの組\n"
+
+#: seq/aconnect/aconnect.c:55
+#, c-format
+msgid "     -d,--disconnect     disconnect\n"
+msgstr "     -d,--disconnect     切り離し\n"
+
+#: seq/aconnect/aconnect.c:56
+#, c-format
+msgid "     -e,--exclusive      exclusive connection\n"
+msgstr "     -e,--exclusive      排他接続\n"
+
+#: seq/aconnect/aconnect.c:57
+#, c-format
+msgid "     -r,--real #         convert real-time-stamp on queue\n"
+msgstr "     -r,--real #         キューのリアルタイムスタンプに変換\n"
+
+#: seq/aconnect/aconnect.c:58
+#, c-format
+msgid "     -t,--tick #         convert tick-time-stamp on queue\n"
+msgstr "    -t,--tick #          キューのティックタイムスタンプに変換\n"
+
+#: seq/aconnect/aconnect.c:59
+#, c-format
+msgid " * List connected ports (no subscription action)\n"
+msgstr " * 接続済みのポートの一覧\n"
+
+#: seq/aconnect/aconnect.c:60
+#, c-format
+msgid "   aconnect -i|-o [-options]\n"
+msgstr "   aconnect -i|-o [-オプション]\n"
+
+#: seq/aconnect/aconnect.c:61
+#, c-format
+msgid "     -i,--input          list input (readable) ports\n"
+msgstr "     -i,--input          入力 (読込) ポートの一覧\n"
+
+#: seq/aconnect/aconnect.c:62
+#, c-format
+msgid "     -o,--output         list output (writable) ports\n"
+msgstr "     -o,--output         主力 (書込) ポートの一覧\n"
+
+#: seq/aconnect/aconnect.c:63
+#, c-format
+msgid "     -l,--list           list current connections of each port\n"
+msgstr "     -l,--list           各ポートの現在の接続を表示\n"
+
+#: seq/aconnect/aconnect.c:64
+#, c-format
+msgid " * Remove all exported connections\n"
+msgstr " * 全てのエクスポートされた接続を切り離す\n"
+
+#: seq/aconnect/aconnect.c:65
+#, c-format
+msgid "     -x, --removeall\n"
+msgstr "     -x, --removall\n"
+
+#: seq/aconnect/aconnect.c:132
+msgid "Connecting To"
+msgstr "接続先"
+
+#: seq/aconnect/aconnect.c:133
+msgid "Connected From"
+msgstr "接続元"
+
+#: seq/aconnect/aconnect.c:169
+#, c-format
+msgid "client %d: '%s' [type=%s]\n"
+msgstr "クライアント %d: '%s' [タイプ=%s]\n"
+
+#: seq/aconnect/aconnect.c:173
+msgid "user"
+msgstr "ユーザ"
+
+#: seq/aconnect/aconnect.c:173
+msgid "kernel"
+msgstr "カーネル"
+
+#: seq/aconnect/aconnect.c:326
+#, c-format
+msgid "can't open sequencer\n"
+msgstr "sequencer をオープンできません\n"
+
+#: seq/aconnect/aconnect.c:354
+#, c-format
+msgid "can't get client id\n"
+msgstr "クライアント ID を取得できません\n"
+
+#: seq/aconnect/aconnect.c:361
+#, c-format
+msgid "can't set client info\n"
+msgstr "クライアント情報を取得できません\n"
+
+#: seq/aconnect/aconnect.c:368
+#, c-format
+msgid "invalid sender address %s\n"
+msgstr "送信アドレスが不正です: %s\n"
+
+#: seq/aconnect/aconnect.c:373 seq/aseqnet/aseqnet.c:290
+#, c-format
+msgid "invalid destination address %s\n"
+msgstr "受信アドレスが不正です: %s\n"
+
+#: seq/aconnect/aconnect.c:387
+#, c-format
+msgid "No subscription is found\n"
+msgstr "接続が見つかりません\n"
+
+#: seq/aconnect/aconnect.c:392
+#, c-format
+msgid "Disconnection failed (%s)\n"
+msgstr "切り離しに失敗 (%s)\n"
+
+#: seq/aconnect/aconnect.c:398
+#, c-format
+msgid "Connection is already subscribed\n"
+msgstr "既に接続されています\n"
+
+#: seq/aconnect/aconnect.c:403
+#, c-format
+msgid "Connection failed (%s)\n"
+msgstr "接続に失敗 (%s)\n"
+
+#: seq/aseqnet/aseqnet.c:164
+#, c-format
+msgid "aseqnet - network client/server on ALSA sequencer\n"
+msgstr "aseqnet - ALSA sequencer 上のネットワーククライアント/サーバ\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 "使用法:\n"
+
+#: seq/aseqnet/aseqnet.c:167
+#, c-format
+msgid "  server mode: aseqnet [-options]\n"
+msgstr "  サーバモード: aseqnet [-オプション]\n"
+
+#: seq/aseqnet/aseqnet.c:168
+#, c-format
+msgid "  client mode: aseqnet [-options] server_host\n"
+msgstr "  クライアントモード: aseqnet [-オプション] サーバホスト\n"
+
+#: seq/aseqnet/aseqnet.c:169
+#, c-format
+msgid "options:\n"
+msgstr "オプション:\n"
+
+#: seq/aseqnet/aseqnet.c:170
+#, c-format
+msgid "  -p,--port # : sepcify TCP port (digit or service name)\n"
+msgstr "  -p,--port # : TCP ポートの指定 (数字またはサービス名)\n"
+
+#: seq/aseqnet/aseqnet.c:171
+#, c-format
+msgid "  -s,--source addr : read from given addr (client:port)\n"
+msgstr "  -s,--source addr : 指定のアドレス(クライアント:ポート)から読み込む\n"
+
+#: seq/aseqnet/aseqnet.c:172
+#, c-format
+msgid "  -d,--dest addr : write to given addr (client:port)\n"
+msgstr "  -d,--dest addr : 指定のアドレス(クライアント:ポート)に書き込む\n"
+
+#: seq/aseqnet/aseqnet.c:173
+#, c-format
+msgid "  -v, --verbose : print verbose messages\n"
+msgstr "  -v,--verbose : 冗長メッセージ表示\n"
+
+#: seq/aseqnet/aseqnet.c:174
+#, c-format
+msgid "  -i, --info : print certain received events\n"
+msgstr "  -i,--info : 受信イベントを表示する\n"
+
+#: seq/aseqnet/aseqnet.c:188
+#, c-format
+msgid "can't malloc\n"
+msgstr "malloc できません\n"
+
+#: seq/aseqnet/aseqnet.c:213
+#, c-format
+msgid "closing files..\n"
+msgstr "ファイルを閉じます..\n"
+
+#: seq/aseqnet/aseqnet.c:272
+#, c-format
+msgid "sequencer opened: %d:%d\n"
+msgstr ""
+
+#: seq/aseqnet/aseqnet.c:279
+#, c-format
+msgid "invalid source address %s\n"
+msgstr "不正な送信アドレス %s\n"
+
+#: seq/aseqnet/aseqnet.c:309
+#, c-format
+msgid "service '%s' is not found in /etc/services\n"
+msgstr "サービス '%s' が /etc/services に見つかりません\n"
+
+#: seq/aseqnet/aseqnet.c:377
+#, c-format
+msgid "too many connections!\n"
+msgstr "接続が多すぎます!\n"
+
+#: seq/aseqnet/aseqnet.c:388
+#, c-format
+msgid "accepted[%d]\n"
+msgstr "了解[%d]\n"
+
+#: seq/aseqnet/aseqnet.c:411
+#, c-format
+msgid "can't get address %s\n"
+msgstr "アドレス %s を取得できません\n"
+
+#: seq/aseqnet/aseqnet.c:422
+#, c-format
+msgid "ok.. connected\n"
+msgstr "ok.. 接続\n"
+
+#: seq/aseqnet/aseqnet.c:518
+#, c-format
+msgid "Channel %2d: Control event : %5d\n"
+msgstr "チャネル %2d: コントロール : %5d\n"
+
+#: seq/aseqnet/aseqnet.c:522
+#, c-format
+msgid "Channel %2d: Pitchbender   : %5d\n"
+msgstr "チャネル %2d: ピッチベンド : %5d\n"
+
+#: seq/aseqnet/aseqnet.c:526
+#, c-format
+msgid "Channel %2d: Note On event : %5d\n"
+msgstr "チャネル %2d: ノートオン   : %5d\n"
+
+#: seq/aseqnet/aseqnet.c:530
+#, c-format
+msgid "Channel %2d: Note Off event: %5d\n"
+msgstr "チャネル %2d: ノートオフ   : %5d\n"
+
+#: seq/aseqnet/aseqnet.c:585
+#, c-format
+msgid "disconnected\n"
+msgstr "切り離し\n"
+
+#: speaker-test/speaker-test.c:102
+msgid "Front Left"
+msgstr ""
+
+#: speaker-test/speaker-test.c:103
+msgid "Front Right"
+msgstr ""
+
+#: speaker-test/speaker-test.c:104
+msgid "Rear Left"
+msgstr ""
+
+#: speaker-test/speaker-test.c:105
+msgid "Rear Right"
+msgstr ""
+
+#: speaker-test/speaker-test.c:107
+msgid "LFE"
+msgstr ""
+
+#: speaker-test/speaker-test.c:108
+msgid "Side Left"
+msgstr ""
+
+#: speaker-test/speaker-test.c:109
+msgid "Side Right"
+msgstr ""
+
+#: speaker-test/speaker-test.c:110
+msgid "Channel 9"
+msgstr ""
+
+#: speaker-test/speaker-test.c:111
+msgid "Channel 10"
+msgstr ""
+
+#: speaker-test/speaker-test.c:112
+msgid "Channel 11"
+msgstr ""
+
+#: speaker-test/speaker-test.c:113
+msgid "Channel 12"
+msgstr ""
+
+#: speaker-test/speaker-test.c:114
+msgid "Channel 13"
+msgstr ""
+
+#: speaker-test/speaker-test.c:115
+msgid "Channel 14"
+msgstr ""
+
+#: speaker-test/speaker-test.c:116
+msgid "Channel 15"
+msgstr ""
+
+#: speaker-test/speaker-test.c:117
+msgid "Channel 16"
+msgstr ""
+
+#: speaker-test/speaker-test.c:307
+#, c-format
+msgid "Broken configuration for playback: no configurations available: %s\n"
+msgstr "再生用に設定できません: 設定がみつかりません: %s\n"
+
+#: speaker-test/speaker-test.c:314
+#, c-format
+msgid "Access type not available for playback: %s\n"
+msgstr "アクセスタイプが不正です: %s\n"
+
+#: speaker-test/speaker-test.c:321
+#, c-format
+msgid "Sample format not available for playback: %s\n"
+msgstr "指定のサンプルフォーマットを使用できません: %s\n"
+
+#: speaker-test/speaker-test.c:328
+#, c-format
+msgid "Channels count (%i) not available for playbacks: %s\n"
+msgstr "チャネル数 (%i) を使用できません: %s\n"
+
+#: speaker-test/speaker-test.c:336
+#, c-format
+msgid "Rate %iHz not available for playback: %s\n"
+msgstr "レート %iHz を使用できません: %s\n"
+
+#: speaker-test/speaker-test.c:341
+#, c-format
+msgid "Rate doesn't match (requested %iHz, get %iHz, err %d)\n"
+msgstr "設定レートが一致しません< (要求値 %iHz, 取得値 %iHz, エラー %d)\n"
+
+#: speaker-test/speaker-test.c:345
+#, c-format
+msgid "Rate set to %iHz (requested %iHz)\n"
+msgstr "レート %iHz (要求値 %iHz)\n"
+
+#: speaker-test/speaker-test.c:351
+#, c-format
+msgid "Buffer size range from %lu to %lu\n"
+msgstr "バッファサイズ範囲 %lu 〜 %lu\n"
+
+#: speaker-test/speaker-test.c:352
+#, c-format
+msgid "Period size range from %lu to %lu\n"
+msgstr "ピリオドサイズ範囲 %lu 〜 %lu\n"
+
+#: speaker-test/speaker-test.c:354
+#, c-format
+msgid "Requested period time %u us\n"
+msgstr "要求されたピリオド長 %u us\n"
+
+#: speaker-test/speaker-test.c:357
+#, c-format
+msgid "Unable to set period time %u us for playback: %s\n"
+msgstr "ピリオド長 %u us を設定できません: %s\n"
+
+#: speaker-test/speaker-test.c:363
+#, c-format
+msgid "Requested buffer time %u us\n"
+msgstr "要求されたバッファ長 %u us\n"
+
+#: speaker-test/speaker-test.c:366
+#, c-format
+msgid "Unable to set buffer time %u us for playback: %s\n"
+msgstr "バッファ長 %u us を設定できません: %s\n"
+
+#: speaker-test/speaker-test.c:375
+#, c-format
+msgid "Using max buffer size %lu\n"
+msgstr "最大バッファサイズ %lu を使用\n"
+
+#: speaker-test/speaker-test.c:378
+#, c-format
+msgid "Unable to set buffer size %lu for playback: %s\n"
+msgstr "バッファサイズ %lu を設定できません: %s\n"
+
+#: speaker-test/speaker-test.c:384
+#, c-format
+msgid "Periods = %u\n"
+msgstr "ピリオド数 = %u\n"
+
+#: speaker-test/speaker-test.c:387
+#, c-format
+msgid "Unable to set nperiods %u for playback: %s\n"
+msgstr "ピリオド数 %u を設定できません: %s\n"
+
+#: speaker-test/speaker-test.c:396
+#, c-format
+msgid "Unable to set hw params for playback: %s\n"
+msgstr "hw params を設定できません: %s\n"
+
+#: speaker-test/speaker-test.c:402
+#, c-format
+msgid "was set period_size = %lu\n"
+msgstr "period_size = %lu で設定\n"
+
+#: speaker-test/speaker-test.c:403
+#, c-format
+msgid "was set buffer_size = %lu\n"
+msgstr "buffer_size = %lu で設定\n"
+
+#: speaker-test/speaker-test.c:405
+#, c-format
+msgid "buffer to small, could not use\n"
+msgstr "バッファが小さすぎます\n"
+
+#: speaker-test/speaker-test.c:418
+#, c-format
+msgid "Unable to determine current swparams for playback: %s\n"
+msgstr "現在の swparams を取得できません: %s\n"
+
+#: speaker-test/speaker-test.c:425
+#, c-format
+msgid "Unable to set start threshold mode for playback: %s\n"
+msgstr "start_threshold モードを設定できません: %s\n"
+
+#: speaker-test/speaker-test.c:432
+#, c-format
+msgid "Unable to set avail min for playback: %s\n"
+msgstr "avail_min を設定できません: %s\n"
+
+#: speaker-test/speaker-test.c:439
+#, c-format
+msgid "Unable to set sw params for playback: %s\n"
+msgstr "再生用の sw params を設定できません: %s\n"
+
+#: speaker-test/speaker-test.c:454
+#, c-format
+msgid "Can't recovery from underrun, prepare failed: %s\n"
+msgstr "アンダーランから復帰失敗: %s\n"
+
+#: speaker-test/speaker-test.c:465
+#, c-format
+msgid "Can't recovery from suspend, prepare failed: %s\n"
+msgstr "サスペンドから復帰失敗: %s\n"
+
+#: speaker-test/speaker-test.c:529 speaker-test/speaker-test.c:926
+#, c-format
+msgid "No enough memory\n"
+msgstr "メモリが足りません\n"
+
+#: speaker-test/speaker-test.c:534
+#, c-format
+msgid "Cannot open WAV file %s\n"
+msgstr "WAVファイルがオープンできません: %s\n"
+
+#: speaker-test/speaker-test.c:538 speaker-test/speaker-test.c:567
+#, c-format
+msgid "Invalid WAV file %s\n"
+msgstr "不正なWAVファイルです: %s\n"
+
+#: speaker-test/speaker-test.c:543
+#, c-format
+msgid "Not a WAV file: %s\n"
+msgstr "WAVファイルではありません: %s\n"
+
+#: speaker-test/speaker-test.c:547
+#, c-format
+msgid "Unsupported WAV format %d for %s\n"
+msgstr "未サポートのWAVフォーマット %d: %s\n"
+
+#: speaker-test/speaker-test.c:552
+#, c-format
+msgid "%s is not a mono stream (%d channels)\n"
+msgstr "%s はモノストリームではありません (%d チャネル)\n"
+
+#: speaker-test/speaker-test.c:557
+#, c-format
+msgid "Sample rate doesn't match (%d) for %s\n"
+msgstr "サンプルレートが不一致です(%d): %s\n"
+
+#: speaker-test/speaker-test.c:562
+#, c-format
+msgid "Unsupported sample format bits %d for %s\n"
+msgstr "未サポートのサンプルフォーマットビット %d: %s\n"
+
+#: speaker-test/speaker-test.c:612
+#, c-format
+msgid "Undefined channel %d\n"
+msgstr "未定義のチャネル %d\n"
+
+#: speaker-test/speaker-test.c:663
+#, c-format
+msgid "Write error: %d,%s\n"
+msgstr "書込エラー: %d,%s\n"
+
+#: speaker-test/speaker-test.c:665
+#, c-format
+msgid "xrun_recovery failed: %d,%s\n"
+msgstr "xrun_recovery 失敗: %d,%s\n"
+
+#: speaker-test/speaker-test.c:723
+#, 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 ""
+"使用法: speaker-test [オプション]...\n"
+"-h,--help            ヘルプ\n"
+"-D,--device          再生デバイス\n"
+"-r,--rate            ストリームレート (Hz)\n"
+"-c,--channels        チャネル数\n"
+"-f,--frequency       正弦波周波数 (hz)\n"
+"-F,--format          サンプルフォーマット\n"
+"-b,--buffer          リングバッファサイズ (μs)\n"
+"-p,--period          ピリオドサイズ (μs)\n"
+"-P,--nperiods        ピリオド数\n"
+"-t,--test            pink=ピンクノイズを使用, sine=正弦波を使用, wav=WAVファ"
+"イル再生\n"
+"-l,--nloops          繰り返しテスト数, 0 = 無制限\n"
+"-s,--speaker         単一スピーカーテスト  1=左 または 2=右\n"
+"-w,--wavfile         指定のWAVファイルをテスト音源として使用\n"
+"-W,--wavdir          WAVファイルのあるディレクトリを指定\n"
+"\n"
+
+#: speaker-test/speaker-test.c:835
+#, c-format
+msgid "Invalid number of periods %d\n"
+msgstr "不正なピリオド数 %d\n"
+
+#: speaker-test/speaker-test.c:849 speaker-test/speaker-test.c:853
+#, c-format
+msgid "Invalid test type %s\n"
+msgstr "不正なテストタイプ %s\n"
+
+#: speaker-test/speaker-test.c:865
+#, c-format
+msgid "Invalid parameter for -s option.\n"
+msgstr "-s オプションの値が不正です\n"
+
+#: speaker-test/speaker-test.c:876
+#, c-format
+msgid "Unknown option '%c'\n"
+msgstr "未知のオプション '%c'\n"
+
+#: speaker-test/speaker-test.c:890
+#, c-format
+msgid "Playback device is %s\n"
+msgstr "再生デバイス: %s\n"
+
+#: speaker-test/speaker-test.c:891
+#, c-format
+msgid "Stream parameters are %iHz, %s, %i channels\n"
+msgstr "ストリームパラメータ: %iHz, %s, %i チャネル\n"
+
+#: speaker-test/speaker-test.c:894
+#, c-format
+msgid "Using 16 octaves of pink noise\n"
+msgstr "16 オクターブのピンクノイズを使用\n"
+
+#: speaker-test/speaker-test.c:897
+#, c-format
+msgid "Sine wave rate is %.4fHz\n"
+msgstr "正弦波レート: %.4fHz\n"
+
+#: speaker-test/speaker-test.c:900
+#, c-format
+msgid "WAV file(s)\n"
+msgstr "WAV ファイル\n"
+
+#: speaker-test/speaker-test.c:906
+#, c-format
+msgid "Playback open error: %d,%s\n"
+msgstr "再生オープンエラー: %d,%s\n"
+
+#: speaker-test/speaker-test.c:911
+#, c-format
+msgid "Setting of hwparams failed: %s\n"
+msgstr "hwparams の設定に失敗: %s\n"
+
+#: speaker-test/speaker-test.c:916
+#, c-format
+msgid "Setting of swparams failed: %s\n"
+msgstr "swparams の設定に失敗: %s\n"
+
+#: speaker-test/speaker-test.c:957 speaker-test/speaker-test.c:979
+#, c-format
+msgid "Transfer failed: %s\n"
+msgstr "転送に失敗しました: %s\n"
+
+#: speaker-test/speaker-test.c:967
+#, c-format
+msgid "Time per period = %lf\n"
+msgstr "ピリオド時間 = %lf\n"
diff --git a/seq/Makefile.am b/seq/Makefile.am
new file mode 100644
index 0000000..2c84cee
--- /dev/null
+++ b/seq/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS=aconnect aplaymidi aseqdump aseqnet
diff --git a/seq/aconnect/Makefile.am b/seq/aconnect/Makefile.am
new file mode 100644
index 0000000..507b9fe
--- /dev/null
+++ b/seq/aconnect/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = -I$(top_srcdir)/include
+EXTRA_DIST = README.aconnect aconnect.1
+LDADD = $(LIBINTL)
+
+bin_PROGRAMS = aconnect
+aconnect_SOURCES = aconnect.c
+man_MANS = aconnect.1
diff --git a/seq/aconnect/README.aconnect b/seq/aconnect/README.aconnect
new file mode 100644
index 0000000..1f6237a
--- /dev/null
+++ b/seq/aconnect/README.aconnect
@@ -0,0 +1,46 @@
+================================================================
+	aconnect - control subscriptions
+		ver.0.1.3
+	Copyright (C) 1999-2000 Takashi Iwai
+================================================================
+
+aconnect is a utility to control subscriptions of two ports as the
+third "manager" client.
+
+For example, the following connects two ports, from 64:0 to 65:0.
+
+	% aconnect 64:0 65:0
+
+To disconnect the existing subscription, use -d option.
+
+	% aconnect -d 64:0 65:0
+
+To see which port is available as input port, run the following
+command:
+
+	% aconnect -i
+	client 0: 'System' [type=kernel]
+	    0 'Timer           '
+	    1 'Announce        '
+	client 64: '0: MIDI Synth' [type=kernel]
+	    0 'card 0: synth-midi: 0'
+
+Similary, to see the output ports, use -o flag.
+
+	% aconnect -o
+	client 64: '0: MIDI Synth' [type=kernel]
+	    0 'card 0: synth-midi: 0'
+	client 65: 'AWE Wave Table Synth : 0' [type=kernel]
+	    0 'Emu8000 port 0  '
+	    1 'Emu8000 port 1  '
+	    2 'Emu8000 port 2  '
+	    3 'Emu8000 port 3  '
+
+The option -l together with -i or -o shows subscribers for each port.
+
+Ports are connected exclusively when the option -e is specified.
+
+For modifying time-stamp with a queue, use -r or -t option followed by
+a queue index which updates the time-stamp.  Former uses real-time queue,
+while the latter uses tick queue.  The queue must be used (not necessarily
+owned) by the receiver client.
diff --git a/seq/aconnect/aconnect.1 b/seq/aconnect/aconnect.1
new file mode 100644
index 0000000..2050187
--- /dev/null
+++ b/seq/aconnect/aconnect.1
@@ -0,0 +1,145 @@
+.TH aconnect 1 "August 31, 2000"
+.de EX
+.nf
+.ft CW
+..
+.de EE
+.ft R
+.fi
+..
+.SH NAME
+aconnect \- ALSA sequencer connection manager
+
+.SH SYNOPSIS
+.B aconnect
+[\-d] [\-options] sender receiver
+.br
+.B aconnect
+\-i|\-o [\-options]
+.br
+.B aconnect
+\-x
+
+.SH DESCRIPTION
+.B aconnect
+is a utility to connect and disconnect two existing ports on ALSA sequencer
+system.
+The ports with the arbitrary subscription permission, such as created
+by
+.B aseqview(1),
+can be connected to any (MIDI) device ports using
+.B aconnect.
+For example, to connect from port 64:0 to 65:0, run as follows:
+.IP "" 4
+% aconnect 64:0 65:0
+.PP
+The connection is one-way, and the whole data to the sender port (64:0)
+is redirected to the receiver port (65:0).  When another port (e.g. 65:1)
+is attached to the same sender port, the data is sent to both receiver
+ports.
+For disconnection, use
+.B \-d
+option.
+.sp
+.EX
+% aconnect \-d 64:0 65:0
+.EE
+.PP
+The address can be given using the client's name.
+.sp
+.EX
+% aconnect External:0 Emu8000:1
+.EE
+.PP
+Then the port 0 of the client matching with the string "External" is
+connected to the port 1 of the client matching with the "Emu8000".
+.PP
+Another function of
+.B aconnect
+is to list the present ports
+on the given condition.
+The input ports, which may become
+.I sender
+ports, can be listed with
+.B \-i
+option.
+.sp
+.EX
+% aconnect \-i
+client 0: 'System' [type=kernel]
+    0 'Timer           '
+    1 'Announce        '
+client 64: 'External MIDI\-0' [type=kernel]
+    0 'MIDI 0\-0        '
+.EE
+.PP
+Similarly, to see the output ports, use
+.B \-o
+flag.
+.PP
+You can remove all existing exported connections using
+.B \-x
+option.  This function is useful for terminating the ALSA drivers,
+because the modules with sequencer connections cannot be unloaded
+unless their connections are removed.
+
+.SH OPTIONS
+.SS CONNECTION MANAGEMENT
+.TP
+.B \-d, \-\-disconnect
+Disconnect the given subscription.
+.TP
+.B \-e, \-\-exclusive
+Connect ports with exclusive mode.
+Both sender and receiver ports can be no longer connected by any other ports.
+.TP
+.B \-r, \-\-real queue
+Convert time-stamps of event packets to the current value of the given
+.I real-time
+queue.
+This is option is, however, not so useful, since
+the receiver port must use (not necessarily own) the specified queue.
+.TP
+.B \-t, \-\-tick queue
+Like
+.B \-r
+option, but 
+time-stamps are converted to the current value of the given
+.I tick
+queue.
+
+.SS LIST PORTS
+.TP
+.B \-i, \-\-input
+List existing input (readable) ports.
+This option is exclusive to
+.B \-o.
+.TP
+.B \-o, \-\-output
+List existing output (writable) ports.
+This option is exclusive to
+.B \-i.
+.TP
+.B \-l, \-\-list
+List the current connection status.  The connected and connecting ports
+from/to each port are listed together.
+The suffix flag
+.B [ex]
+means the connection is exclusive.
+The suffix flag
+.B [real:#]
+and
+.B [tick:#]
+mean the connection includes real-time and tick conversion on the listed
+queue, respectively.
+
+.SS REMOVE ALL CONNECTIONS
+.TP
+.B \-x, \-\-removeall
+Remove all exported connections.
+
+.SH "SEE ALSO"
+aseqnet(1), aseqview(1)
+
+.SH AUTHOR
+Takashi Iwai <tiwai@suse.de>
diff --git a/seq/aconnect/aconnect.c b/seq/aconnect/aconnect.c
new file mode 100644
index 0000000..8c66cfd
--- /dev/null
+++ b/seq/aconnect/aconnect.c
@@ -0,0 +1,392 @@
+/*
+ * connect / disconnect two subscriber ports
+ *   ver.0.1.3
+ *
+ * Copyright (C) 1999 Takashi Iwai
+ * 
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ * 
+ *  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.
+ *
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <locale.h>
+#include <sys/ioctl.h>
+#include <alsa/asoundlib.h>
+#include "aconfig.h"
+#include "gettext.h"
+
+static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
+{
+	va_list arg;
+
+	if (err == ENOENT)	/* Ignore those misleading "warnings" */
+		return;
+	va_start(arg, fmt);
+	fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function);
+	vfprintf(stderr, fmt, arg);
+	if (err)
+		fprintf(stderr, ": %s", snd_strerror(err));
+	putc('\n', stderr);
+	va_end(arg);
+}
+
+static void usage(void)
+{
+	printf(_("aconnect - ALSA sequencer connection manager\n"));
+	printf(_("Copyright (C) 1999-2000 Takashi Iwai\n"));
+	printf(_("Usage:\n"));
+	printf(_(" * Connection/disconnection between two ports\n"));
+	printf(_("   aconnect [-options] sender receiver\n"));
+	printf(_("     sender, receiver = client:port pair\n"));
+	printf(_("     -d,--disconnect     disconnect\n"));
+	printf(_("     -e,--exclusive      exclusive connection\n"));
+	printf(_("     -r,--real #         convert real-time-stamp on queue\n"));
+	printf(_("     -t,--tick #         convert tick-time-stamp on queue\n"));
+	printf(_(" * List connected ports (no subscription action)\n"));
+	printf(_("   aconnect -i|-o [-options]\n"));
+	printf(_("     -i,--input          list input (readable) ports\n"));
+	printf(_("     -o,--output         list output (writable) ports\n"));
+	printf(_("     -l,--list           list current connections of each port\n"));
+	printf(_(" * Remove all exported connections\n"));
+	printf(_("     -x, --removeall\n"));
+}
+
+/*
+ * check permission (capability) of specified port
+ */
+
+#define LIST_INPUT	1
+#define LIST_OUTPUT	2
+
+#define perm_ok(pinfo,bits) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
+
+static int check_permission(snd_seq_port_info_t *pinfo, int perm)
+{
+	if (perm) {
+		if (perm & LIST_INPUT) {
+			if (perm_ok(pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))
+				goto __ok;
+		}
+		if (perm & LIST_OUTPUT) {
+			if (perm_ok(pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
+				goto __ok;
+		}
+		return 0;
+	}
+ __ok:
+	if (snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_NO_EXPORT)
+		return 0;
+	return 1;
+}
+
+/*
+ * list subscribers of specified type
+ */
+static void list_each_subs(snd_seq_t *seq, snd_seq_query_subscribe_t *subs, int type, const char *msg)
+{
+	int count = 0;
+	snd_seq_query_subscribe_set_type(subs, type);
+	snd_seq_query_subscribe_set_index(subs, 0);
+	while (snd_seq_query_port_subscribers(seq, subs) >= 0) {
+		const snd_seq_addr_t *addr;
+		if (count++ == 0)
+			printf("\t%s: ", msg);
+		else
+			printf(", ");
+		addr = snd_seq_query_subscribe_get_addr(subs);
+		printf("%d:%d", addr->client, addr->port);
+		if (snd_seq_query_subscribe_get_exclusive(subs))
+			printf("[ex]");
+		if (snd_seq_query_subscribe_get_time_update(subs))
+			printf("[%s:%d]",
+			       (snd_seq_query_subscribe_get_time_real(subs) ? "real" : "tick"),
+			       snd_seq_query_subscribe_get_queue(subs));
+		snd_seq_query_subscribe_set_index(subs, snd_seq_query_subscribe_get_index(subs) + 1);
+	}
+	if (count > 0)
+		printf("\n");
+}
+
+/*
+ * list subscribers
+ */
+static void list_subscribers(snd_seq_t *seq, const snd_seq_addr_t *addr)
+{
+	snd_seq_query_subscribe_t *subs;
+	snd_seq_query_subscribe_alloca(&subs);
+	snd_seq_query_subscribe_set_root(subs, addr);
+	list_each_subs(seq, subs, SND_SEQ_QUERY_SUBS_READ, _("Connecting To"));
+	list_each_subs(seq, subs, SND_SEQ_QUERY_SUBS_WRITE, _("Connected From"));
+}
+
+/*
+ * search all ports
+ */
+typedef void (*action_func_t)(snd_seq_t *seq, snd_seq_client_info_t *cinfo, snd_seq_port_info_t *pinfo, int count);
+
+static void do_search_port(snd_seq_t *seq, int perm, action_func_t do_action)
+{
+	snd_seq_client_info_t *cinfo;
+	snd_seq_port_info_t *pinfo;
+	int count;
+
+	snd_seq_client_info_alloca(&cinfo);
+	snd_seq_port_info_alloca(&pinfo);
+	snd_seq_client_info_set_client(cinfo, -1);
+	while (snd_seq_query_next_client(seq, cinfo) >= 0) {
+		/* reset query info */
+		snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
+		snd_seq_port_info_set_port(pinfo, -1);
+		count = 0;
+		while (snd_seq_query_next_port(seq, pinfo) >= 0) {
+			if (check_permission(pinfo, perm)) {
+				do_action(seq, cinfo, pinfo, count);
+				count++;
+			}
+		}
+	}
+}
+
+
+static void print_port(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
+		       snd_seq_port_info_t *pinfo, int count)
+{
+	if (! count) {
+		printf(_("client %d: '%s' [type=%s]\n"),
+		       snd_seq_client_info_get_client(cinfo),
+		       snd_seq_client_info_get_name(cinfo),
+		       (snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ?
+			_("user") : _("kernel")));
+	}
+	printf("  %3d '%-16s'\n",
+	       snd_seq_port_info_get_port(pinfo),
+	       snd_seq_port_info_get_name(pinfo));
+}
+
+static void print_port_and_subs(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
+				snd_seq_port_info_t *pinfo, int count)
+{
+	print_port(seq, cinfo, pinfo, count);
+	list_subscribers(seq, snd_seq_port_info_get_addr(pinfo));
+}
+
+
+/*
+ * remove all (exported) connections
+ */
+static void remove_connection(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
+			      snd_seq_port_info_t *pinfo, int count)
+{
+	snd_seq_query_subscribe_t *query;
+	snd_seq_port_info_t *port;
+	snd_seq_port_subscribe_t *subs;
+
+	snd_seq_query_subscribe_alloca(&query);
+	snd_seq_query_subscribe_set_root(query, snd_seq_port_info_get_addr(pinfo));
+	snd_seq_query_subscribe_set_type(query, SND_SEQ_QUERY_SUBS_READ);
+	snd_seq_query_subscribe_set_index(query, 0);
+
+	snd_seq_port_info_alloca(&port);
+	snd_seq_port_subscribe_alloca(&subs);
+
+	while (snd_seq_query_port_subscribers(seq, query) >= 0) {
+		const snd_seq_addr_t *sender = snd_seq_query_subscribe_get_root(query);
+		const snd_seq_addr_t *dest = snd_seq_query_subscribe_get_addr(query);
+
+		if (snd_seq_get_any_port_info(seq, dest->client, dest->port, port) < 0 ||
+		    !(snd_seq_port_info_get_capability(port) & SND_SEQ_PORT_CAP_SUBS_WRITE) ||
+		    (snd_seq_port_info_get_capability(port) & SND_SEQ_PORT_CAP_NO_EXPORT)) {
+			snd_seq_query_subscribe_set_index(query, snd_seq_query_subscribe_get_index(query) + 1);
+			continue;
+		}
+		snd_seq_port_subscribe_set_queue(subs, snd_seq_query_subscribe_get_queue(query));
+		snd_seq_port_subscribe_set_sender(subs, sender);
+		snd_seq_port_subscribe_set_dest(subs, dest);
+		if (snd_seq_unsubscribe_port(seq, subs) < 0) {
+			snd_seq_query_subscribe_set_index(query, snd_seq_query_subscribe_get_index(query) + 1);
+		}
+	}
+}
+
+static void remove_all_connections(snd_seq_t *seq)
+{
+	do_search_port(seq, 0, remove_connection);
+}
+
+
+/*
+ * main..
+ */
+
+enum {
+	SUBSCRIBE, UNSUBSCRIBE, LIST, REMOVE_ALL
+};
+
+static const struct option long_option[] = {
+	{"disconnect", 0, NULL, 'd'},
+	{"input", 0, NULL, 'i'},
+	{"output", 0, NULL, 'o'},
+	{"real", 1, NULL, 'r'},
+	{"tick", 1, NULL, 't'},
+	{"exclusive", 0, NULL, 'e'},
+	{"list", 0, NULL, 'l'},
+	{"removeall", 0, NULL, 'x'},
+	{NULL, 0, NULL, 0},
+};
+
+int main(int argc, char **argv)
+{
+	int c;
+	snd_seq_t *seq;
+	int queue = 0, convert_time = 0, convert_real = 0, exclusive = 0;
+	int command = SUBSCRIBE;
+	int list_perm = 0;
+	int client;
+	int list_subs = 0;
+	snd_seq_port_subscribe_t *subs;
+	snd_seq_addr_t sender, dest;
+
+#ifdef ENABLE_NLS
+	setlocale(LC_ALL, "");
+	textdomain(PACKAGE);
+#endif
+
+	while ((c = getopt_long(argc, argv, "dior:t:elx", long_option, NULL)) != -1) {
+		switch (c) {
+		case 'd':
+			command = UNSUBSCRIBE;
+			break;
+		case 'i':
+			command = LIST;
+			list_perm |= LIST_INPUT;
+			break;
+		case 'o':
+			command = LIST;
+			list_perm |= LIST_OUTPUT;
+			break;
+		case 'e':
+			exclusive = 1;
+			break;
+		case 'r':
+			queue = atoi(optarg);
+			convert_time = 1;
+			convert_real = 1;
+			break;
+		case 't':
+			queue = atoi(optarg);
+			convert_time = 1;
+			convert_real = 0;
+			break;
+		case 'l':
+			list_subs = 1;
+			break;
+		case 'x':
+			command = REMOVE_ALL;
+			break;
+		default:
+			usage();
+			exit(1);
+		}
+	}
+
+	if (snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
+		fprintf(stderr, _("can't open sequencer\n"));
+		return 1;
+	}
+	
+	snd_lib_error_set_handler(error_handler);
+
+	switch (command) {
+	case LIST:
+		do_search_port(seq, list_perm,
+			       list_subs ? print_port_and_subs : print_port);
+		snd_seq_close(seq);
+		return 0;
+	case REMOVE_ALL:
+		remove_all_connections(seq);
+		snd_seq_close(seq);
+		return 0;
+	}
+
+	/* connection or disconnection */
+
+	if (optind + 2 > argc) {
+		snd_seq_close(seq);
+		usage();
+		exit(1);
+	}
+
+	if ((client = snd_seq_client_id(seq)) < 0) {
+		snd_seq_close(seq);
+		fprintf(stderr, _("can't get client id\n"));
+		return 1;
+	}
+
+	/* set client info */
+	if (snd_seq_set_client_name(seq, "ALSA Connector") < 0) {
+		snd_seq_close(seq);
+		fprintf(stderr, _("can't set client info\n"));
+		return 1;
+	}
+
+	/* set subscription */
+	if (snd_seq_parse_address(seq, &sender, argv[optind]) < 0) {
+		snd_seq_close(seq);
+		fprintf(stderr, _("invalid sender address %s\n"), argv[optind]);
+		return 1;
+	}
+	if (snd_seq_parse_address(seq, &dest, argv[optind + 1]) < 0) {
+		snd_seq_close(seq);
+		fprintf(stderr, _("invalid destination address %s\n"), argv[optind + 1]);
+		return 1;
+	}
+	snd_seq_port_subscribe_alloca(&subs);
+	snd_seq_port_subscribe_set_sender(subs, &sender);
+	snd_seq_port_subscribe_set_dest(subs, &dest);
+	snd_seq_port_subscribe_set_queue(subs, queue);
+	snd_seq_port_subscribe_set_exclusive(subs, exclusive);
+	snd_seq_port_subscribe_set_time_update(subs, convert_time);
+	snd_seq_port_subscribe_set_time_real(subs, convert_real);
+
+	if (command == UNSUBSCRIBE) {
+		if (snd_seq_get_port_subscription(seq, subs) < 0) {
+			snd_seq_close(seq);
+			fprintf(stderr, _("No subscription is found\n"));
+			return 1;
+		}
+		if (snd_seq_unsubscribe_port(seq, subs) < 0) {
+			snd_seq_close(seq);
+			fprintf(stderr, _("Disconnection failed (%s)\n"), snd_strerror(errno));
+			return 1;
+		}
+	} else {
+		if (snd_seq_get_port_subscription(seq, subs) == 0) {
+			snd_seq_close(seq);
+			fprintf(stderr, _("Connection is already subscribed\n"));
+			return 1;
+		}
+		if (snd_seq_subscribe_port(seq, subs) < 0) {
+			snd_seq_close(seq);
+			fprintf(stderr, _("Connection failed (%s)\n"), snd_strerror(errno));
+			return 1;
+		}
+	}
+
+	snd_seq_close(seq);
+
+	return 0;
+}
diff --git a/seq/aplaymidi/Makefile.am b/seq/aplaymidi/Makefile.am
new file mode 100644
index 0000000..bed2a0e
--- /dev/null
+++ b/seq/aplaymidi/Makefile.am
@@ -0,0 +1,5 @@
+INCLUDES = -I$(top_srcdir)/include
+EXTRA_DIST = aplaymidi.1 arecordmidi.1
+
+bin_PROGRAMS = aplaymidi arecordmidi
+man_MANS = aplaymidi.1 arecordmidi.1
diff --git a/seq/aplaymidi/aplaymidi.1 b/seq/aplaymidi/aplaymidi.1
new file mode 100644
index 0000000..02fef9d
--- /dev/null
+++ b/seq/aplaymidi/aplaymidi.1
@@ -0,0 +1,63 @@
+.TH APLAYMIDI 1 "2 Nov 2011"
+
+.SH NAME
+aplaymidi \- play Standard MIDI Files
+
+.SH SYNOPSIS
+.B aplaymidi
+\-p client:port[,...] [\-d delay] midifile ...
+
+.SH DESCRIPTION
+.B aplaymidi
+is a command-line utility that plays the specified MIDI file(s) to one
+or more ALSA sequencer ports.
+
+.SH OPTIONS
+
+.TP
+.I \-h, \-\-help
+Prints a list of options.
+
+.TP
+.I \-V, \-\-version
+Prints the current version.
+
+.TP
+.I \-l, \-\-list
+Prints a list of possible output ports.
+
+.TP
+.I \-p, \-\-port=client:port,...
+Sets the sequencer port(s) to which the events in the MIDI file(s) are
+sent.
+
+A client can be specified by its number, its name, or a prefix of its
+name. A port is specified by its number; for port 0 of a client, the
+":0" part of the port specification can be omitted.
+
+Multiple ports can be specified to allow playback of MIDI file(s) that
+contain events for multiple devices (ports), as specified by "Port
+Number" meta events.
+
+For compatibility with
+.B pmidi(1),
+the port specification is taken from the
+.I ALSA_OUTPUT_PORTS
+environment variable if none is given on the command line.
+
+.TP
+.I \-d, \-\-delay=seconds
+Specifies how long to wait after the end of each MIDI file,
+to allow the last notes to die away.
+
+.SH BUGS
+.B aplaymidi
+handles "Port Number" meta events, but not "Port Name" meta events.
+
+.SH SEE ALSO
+pmidi(1)
+.br
+playmidi(1)
+
+.SH AUTHOR
+Clemens Ladisch <clemens@ladisch.de>
diff --git a/seq/aplaymidi/aplaymidi.c b/seq/aplaymidi/aplaymidi.c
new file mode 100644
index 0000000..ad508b3
--- /dev/null
+++ b/seq/aplaymidi/aplaymidi.c
@@ -0,0 +1,932 @@
+/*
+ * aplaymidi.c - play Standard MIDI Files to sequencer port(s)
+ *
+ * Copyright (c) 2004-2006 Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  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
+ */
+
+/* TODO: sequencer queue timer selection */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <alsa/asoundlib.h>
+#include "aconfig.h"
+#include "version.h"
+
+/*
+ * 31.25 kbaud, one start bit, eight data bits, two stop bits.
+ * (The MIDI spec says one stop bit, but every transmitter uses two, just to be
+ * sure, so we better not exceed that to avoid overflowing the output buffer.)
+ */
+#define MIDI_BYTES_PER_SEC (31250 / (1 + 8 + 2))
+
+/*
+ * A MIDI event after being parsed/loaded from the file.
+ * There could be made a case for using snd_seq_event_t instead.
+ */
+struct event {
+	struct event *next;		/* linked list */
+
+	unsigned char type;		/* SND_SEQ_EVENT_xxx */
+	unsigned char port;		/* port index */
+	unsigned int tick;
+	union {
+		unsigned char d[3];	/* channel and data bytes */
+		int tempo;
+		unsigned int length;	/* length of sysex data */
+	} data;
+	unsigned char sysex[0];
+};
+
+struct track {
+	struct event *first_event;	/* list of all events in this track */
+	int end_tick;			/* length of this track */
+
+	struct event *current_event;	/* used while loading and playing */
+};
+
+static snd_seq_t *seq;
+static int client;
+static int port_count;
+static snd_seq_addr_t *ports;
+static int queue;
+static int end_delay = 2;
+static const char *file_name;
+static FILE *file;
+static int file_offset;		/* current offset in input file */
+static int num_tracks;
+static struct track *tracks;
+static int smpte_timing;
+
+/* prints an error message to stderr */
+static void errormsg(const char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	vfprintf(stderr, msg, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+}
+
+/* prints an error message to stderr, and dies */
+static void fatal(const char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	vfprintf(stderr, msg, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+	exit(EXIT_FAILURE);
+}
+
+/* memory allocation error handling */
+static void check_mem(void *p)
+{
+	if (!p)
+		fatal("Out of memory");
+}
+
+/* error handling for ALSA functions */
+static void check_snd(const char *operation, int err)
+{
+	if (err < 0)
+		fatal("Cannot %s - %s", operation, snd_strerror(err));
+}
+
+static void init_seq(void)
+{
+	int err;
+
+	/* open sequencer */
+	err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
+	check_snd("open sequencer", err);
+
+	/* set our name (otherwise it's "Client-xxx") */
+	err = snd_seq_set_client_name(seq, "aplaymidi");
+	check_snd("set client name", err);
+
+	/* find out who we actually are */
+	client = snd_seq_client_id(seq);
+	check_snd("get client id", client);
+}
+
+/* parses one or more port addresses from the string */
+static void parse_ports(const char *arg)
+{
+	char *buf, *s, *port_name;
+	int err;
+
+	/* make a copy of the string because we're going to modify it */
+	buf = strdup(arg);
+	check_mem(buf);
+
+	for (port_name = s = buf; s; port_name = s + 1) {
+		/* Assume that ports are separated by commas.  We don't use
+		 * spaces because those are valid in client names. */
+		s = strchr(port_name, ',');
+		if (s)
+			*s = '\0';
+
+		++port_count;
+		ports = realloc(ports, port_count * sizeof(snd_seq_addr_t));
+		check_mem(ports);
+
+		err = snd_seq_parse_address(seq, &ports[port_count - 1], port_name);
+		if (err < 0)
+			fatal("Invalid port %s - %s", port_name, snd_strerror(err));
+	}
+
+	free(buf);
+}
+
+static void create_source_port(void)
+{
+	snd_seq_port_info_t *pinfo;
+	int err;
+
+	snd_seq_port_info_alloca(&pinfo);
+
+	/* the first created port is 0 anyway, but let's make sure ... */
+	snd_seq_port_info_set_port(pinfo, 0);
+	snd_seq_port_info_set_port_specified(pinfo, 1);
+
+	snd_seq_port_info_set_name(pinfo, "aplaymidi");
+
+	snd_seq_port_info_set_capability(pinfo, 0); /* sic */
+	snd_seq_port_info_set_type(pinfo,
+				   SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+				   SND_SEQ_PORT_TYPE_APPLICATION);
+
+	err = snd_seq_create_port(seq, pinfo);
+	check_snd("create port", err);
+}
+
+static void create_queue(void)
+{
+	queue = snd_seq_alloc_named_queue(seq, "aplaymidi");
+	check_snd("create queue", queue);
+	/* the queue is now locked, which is just fine */
+}
+
+static void connect_ports(void)
+{
+	int i, err;
+
+	/*
+	 * We send MIDI events with explicit destination addresses, so we don't
+	 * need any connections to the playback ports.  But we connect to those
+	 * anyway to force any underlying RawMIDI ports to remain open while
+	 * we're playing - otherwise, ALSA would reset the port after every
+	 * event.
+	 */
+	for (i = 0; i < port_count; ++i) {
+		err = snd_seq_connect_to(seq, 0, ports[i].client, ports[i].port);
+		if (err < 0)
+			fatal("Cannot connect to port %d:%d - %s",
+			      ports[i].client, ports[i].port, snd_strerror(err));
+	}
+}
+
+static int read_byte(void)
+{
+	++file_offset;
+	return getc(file);
+}
+
+/* reads a little-endian 32-bit integer */
+static int read_32_le(void)
+{
+	int value;
+	value = read_byte();
+	value |= read_byte() << 8;
+	value |= read_byte() << 16;
+	value |= read_byte() << 24;
+	return !feof(file) ? value : -1;
+}
+
+/* reads a 4-character identifier */
+static int read_id(void)
+{
+	return read_32_le();
+}
+#define MAKE_ID(c1, c2, c3, c4) ((c1) | ((c2) << 8) | ((c3) << 16) | ((c4) << 24))
+
+/* reads a fixed-size big-endian number */
+static int read_int(int bytes)
+{
+	int c, value = 0;
+
+	do {
+		c = read_byte();
+		if (c == EOF)
+			return -1;
+		value = (value << 8) | c;
+	} while (--bytes);
+	return value;
+}
+
+/* reads a variable-length number */
+static int read_var(void)
+{
+	int value, c;
+
+	c = read_byte();
+	value = c & 0x7f;
+	if (c & 0x80) {
+		c = read_byte();
+		value = (value << 7) | (c & 0x7f);
+		if (c & 0x80) {
+			c = read_byte();
+			value = (value << 7) | (c & 0x7f);
+			if (c & 0x80) {
+				c = read_byte();
+				value = (value << 7) | c;
+				if (c & 0x80)
+					return -1;
+			}
+		}
+	}
+	return !feof(file) ? value : -1;
+}
+
+/* allocates a new event */
+static struct event *new_event(struct track *track, int sysex_length)
+{
+	struct event *event;
+
+	event = malloc(sizeof(struct event) + sysex_length);
+	check_mem(event);
+
+	event->next = NULL;
+
+	/* append at the end of the track's linked list */
+	if (track->current_event)
+		track->current_event->next = event;
+	else
+		track->first_event = event;
+	track->current_event = event;
+
+	return event;
+}
+
+static void skip(int bytes)
+{
+	while (bytes > 0)
+		read_byte(), --bytes;
+}
+
+/* reads one complete track from the file */
+static int read_track(struct track *track, int track_end)
+{
+	int tick = 0;
+	unsigned char last_cmd = 0;
+	unsigned char port = 0;
+
+	/* the current file position is after the track ID and length */
+	while (file_offset < track_end) {
+		unsigned char cmd;
+		struct event *event;
+		int delta_ticks, len, c;
+
+		delta_ticks = read_var();
+		if (delta_ticks < 0)
+			break;
+		tick += delta_ticks;
+
+		c = read_byte();
+		if (c < 0)
+			break;
+
+		if (c & 0x80) {
+			/* have command */
+			cmd = c;
+			if (cmd < 0xf0)
+				last_cmd = cmd;
+		} else {
+			/* running status */
+			ungetc(c, file);
+			file_offset--;
+			cmd = last_cmd;
+			if (!cmd)
+				goto _error;
+		}
+
+		switch (cmd >> 4) {
+			/* maps SMF events to ALSA sequencer events */
+			static const unsigned char cmd_type[] = {
+				[0x8] = SND_SEQ_EVENT_NOTEOFF,
+				[0x9] = SND_SEQ_EVENT_NOTEON,
+				[0xa] = SND_SEQ_EVENT_KEYPRESS,
+				[0xb] = SND_SEQ_EVENT_CONTROLLER,
+				[0xc] = SND_SEQ_EVENT_PGMCHANGE,
+				[0xd] = SND_SEQ_EVENT_CHANPRESS,
+				[0xe] = SND_SEQ_EVENT_PITCHBEND
+			};
+
+		case 0x8: /* channel msg with 2 parameter bytes */
+		case 0x9:
+		case 0xa:
+		case 0xb:
+		case 0xe:
+			event = new_event(track, 0);
+			event->type = cmd_type[cmd >> 4];
+			event->port = port;
+			event->tick = tick;
+			event->data.d[0] = cmd & 0x0f;
+			event->data.d[1] = read_byte() & 0x7f;
+			event->data.d[2] = read_byte() & 0x7f;
+			break;
+
+		case 0xc: /* channel msg with 1 parameter byte */
+		case 0xd:
+			event = new_event(track, 0);
+			event->type = cmd_type[cmd >> 4];
+			event->port = port;
+			event->tick = tick;
+			event->data.d[0] = cmd & 0x0f;
+			event->data.d[1] = read_byte() & 0x7f;
+			break;
+
+		case 0xf:
+			switch (cmd) {
+			case 0xf0: /* sysex */
+			case 0xf7: /* continued sysex, or escaped commands */
+				len = read_var();
+				if (len < 0)
+					goto _error;
+				if (cmd == 0xf0)
+					++len;
+				event = new_event(track, len);
+				event->type = SND_SEQ_EVENT_SYSEX;
+				event->port = port;
+				event->tick = tick;
+				event->data.length = len;
+				if (cmd == 0xf0) {
+					event->sysex[0] = 0xf0;
+					c = 1;
+				} else {
+					c = 0;
+				}
+				for (; c < len; ++c)
+					event->sysex[c] = read_byte();
+				break;
+
+			case 0xff: /* meta event */
+				c = read_byte();
+				len = read_var();
+				if (len < 0)
+					goto _error;
+
+				switch (c) {
+				case 0x21: /* port number */
+					if (len < 1)
+						goto _error;
+					port = read_byte() % port_count;
+					skip(len - 1);
+					break;
+
+				case 0x2f: /* end of track */
+					track->end_tick = tick;
+					skip(track_end - file_offset);
+					return 1;
+
+				case 0x51: /* tempo */
+					if (len < 3)
+						goto _error;
+					if (smpte_timing) {
+						/* SMPTE timing doesn't change */
+						skip(len);
+					} else {
+						event = new_event(track, 0);
+						event->type = SND_SEQ_EVENT_TEMPO;
+						event->port = port;
+						event->tick = tick;
+						event->data.tempo = read_byte() << 16;
+						event->data.tempo |= read_byte() << 8;
+						event->data.tempo |= read_byte();
+						skip(len - 3);
+					}
+					break;
+
+				default: /* ignore all other meta events */
+					skip(len);
+					break;
+				}
+				break;
+
+			default: /* invalid Fx command */
+				goto _error;
+			}
+			break;
+
+		default: /* cannot happen */
+			goto _error;
+		}
+	}
+_error:
+	errormsg("%s: invalid MIDI data (offset %#x)", file_name, file_offset);
+	return 0;
+}
+
+/* reads an entire MIDI file */
+static int read_smf(void)
+{
+	int header_len, type, time_division, i, err;
+	snd_seq_queue_tempo_t *queue_tempo;
+
+	/* the curren position is immediately after the "MThd" id */
+	header_len = read_int(4);
+	if (header_len < 6) {
+invalid_format:
+		errormsg("%s: invalid file format", file_name);
+		return 0;
+	}
+
+	type = read_int(2);
+	if (type != 0 && type != 1) {
+		errormsg("%s: type %d format is not supported", file_name, type);
+		return 0;
+	}
+
+	num_tracks = read_int(2);
+	if (num_tracks < 1 || num_tracks > 1000) {
+		errormsg("%s: invalid number of tracks (%d)", file_name, num_tracks);
+		num_tracks = 0;
+		return 0;
+	}
+	tracks = calloc(num_tracks, sizeof(struct track));
+	if (!tracks) {
+		errormsg("out of memory");
+		num_tracks = 0;
+		return 0;
+	}
+
+	time_division = read_int(2);
+	if (time_division < 0)
+		goto invalid_format;
+
+	/* interpret and set tempo */
+	snd_seq_queue_tempo_alloca(&queue_tempo);
+	smpte_timing = !!(time_division & 0x8000);
+	if (!smpte_timing) {
+		/* time_division is ticks per quarter */
+		snd_seq_queue_tempo_set_tempo(queue_tempo, 500000); /* default: 120 bpm */
+		snd_seq_queue_tempo_set_ppq(queue_tempo, time_division);
+	} else {
+		/* upper byte is negative frames per second */
+		i = 0x80 - ((time_division >> 8) & 0x7f);
+		/* lower byte is ticks per frame */
+		time_division &= 0xff;
+		/* now pretend that we have quarter-note based timing */
+		switch (i) {
+		case 24:
+			snd_seq_queue_tempo_set_tempo(queue_tempo, 500000);
+			snd_seq_queue_tempo_set_ppq(queue_tempo, 12 * time_division);
+			break;
+		case 25:
+			snd_seq_queue_tempo_set_tempo(queue_tempo, 400000);
+			snd_seq_queue_tempo_set_ppq(queue_tempo, 10 * time_division);
+			break;
+		case 29: /* 30 drop-frame */
+			snd_seq_queue_tempo_set_tempo(queue_tempo, 100000000);
+			snd_seq_queue_tempo_set_ppq(queue_tempo, 2997 * time_division);
+			break;
+		case 30:
+			snd_seq_queue_tempo_set_tempo(queue_tempo, 500000);
+			snd_seq_queue_tempo_set_ppq(queue_tempo, 15 * time_division);
+			break;
+		default:
+			errormsg("%s: invalid number of SMPTE frames per second (%d)",
+				 file_name, i);
+			return 0;
+		}
+	}
+	err = snd_seq_set_queue_tempo(seq, queue, queue_tempo);
+	if (err < 0) {
+		errormsg("Cannot set queue tempo (%u/%i)",
+			 snd_seq_queue_tempo_get_tempo(queue_tempo),
+			 snd_seq_queue_tempo_get_ppq(queue_tempo));
+		return 0;
+	}
+
+	/* read tracks */
+	for (i = 0; i < num_tracks; ++i) {
+		int len;
+
+		/* search for MTrk chunk */
+		for (;;) {
+			int id = read_id();
+			len = read_int(4);
+			if (feof(file)) {
+				errormsg("%s: unexpected end of file", file_name);
+				return 0;
+			}
+			if (len < 0 || len >= 0x10000000) {
+				errormsg("%s: invalid chunk length %d", file_name, len);
+				return 0;
+			}
+			if (id == MAKE_ID('M', 'T', 'r', 'k'))
+				break;
+			skip(len);
+		}
+		if (!read_track(&tracks[i], file_offset + len))
+			return 0;
+	}
+	return 1;
+}
+
+static int read_riff(void)
+{
+	/* skip file length */
+	read_byte();
+	read_byte();
+	read_byte();
+	read_byte();
+
+	/* check file type ("RMID" = RIFF MIDI) */
+	if (read_id() != MAKE_ID('R', 'M', 'I', 'D')) {
+invalid_format:
+		errormsg("%s: invalid file format", file_name);
+		return 0;
+	}
+	/* search for "data" chunk */
+	for (;;) {
+		int id = read_id();
+		int len = read_32_le();
+		if (feof(file)) {
+data_not_found:
+			errormsg("%s: data chunk not found", file_name);
+			return 0;
+		}
+		if (id == MAKE_ID('d', 'a', 't', 'a'))
+			break;
+		if (len < 0)
+			goto data_not_found;
+		skip((len + 1) & ~1);
+	}
+	/* the "data" chunk must contain data in SMF format */
+	if (read_id() != MAKE_ID('M', 'T', 'h', 'd'))
+		goto invalid_format;
+	return read_smf();
+}
+
+static void cleanup_file_data(void)
+{
+	int i;
+	struct event *event;
+
+	for (i = 0; i < num_tracks; ++i) {
+		event = tracks[i].first_event;
+		while (event) {
+			struct event *next = event->next;
+			free(event);
+			event = next;
+		}
+	}
+	num_tracks = 0;
+	free(tracks);
+	tracks = NULL;
+}
+
+static void handle_big_sysex(snd_seq_event_t *ev)
+{
+	unsigned int length;
+	ssize_t event_size;
+	int err;
+
+	length = ev->data.ext.len;
+	if (length > MIDI_BYTES_PER_SEC)
+		ev->data.ext.len = MIDI_BYTES_PER_SEC;
+	event_size = snd_seq_event_length(ev);
+	if (event_size + 1 > snd_seq_get_output_buffer_size(seq)) {
+		err = snd_seq_drain_output(seq);
+		check_snd("drain output", err);
+		err = snd_seq_set_output_buffer_size(seq, event_size + 1);
+		check_snd("set output buffer size", err);
+	}
+	while (length > MIDI_BYTES_PER_SEC) {
+		err = snd_seq_event_output(seq, ev);
+		check_snd("output event", err);
+		err = snd_seq_drain_output(seq);
+		check_snd("drain output", err);
+		err = snd_seq_sync_output_queue(seq);
+		check_snd("sync output", err);
+		if (sleep(1))
+			fatal("aborted");
+		ev->data.ext.ptr += MIDI_BYTES_PER_SEC;
+		length -= MIDI_BYTES_PER_SEC;
+	}
+	ev->data.ext.len = length;
+}
+
+static void play_midi(void)
+{
+	snd_seq_event_t ev;
+	int i, max_tick, err;
+
+	/* calculate length of the entire file */
+	max_tick = -1;
+	for (i = 0; i < num_tracks; ++i) {
+		if (tracks[i].end_tick > max_tick)
+			max_tick = tracks[i].end_tick;
+	}
+
+	/* initialize current position in each track */
+	for (i = 0; i < num_tracks; ++i)
+		tracks[i].current_event = tracks[i].first_event;
+
+	/* common settings for all our events */
+	snd_seq_ev_clear(&ev);
+	ev.queue = queue;
+	ev.source.port = 0;
+	ev.flags = SND_SEQ_TIME_STAMP_TICK;
+
+	err = snd_seq_start_queue(seq, queue, NULL);
+	check_snd("start queue", err);
+	/* The queue won't be started until the START_QUEUE event is
+	 * actually drained to the kernel, which is exactly what we want. */
+
+	for (;;) {
+		struct event* event = NULL;
+		struct track* event_track = NULL;
+		int i, min_tick = max_tick + 1;
+
+		/* search next event */
+		for (i = 0; i < num_tracks; ++i) {
+			struct track *track = &tracks[i];
+			struct event *e2 = track->current_event;
+			if (e2 && e2->tick < min_tick) {
+				min_tick = e2->tick;
+				event = e2;
+				event_track = track;
+			}
+		}
+		if (!event)
+			break; /* end of song reached */
+
+		/* advance pointer to next event */
+		event_track->current_event = event->next;
+
+		/* output the event */
+		ev.type = event->type;
+		ev.time.tick = event->tick;
+		ev.dest = ports[event->port];
+		switch (ev.type) {
+		case SND_SEQ_EVENT_NOTEON:
+		case SND_SEQ_EVENT_NOTEOFF:
+		case SND_SEQ_EVENT_KEYPRESS:
+			snd_seq_ev_set_fixed(&ev);
+			ev.data.note.channel = event->data.d[0];
+			ev.data.note.note = event->data.d[1];
+			ev.data.note.velocity = event->data.d[2];
+			break;
+		case SND_SEQ_EVENT_CONTROLLER:
+			snd_seq_ev_set_fixed(&ev);
+			ev.data.control.channel = event->data.d[0];
+			ev.data.control.param = event->data.d[1];
+			ev.data.control.value = event->data.d[2];
+			break;
+		case SND_SEQ_EVENT_PGMCHANGE:
+		case SND_SEQ_EVENT_CHANPRESS:
+			snd_seq_ev_set_fixed(&ev);
+			ev.data.control.channel = event->data.d[0];
+			ev.data.control.value = event->data.d[1];
+			break;
+		case SND_SEQ_EVENT_PITCHBEND:
+			snd_seq_ev_set_fixed(&ev);
+			ev.data.control.channel = event->data.d[0];
+			ev.data.control.value =
+				((event->data.d[1]) |
+				 ((event->data.d[2]) << 7)) - 0x2000;
+			break;
+		case SND_SEQ_EVENT_SYSEX:
+			snd_seq_ev_set_variable(&ev, event->data.length,
+						event->sysex);
+			handle_big_sysex(&ev);
+			break;
+		case SND_SEQ_EVENT_TEMPO:
+			snd_seq_ev_set_fixed(&ev);
+			ev.dest.client = SND_SEQ_CLIENT_SYSTEM;
+			ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
+			ev.data.queue.queue = queue;
+			ev.data.queue.param.value = event->data.tempo;
+			break;
+		default:
+			fatal("Invalid event type %d!", ev.type);
+		}
+
+		/* this blocks when the output pool has been filled */
+		err = snd_seq_event_output(seq, &ev);
+		check_snd("output event", err);
+	}
+
+	/* schedule queue stop at end of song */
+	snd_seq_ev_set_fixed(&ev);
+	ev.type = SND_SEQ_EVENT_STOP;
+	ev.time.tick = max_tick;
+	ev.dest.client = SND_SEQ_CLIENT_SYSTEM;
+	ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
+	ev.data.queue.queue = queue;
+	err = snd_seq_event_output(seq, &ev);
+	check_snd("output event", err);
+
+	/* make sure that the sequencer sees all our events */
+	err = snd_seq_drain_output(seq);
+	check_snd("drain output", err);
+
+	/*
+	 * There are three possibilities how to wait until all events have
+	 * been played:
+	 * 1) send an event back to us (like pmidi does), and wait for it;
+	 * 2) wait for the EVENT_STOP notification for our queue which is sent
+	 *    by the system timer port (this would require a subscription);
+	 * 3) wait until the output pool is empty.
+	 * The last is the simplest.
+	 */
+	err = snd_seq_sync_output_queue(seq);
+	check_snd("sync output", err);
+
+	/* give the last notes time to die away */
+	if (end_delay > 0)
+		sleep(end_delay);
+}
+
+static void play_file(void)
+{
+	int ok;
+
+	if (!strcmp(file_name, "-"))
+		file = stdin;
+	else
+		file = fopen(file_name, "rb");
+	if (!file) {
+		errormsg("Cannot open %s - %s", file_name, strerror(errno));
+		return;
+	}
+
+	file_offset = 0;
+	ok = 0;
+
+	switch (read_id()) {
+	case MAKE_ID('M', 'T', 'h', 'd'):
+		ok = read_smf();
+		break;
+	case MAKE_ID('R', 'I', 'F', 'F'):
+		ok = read_riff();
+		break;
+	default:
+		errormsg("%s is not a Standard MIDI File", file_name);
+		break;
+	}
+
+	if (file != stdin)
+		fclose(file);
+
+	if (ok)
+		play_midi();
+
+	cleanup_file_data();
+}
+
+static void list_ports(void)
+{
+	snd_seq_client_info_t *cinfo;
+	snd_seq_port_info_t *pinfo;
+
+	snd_seq_client_info_alloca(&cinfo);
+	snd_seq_port_info_alloca(&pinfo);
+
+	puts(" Port    Client name                      Port name");
+
+	snd_seq_client_info_set_client(cinfo, -1);
+	while (snd_seq_query_next_client(seq, cinfo) >= 0) {
+		int client = snd_seq_client_info_get_client(cinfo);
+
+		snd_seq_port_info_set_client(pinfo, client);
+		snd_seq_port_info_set_port(pinfo, -1);
+		while (snd_seq_query_next_port(seq, pinfo) >= 0) {
+			/* port must understand MIDI messages */
+			if (!(snd_seq_port_info_get_type(pinfo)
+			      & SND_SEQ_PORT_TYPE_MIDI_GENERIC))
+				continue;
+			/* we need both WRITE and SUBS_WRITE */
+			if ((snd_seq_port_info_get_capability(pinfo)
+			     & (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE))
+			    != (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE))
+				continue;
+			printf("%3d:%-3d  %-32.32s %s\n",
+			       snd_seq_port_info_get_client(pinfo),
+			       snd_seq_port_info_get_port(pinfo),
+			       snd_seq_client_info_get_name(cinfo),
+			       snd_seq_port_info_get_name(pinfo));
+		}
+	}
+}
+
+static void usage(const char *argv0)
+{
+	printf(
+		"Usage: %s -p client:port[,...] [-d delay] midifile ...\n"
+		"-h, --help                  this help\n"
+		"-V, --version               print current version\n"
+		"-l, --list                  list all possible output ports\n"
+		"-p, --port=client:port,...  set port(s) to play to\n"
+		"-d, --delay=seconds         delay after song ends\n",
+		argv0);
+}
+
+static void version(void)
+{
+	puts("aplaymidi version " SND_UTIL_VERSION_STR);
+}
+
+int main(int argc, char *argv[])
+{
+	static const char short_options[] = "hVlp:d:";
+	static const struct option long_options[] = {
+		{"help", 0, NULL, 'h'},
+		{"version", 0, NULL, 'V'},
+		{"list", 0, NULL, 'l'},
+		{"port", 1, NULL, 'p'},
+		{"delay", 1, NULL, 'd'},
+		{}
+	};
+	int c;
+	int do_list = 0;
+
+	init_seq();
+
+	while ((c = getopt_long(argc, argv, short_options,
+				long_options, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			usage(argv[0]);
+			return 0;
+		case 'V':
+			version();
+			return 0;
+		case 'l':
+			do_list = 1;
+			break;
+		case 'p':
+			parse_ports(optarg);
+			break;
+		case 'd':
+			end_delay = atoi(optarg);
+			break;
+		default:
+			usage(argv[0]);
+			return 1;
+		}
+	}
+
+	if (do_list) {
+		list_ports();
+	} else {
+		if (port_count < 1) {
+			/* use env var for compatibility with pmidi */
+			const char *ports_str = getenv("ALSA_OUTPUT_PORTS");
+			if (ports_str)
+				parse_ports(ports_str);
+			if (port_count < 1) {
+				errormsg("Please specify at least one port with --port.");
+				return 1;
+			}
+		}
+		if (optind >= argc) {
+			errormsg("Please specify a file to play.");
+			return 1;
+		}
+
+		create_source_port();
+		create_queue();
+		connect_ports();
+
+		for (; optind < argc; ++optind) {
+			file_name = argv[optind];
+			play_file();
+		}
+	}
+	snd_seq_close(seq);
+	return 0;
+}
diff --git a/seq/aplaymidi/arecordmidi.1 b/seq/aplaymidi/arecordmidi.1
new file mode 100644
index 0000000..78b3a3a
--- /dev/null
+++ b/seq/aplaymidi/arecordmidi.1
@@ -0,0 +1,82 @@
+.TH ARECORDMIDI 1 "17 Sep 2007"
+
+.SH NAME
+arecordmidi \- record Standard MIDI Files
+
+.SH SYNOPSIS
+.B arecordmidi
+\-p client:port[,...] [options] midifile
+
+.SH DESCRIPTION
+.B arecordmidi
+is a command-line utility that records a Standard MIDI File from one or
+more ALSA sequencer ports.
+
+To stop recording, press Ctrl+C.
+
+.SH OPTIONS
+
+.TP
+.I \-h,\-\-help
+Prints a list of options.
+
+.TP
+.I \-V,\-\-version
+Prints the current version.
+
+.TP
+.I \-l,\-\-list
+Prints a list of possible input ports.
+
+.TP
+.I \-p,\-\-port=client:port,...
+Sets the sequencer port(s) from which events are recorded.
+
+A client can be specified by its number, its name, or a prefix of its
+name. A port is specified by its number; for port 0 of a client, the
+":0" part of the port specification can be omitted.
+
+.TP
+.I \-b,\-\-bpm=beats
+Sets the musical tempo of the MIDI file, in beats per minute.
+The default value is 120 BPM.
+
+.TP
+.I \-f,\-\-fps=frames
+Sets the SMPTE resolution, in frames per second.
+Possible values are 24, 25, 29.97 (for 30 drop-frame), and 30.
+
+.TP
+.I \-t,\-\-ticks=ticks
+Sets the resolution of timestamps (ticks) in the MIDI file,
+in ticks per beat (when using musical tempo) or ticks per frame
+(when using SMPTE timing).
+The default value is 384 ticks/beat or 40 ticks/frame, respectively.
+
+.TP
+.I \-s,\-\-split\-channels
+Specifies that the data for each MIDI channel should be written to a
+separate track in the MIDI file.
+This will result in a "format 1" file.
+Otherwise, when there is only one track,
+.B arecordmidi
+will generate a "format 0" file.
+
+.TP
+.I \-m,\-\-metronome=client:port
+Plays a metronome signal on the specified sequencer port.
+
+Metronome sounds are played on channel 10, MIDI notes 33 & 34 (GM2/GS/XG 
+metronome standard notes), with velocity 100 and duration 1.
+
+.TP
+.I \-i,\-\-timesig=numerator:denominator
+Sets the time signature for the MIDI file and metronome.
+
+The time signature is specified as usual with two numbers, representing
+the numerator and denominator of the time signature as it would be 
+notated. The denominator must be a power of two. Both numbers should be
+separated by a colon. The time signature is 4:4 by default.
+
+.SH AUTHOR
+Clemens Ladisch <clemens@ladisch.de>
diff --git a/seq/aplaymidi/arecordmidi.c b/seq/aplaymidi/arecordmidi.c
new file mode 100644
index 0000000..9628086
--- /dev/null
+++ b/seq/aplaymidi/arecordmidi.c
@@ -0,0 +1,880 @@
+/*
+ * arecordmidi.c - record standard MIDI files from sequencer ports
+ *
+ * Copyright (c) 2004-2005 Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  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
+ */
+
+/* TODO: sequencer queue timer selection */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/poll.h>
+#include <alsa/asoundlib.h>
+#include "aconfig.h"
+#include "version.h"
+
+#define BUFFER_SIZE 4088
+
+/* linked list of buffers, stores data as in the .mid file */
+struct buffer {
+	struct buffer *next;
+	unsigned char buf[BUFFER_SIZE];
+};
+
+struct smf_track {
+	int size;			/* size of entire data */
+	int cur_buf_size;		/* size of cur_buf */
+	struct buffer *cur_buf;
+	snd_seq_tick_time_t last_tick;	/* end of track */
+	unsigned char last_command;	/* used for running status */
+	int used;			/* anything record on this track */
+	struct buffer first_buf;	/* list head */
+};
+
+/* timing/sysex + 16 channels */
+#define TRACKS_PER_PORT 17
+
+/* metronome settings */
+/* TODO: create options for this */
+#define METRONOME_CHANNEL 9
+#define METRONOME_STRONG_NOTE 34
+#define METRONOME_WEAK_NOTE 33
+#define METRONOME_VELOCITY 100
+#define METRONOME_PROGRAM 0
+
+static snd_seq_t *seq;
+static int client;
+static int port_count;
+static snd_seq_addr_t *ports;
+static int queue;
+static int smpte_timing = 0;
+static int beats = 120;
+static int frames;
+static int ticks = 0;
+static FILE *file;
+static int channel_split;
+static int num_tracks;
+static struct smf_track *tracks;
+static volatile sig_atomic_t stop = 0;
+static int use_metronome = 0;
+static snd_seq_addr_t metronome_port;
+static int metronome_weak_note = METRONOME_WEAK_NOTE;
+static int metronome_strong_note = METRONOME_STRONG_NOTE;
+static int metronome_velocity = METRONOME_VELOCITY;
+static int metronome_program = METRONOME_PROGRAM;
+static int metronome_channel = METRONOME_CHANNEL;
+static int ts_num = 4; /* time signature: numerator */
+static int ts_div = 4; /* time signature: denominator */
+static int ts_dd = 2; /* time signature: denominator as a power of two */
+
+
+/* prints an error message to stderr, and dies */
+static void fatal(const char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	vfprintf(stderr, msg, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+	exit(EXIT_FAILURE);
+}
+
+/* memory allocation error handling */
+static void check_mem(void *p)
+{
+	if (!p)
+		fatal("Out of memory");
+}
+
+/* error handling for ALSA functions */
+static void check_snd(const char *operation, int err)
+{
+	if (err < 0)
+		fatal("Cannot %s - %s", operation, snd_strerror(err));
+}
+
+static void init_seq(void)
+{
+	int err;
+
+	/* open sequencer */
+	err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
+	check_snd("open sequencer", err);
+
+	/* find out our client's id */
+	client = snd_seq_client_id(seq);
+	check_snd("get client id", client);
+
+	/* set our client's name */
+	err = snd_seq_set_client_name(seq, "arecordmidi");
+	check_snd("set client name", err);
+}
+
+/* parses one or more port addresses from the string */
+static void parse_ports(const char *arg)
+{
+	char *buf, *s, *port_name;
+	int err;
+
+	/* make a copy of the string because we're going to modify it */
+	buf = strdup(arg);
+	check_mem(buf);
+
+	for (port_name = s = buf; s; port_name = s + 1) {
+		/* Assume that ports are separated by commas.  We don't use
+		 * spaces because those are valid in client names. */
+		s = strchr(port_name, ',');
+		if (s)
+			*s = '\0';
+
+		++port_count;
+		ports = realloc(ports, port_count * sizeof(snd_seq_addr_t));
+		check_mem(ports);
+
+		err = snd_seq_parse_address(seq, &ports[port_count - 1], port_name);
+		if (err < 0)
+			fatal("Invalid port %s - %s", port_name, snd_strerror(err));
+	}
+
+	free(buf);
+}
+
+/* parses the metronome port address */
+static void init_metronome(const char *arg)
+{
+	int err;
+
+	err = snd_seq_parse_address(seq, &metronome_port, arg);
+	if (err < 0)
+		fatal("Invalid port %s - %s", arg, snd_strerror(err));
+	use_metronome = 1;
+}
+
+/* parses time signature specification */
+static void time_signature(const char *arg)
+{
+	long x = 0;
+	char *sep;
+
+	x = strtol(arg, &sep, 10);
+	if (x < 1 || x > 64 || *sep != ':')
+		fatal("Invalid time signature (%s)", arg);
+	ts_num = x;
+	x = strtol(++sep, NULL, 10);
+	if (x < 1 || x > 64)
+		fatal("Invalid time signature (%s)", arg);
+	ts_div = x;
+	for (ts_dd = 0; x > 1; x /= 2)
+		++ts_dd;
+}
+
+/*
+ * Metronome implementation
+ */
+static void metronome_note(unsigned char note, unsigned int tick)
+{
+	snd_seq_event_t ev;
+	snd_seq_ev_clear(&ev);
+	snd_seq_ev_set_note(&ev, metronome_channel, note, metronome_velocity, 1);
+	snd_seq_ev_schedule_tick(&ev, queue, 0, tick);
+	snd_seq_ev_set_source(&ev, port_count);
+	snd_seq_ev_set_subs(&ev);
+	snd_seq_event_output(seq, &ev);
+}
+
+static void metronome_echo(unsigned int tick)
+{
+	snd_seq_event_t ev;
+	snd_seq_ev_clear(&ev);
+	ev.type = SND_SEQ_EVENT_USR0;
+	snd_seq_ev_schedule_tick(&ev, queue, 0, tick);
+	snd_seq_ev_set_source(&ev, port_count);
+	snd_seq_ev_set_dest(&ev, client, port_count);
+	snd_seq_event_output(seq, &ev);
+}
+
+static void metronome_pattern(unsigned int tick)
+{
+	int j, t, duration;
+
+	t = tick;
+	duration = ticks * 4 / ts_div;
+	for (j = 0; j < ts_num; j++) {
+		metronome_note(j ? metronome_weak_note : metronome_strong_note, t);
+		t += duration;
+	}
+	metronome_echo(t);
+	snd_seq_drain_output(seq);
+}
+
+static void metronome_set_program(void)
+{
+	snd_seq_event_t ev;
+
+	snd_seq_ev_clear(&ev);
+	snd_seq_ev_set_pgmchange(&ev, metronome_channel, metronome_program);
+	snd_seq_ev_set_source(&ev, port_count);
+	snd_seq_ev_set_subs(&ev);
+	snd_seq_event_output(seq, &ev);
+}
+
+static void init_tracks(void)
+{
+	int i;
+
+	/* MIDI RP-019 says we need at least one track per port */
+	num_tracks = port_count;
+	/* Allocate one track for each possible channel.
+	 * Empty tracks won't be written to the file. */
+	if (channel_split)
+		num_tracks *= TRACKS_PER_PORT;
+
+	tracks = calloc(num_tracks, sizeof(struct smf_track));
+	check_mem(tracks);
+	for (i = 0; i < num_tracks; ++i)
+		tracks[i].cur_buf = &tracks[i].first_buf;
+}
+
+static void create_queue(void)
+{
+	snd_seq_queue_tempo_t *tempo;
+	int err;
+
+	queue = snd_seq_alloc_named_queue(seq, "arecordmidi");
+	check_snd("create queue", queue);
+
+	snd_seq_queue_tempo_alloca(&tempo);
+	if (!smpte_timing) {
+		snd_seq_queue_tempo_set_tempo(tempo, 60000000 / beats);
+		snd_seq_queue_tempo_set_ppq(tempo, ticks);
+	} else {
+		/*
+		 * ALSA doesn't know about the SMPTE time divisions, so
+		 * we pretend to have a musical tempo with the equivalent
+		 * number of ticks/s.
+		 */
+		switch (frames) {
+		case 24:
+			snd_seq_queue_tempo_set_tempo(tempo, 500000);
+			snd_seq_queue_tempo_set_ppq(tempo, 12 * ticks);
+			break;
+		case 25:
+			snd_seq_queue_tempo_set_tempo(tempo, 400000);
+			snd_seq_queue_tempo_set_ppq(tempo, 10 * ticks);
+			break;
+		case 29:
+			snd_seq_queue_tempo_set_tempo(tempo, 100000000);
+			snd_seq_queue_tempo_set_ppq(tempo, 2997 * ticks);
+			break;
+		case 30:
+			snd_seq_queue_tempo_set_tempo(tempo, 500000);
+			snd_seq_queue_tempo_set_ppq(tempo, 15 * ticks);
+			break;
+		default:
+			fatal("Invalid SMPTE frames %d", frames);
+		}
+	}
+	err = snd_seq_set_queue_tempo(seq, queue, tempo);
+	if (err < 0)
+		fatal("Cannot set queue tempo (%u/%i)",
+		      snd_seq_queue_tempo_get_tempo(tempo),
+		      snd_seq_queue_tempo_get_ppq(tempo));
+}
+
+static void create_ports(void)
+{
+	snd_seq_port_info_t *pinfo;
+	int i, err;
+	char name[32];
+
+	snd_seq_port_info_alloca(&pinfo);
+
+	/* common information for all our ports */
+	snd_seq_port_info_set_capability(pinfo,
+					 SND_SEQ_PORT_CAP_WRITE |
+					 SND_SEQ_PORT_CAP_SUBS_WRITE);
+	snd_seq_port_info_set_type(pinfo,
+				   SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+				   SND_SEQ_PORT_TYPE_APPLICATION);
+	snd_seq_port_info_set_midi_channels(pinfo, 16);
+
+	/* we want to know when the events got delivered to us */
+	snd_seq_port_info_set_timestamping(pinfo, 1);
+	snd_seq_port_info_set_timestamp_queue(pinfo, queue);
+
+	/* our port number is the same as our port index */
+	snd_seq_port_info_set_port_specified(pinfo, 1);
+	for (i = 0; i < port_count; ++i) {
+		snd_seq_port_info_set_port(pinfo, i);
+
+		sprintf(name, "arecordmidi port %i", i);
+		snd_seq_port_info_set_name(pinfo, name);
+
+		err = snd_seq_create_port(seq, pinfo);
+		check_snd("create port", err);
+	}
+
+	/* create an optional metronome port */
+	if (use_metronome) {
+		snd_seq_port_info_set_port(pinfo, port_count);
+		snd_seq_port_info_set_name(pinfo, "arecordmidi metronome");
+		snd_seq_port_info_set_capability(pinfo,
+						 SND_SEQ_PORT_CAP_READ |
+						 SND_SEQ_PORT_CAP_WRITE);
+		snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_APPLICATION);
+		snd_seq_port_info_set_midi_channels(pinfo, 0);
+		snd_seq_port_info_set_timestamping(pinfo, 0);
+		err = snd_seq_create_port(seq, pinfo);
+		check_snd("create metronome port", err);
+	}
+}
+
+static void connect_ports(void)
+{
+	int i, err;
+
+	for (i = 0; i < port_count; ++i) {
+		err = snd_seq_connect_from(seq, i, ports[i].client, ports[i].port);
+		if (err < 0)
+			fatal("Cannot connect from port %d:%d - %s",
+			      ports[i].client, ports[i].port, snd_strerror(err));
+	}
+
+	/* subscribe the metronome port */
+	if (use_metronome) {
+	        err = snd_seq_connect_to(seq, port_count, metronome_port.client, metronome_port.port);
+		if (err < 0)
+	    		fatal("Cannot connect to port %d:%d - %s",
+			      metronome_port.client, metronome_port.port, snd_strerror(err));
+	}
+}
+
+/* records a byte to be written to the .mid file */
+static void add_byte(struct smf_track *track, unsigned char byte)
+{
+	/* make sure we have enough room in the current buffer */
+	if (track->cur_buf_size >= BUFFER_SIZE) {
+		track->cur_buf->next = calloc(1, sizeof(struct buffer));
+		if (!track->cur_buf->next)
+			fatal("out of memory");
+		track->cur_buf = track->cur_buf->next;
+		track->cur_buf_size = 0;
+	}
+
+	track->cur_buf->buf[track->cur_buf_size++] = byte;
+	track->size++;
+}
+
+/* record a variable-length quantity */
+static void var_value(struct smf_track *track, int v)
+{
+	if (v >= (1 << 28))
+		add_byte(track, 0x80 | ((v >> 28) & 0x03));
+	if (v >= (1 << 21))
+		add_byte(track, 0x80 | ((v >> 21) & 0x7f));
+	if (v >= (1 << 14))
+		add_byte(track, 0x80 | ((v >> 14) & 0x7f));
+	if (v >= (1 << 7))
+		add_byte(track, 0x80 | ((v >> 7) & 0x7f));
+	add_byte(track, v & 0x7f);
+}
+
+/* record the delta time from the last event */
+static void delta_time(struct smf_track *track, const snd_seq_event_t *ev)
+{
+	int diff = ev->time.tick - track->last_tick;
+	if (diff < 0)
+		diff = 0;
+	var_value(track, diff);
+	track->last_tick = ev->time.tick;
+}
+
+/* record a status byte (or not if we can use running status) */
+static void command(struct smf_track *track, unsigned char cmd)
+{
+	if (cmd != track->last_command)
+		add_byte(track, cmd);
+	track->last_command = cmd < 0xf0 ? cmd : 0;
+}
+
+/* put port numbers into all tracks */
+static void record_port_numbers(void)
+{
+	int i;
+
+	for (i = 0; i < num_tracks; ++i) {
+		var_value(&tracks[i], 0);
+		add_byte(&tracks[i], 0xff);
+		add_byte(&tracks[i], 0x21);
+		var_value(&tracks[i], 1);
+		if (channel_split)
+			add_byte(&tracks[i], i / TRACKS_PER_PORT);
+		else
+			add_byte(&tracks[i], i);
+	}
+}
+
+static void record_event(const snd_seq_event_t *ev)
+{
+	unsigned int i;
+	struct smf_track *track;
+
+	/* ignore events without proper timestamps */
+	if (ev->queue != queue || !snd_seq_ev_is_tick(ev))
+		return;
+
+	/* determine which track to record to */
+	i = ev->dest.port;
+	if (i == port_count) {
+		if (ev->type == SND_SEQ_EVENT_USR0)
+			metronome_pattern(ev->time.tick);
+		return;
+	}
+	if (channel_split) {
+		i *= TRACKS_PER_PORT;
+		if (snd_seq_ev_is_channel_type(ev))
+			i += 1 + (ev->data.note.channel & 0xf);
+	}
+	if (i >= num_tracks)
+		return;
+	track = &tracks[i];
+
+	switch (ev->type) {
+	case SND_SEQ_EVENT_NOTEON:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_NOTE_ON | (ev->data.note.channel & 0xf));
+		add_byte(track, ev->data.note.note & 0x7f);
+		add_byte(track, ev->data.note.velocity & 0x7f);
+		break;
+	case SND_SEQ_EVENT_NOTEOFF:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_NOTE_OFF | (ev->data.note.channel & 0xf));
+		add_byte(track, ev->data.note.note & 0x7f);
+		add_byte(track, ev->data.note.velocity & 0x7f);
+		break;
+	case SND_SEQ_EVENT_KEYPRESS:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_NOTE_PRESSURE | (ev->data.note.channel & 0xf));
+		add_byte(track, ev->data.note.note & 0x7f);
+		add_byte(track, ev->data.note.velocity & 0x7f);
+		break;
+	case SND_SEQ_EVENT_CONTROLLER:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_CONTROL | (ev->data.control.channel & 0xf));
+		add_byte(track, ev->data.control.param & 0x7f);
+		add_byte(track, ev->data.control.value & 0x7f);
+		break;
+	case SND_SEQ_EVENT_PGMCHANGE:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_PGM_CHANGE | (ev->data.control.channel & 0xf));
+		add_byte(track, ev->data.control.value & 0x7f);
+		break;
+	case SND_SEQ_EVENT_CHANPRESS:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_CHANNEL_PRESSURE | (ev->data.control.channel & 0xf));
+		add_byte(track, ev->data.control.value & 0x7f);
+		break;
+	case SND_SEQ_EVENT_PITCHBEND:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_BENDER | (ev->data.control.channel & 0xf));
+		add_byte(track, (ev->data.control.value + 8192) & 0x7f);
+		add_byte(track, ((ev->data.control.value + 8192) >> 7) & 0x7f);
+		break;
+	case SND_SEQ_EVENT_CONTROL14:
+		/* create two commands for MSB and LSB */
+		delta_time(track, ev);
+		command(track, MIDI_CMD_CONTROL | (ev->data.control.channel & 0xf));
+		add_byte(track, ev->data.control.param & 0x7f);
+		add_byte(track, (ev->data.control.value >> 7) & 0x7f);
+		if ((ev->data.control.param & 0x7f) < 0x20) {
+			delta_time(track, ev);
+			/* running status */
+			add_byte(track, (ev->data.control.param & 0x7f) + 0x20);
+			add_byte(track, ev->data.control.value & 0x7f);
+		}
+		break;
+	case SND_SEQ_EVENT_NONREGPARAM:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_CONTROL | (ev->data.control.channel & 0xf));
+		add_byte(track, MIDI_CTL_NONREG_PARM_NUM_LSB);
+		add_byte(track, ev->data.control.param & 0x7f);
+		delta_time(track, ev);
+		add_byte(track, MIDI_CTL_NONREG_PARM_NUM_MSB);
+		add_byte(track, (ev->data.control.param >> 7) & 0x7f);
+		delta_time(track, ev);
+		add_byte(track, MIDI_CTL_MSB_DATA_ENTRY);
+		add_byte(track, (ev->data.control.value >> 7) & 0x7f);
+		delta_time(track, ev);
+		add_byte(track, MIDI_CTL_LSB_DATA_ENTRY);
+		add_byte(track, ev->data.control.value & 0x7f);
+		break;
+	case SND_SEQ_EVENT_REGPARAM:
+		delta_time(track, ev);
+		command(track, MIDI_CMD_CONTROL | (ev->data.control.channel & 0xf));
+		add_byte(track, MIDI_CTL_REGIST_PARM_NUM_LSB);
+		add_byte(track, ev->data.control.param & 0x7f);
+		delta_time(track, ev);
+		add_byte(track, MIDI_CTL_REGIST_PARM_NUM_MSB);
+		add_byte(track, (ev->data.control.param >> 7) & 0x7f);
+		delta_time(track, ev);
+		add_byte(track, MIDI_CTL_MSB_DATA_ENTRY);
+		add_byte(track, (ev->data.control.value >> 7) & 0x7f);
+		delta_time(track, ev);
+		add_byte(track, MIDI_CTL_LSB_DATA_ENTRY);
+		add_byte(track, ev->data.control.value & 0x7f);
+		break;
+#if 0	/* ignore */
+	case SND_SEQ_EVENT_SONGPOS:
+	case SND_SEQ_EVENT_SONGSEL:
+	case SND_SEQ_EVENT_QFRAME:
+	case SND_SEQ_EVENT_START:
+	case SND_SEQ_EVENT_CONTINUE:
+	case SND_SEQ_EVENT_STOP:
+	case SND_SEQ_EVENT_TUNE_REQUEST:
+	case SND_SEQ_EVENT_RESET:
+	case SND_SEQ_EVENT_SENSING:
+		break;
+#endif
+	case SND_SEQ_EVENT_SYSEX:
+		if (ev->data.ext.len == 0)
+			break;
+		delta_time(track, ev);
+		if (*(unsigned char*)ev->data.ext.ptr == 0xf0)
+			command(track, 0xf0), i = 1;
+		else
+			command(track, 0xf7), i = 0;
+		var_value(track, ev->data.ext.len - i);
+		for (; i < ev->data.ext.len; ++i)
+			add_byte(track, ((unsigned char*)ev->data.ext.ptr)[i]);
+		break;
+	default:
+		return;
+	}
+	track->used = 1;
+}
+
+static void finish_tracks(void)
+{
+	snd_seq_queue_status_t *queue_status;
+	int tick, i, err;
+
+	snd_seq_queue_status_alloca(&queue_status);
+
+	err = snd_seq_get_queue_status(seq, queue, queue_status);
+	check_snd("get queue status", err);
+	tick = snd_seq_queue_status_get_tick_time(queue_status);
+
+	/* make length of first track the recording length */
+	var_value(&tracks[0], tick - tracks[0].last_tick);
+	add_byte(&tracks[0], 0xff);
+	add_byte(&tracks[0], 0x2f);
+	var_value(&tracks[0], 0);
+
+	/* finish other tracks */
+	for (i = 1; i < num_tracks; ++i) {
+		var_value(&tracks[i], 0);
+		add_byte(&tracks[i], 0xff);
+		add_byte(&tracks[i], 0x2f);
+		var_value(&tracks[i], 0);
+	}
+}
+
+static void write_file(void)
+{
+	int used_tracks, time_division, i;
+	struct buffer *buf;
+
+	used_tracks = 0;
+	for (i = 0; i < num_tracks; ++i)
+		used_tracks += !!tracks[i].used;
+
+	/* header id and length */
+	fwrite("MThd\0\0\0\6", 1, 8, file);
+	/* type 0 or 1 */
+	fputc(0, file);
+	fputc(used_tracks > 1, file);
+	/* number of tracks */
+	fputc((used_tracks >> 8) & 0xff, file);
+	fputc(used_tracks & 0xff, file);
+	/* time division */
+	time_division = ticks;
+	if (smpte_timing)
+		time_division |= (0x100 - frames) << 8;
+	fputc(time_division >> 8, file);
+	fputc(time_division & 0xff, file);
+
+	for (i = 0; i < num_tracks; ++i) {
+		if (!tracks[i].used)
+			continue;
+		/* track id */
+		fwrite("MTrk", 1, 4, file);
+		/* data length */
+		fputc((tracks[i].size >> 24) & 0xff, file);
+		fputc((tracks[i].size >> 16) & 0xff, file);
+		fputc((tracks[i].size >> 8) & 0xff, file);
+		fputc(tracks[i].size & 0xff, file);
+		/* track contents */
+		for (buf = &tracks[i].first_buf; buf; buf = buf->next)
+			fwrite(buf->buf, 1, buf == tracks[i].cur_buf
+			       ? tracks[i].cur_buf_size : BUFFER_SIZE, file);
+	}
+}
+
+static void list_ports(void)
+{
+	snd_seq_client_info_t *cinfo;
+	snd_seq_port_info_t *pinfo;
+
+	snd_seq_client_info_alloca(&cinfo);
+	snd_seq_port_info_alloca(&pinfo);
+
+	puts(" Port    Client name                      Port name");
+
+	snd_seq_client_info_set_client(cinfo, -1);
+	while (snd_seq_query_next_client(seq, cinfo) >= 0) {
+		int client = snd_seq_client_info_get_client(cinfo);
+
+		if (client == SND_SEQ_CLIENT_SYSTEM)
+			continue; /* don't show system timer and announce ports */
+		snd_seq_port_info_set_client(pinfo, client);
+		snd_seq_port_info_set_port(pinfo, -1);
+		while (snd_seq_query_next_port(seq, pinfo) >= 0) {
+			/* port must understand MIDI messages */
+			if (!(snd_seq_port_info_get_type(pinfo)
+			      & SND_SEQ_PORT_TYPE_MIDI_GENERIC))
+				continue;
+			/* we need both READ and SUBS_READ */
+			if ((snd_seq_port_info_get_capability(pinfo)
+			     & (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ))
+			    != (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ))
+				continue;
+			printf("%3d:%-3d  %-32.32s %s\n",
+			       snd_seq_port_info_get_client(pinfo),
+			       snd_seq_port_info_get_port(pinfo),
+			       snd_seq_client_info_get_name(cinfo),
+			       snd_seq_port_info_get_name(pinfo));
+		}
+	}
+}
+
+static void help(const char *argv0)
+{
+	fprintf(stderr, "Usage: %s [options] outputfile\n"
+		"\nAvailable options:\n"
+		"  -h,--help                  this help\n"
+		"  -V,--version               show version\n"
+		"  -l,--list                  list input ports\n"
+		"  -p,--port=client:port,...  source port(s)\n"
+		"  -b,--bpm=beats             tempo in beats per minute\n"
+		"  -f,--fps=frames            resolution in frames per second (SMPTE)\n"
+		"  -t,--ticks=ticks           resolution in ticks per beat or frame\n"
+		"  -s,--split-channels        create a track for each channel\n"
+		"  -m,--metronome=client:port play a metronome signal\n"
+		"  -i,--timesig=nn:dd         time signature\n",
+		argv0);
+}
+
+static void version(void)
+{
+	fputs("arecordmidi version " SND_UTIL_VERSION_STR "\n", stderr);
+}
+
+static void sighandler(int sig)
+{
+	stop = 1;
+}
+
+int main(int argc, char *argv[])
+{
+	static const char short_options[] = "hVlp:b:f:t:sdm:i:";
+	static const struct option long_options[] = {
+		{"help", 0, NULL, 'h'},
+		{"version", 0, NULL, 'V'},
+		{"list", 0, NULL, 'l'},
+		{"port", 1, NULL, 'p'},
+		{"bpm", 1, NULL, 'b'},
+		{"fps", 1, NULL, 'f'},
+		{"ticks", 1, NULL, 't'},
+		{"split-channels", 0, NULL, 's'},
+		{"dump", 0, NULL, 'd'},
+		{"metronome", 1, NULL, 'm'},
+		{"timesig", 1, NULL, 'i'},
+		{ }
+	};
+
+	char *filename = NULL;
+	int do_list = 0;
+	struct pollfd *pfds;
+	int npfds;
+	int c, err;
+
+	init_seq();
+
+	while ((c = getopt_long(argc, argv, short_options,
+				long_options, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			help(argv[0]);
+			return 0;
+		case 'V':
+			version();
+			return 0;
+		case 'l':
+			do_list = 1;
+			break;
+		case 'p':
+			parse_ports(optarg);
+			break;
+		case 'b':
+			beats = atoi(optarg);
+			if (beats < 4 || beats > 6000)
+				fatal("Invalid tempo");
+			smpte_timing = 0;
+			break;
+		case 'f':
+			frames = atoi(optarg);
+			if (frames != 24 && frames != 25 &&
+			    frames != 29 && frames != 30)
+				fatal("Invalid number of frames/s");
+			smpte_timing = 1;
+			break;
+		case 't':
+			ticks = atoi(optarg);
+			if (ticks < 1 || ticks > 0x7fff)
+				fatal("Invalid number of ticks");
+			break;
+		case 's':
+			channel_split = 1;
+			break;
+		case 'd':
+			fputs("The --dump option isn't supported anymore, use aseqdump instead.\n", stderr);
+			break;
+		case 'm':
+			init_metronome(optarg);
+			break;
+		case 'i':
+			time_signature(optarg);
+			break;
+		default:
+			help(argv[0]);
+			return 1;
+		}
+	}
+
+	if (do_list) {
+		list_ports();
+		return 0;
+	}
+
+	if (port_count < 1) {
+		fputs("Pleast specify a source port with --port.\n", stderr);
+		return 1;
+	}
+
+	if (!ticks)
+		ticks = smpte_timing ? 40 : 384;
+	if (smpte_timing && ticks > 0xff)
+		ticks = 0xff;
+
+	if (optind >= argc) {
+		fputs("Please specify a file to record to.\n", stderr);
+		return 1;
+	}
+	filename = argv[optind];
+
+	init_tracks();
+	create_queue();
+	create_ports();
+	connect_ports();
+	if (port_count > 1)
+		record_port_numbers();
+
+	/* record tempo */
+	if (!smpte_timing) {
+		int usecs_per_quarter = 60000000 / beats;
+		var_value(&tracks[0], 0); /* delta time */
+		add_byte(&tracks[0], 0xff);
+		add_byte(&tracks[0], 0x51);
+		var_value(&tracks[0], 3);
+		add_byte(&tracks[0], usecs_per_quarter >> 16);
+		add_byte(&tracks[0], usecs_per_quarter >> 8);
+		add_byte(&tracks[0], usecs_per_quarter);
+
+		/* time signature */
+		var_value(&tracks[0], 0); /* delta time */
+		add_byte(&tracks[0], 0xff);
+		add_byte(&tracks[0], 0x58);
+		var_value(&tracks[0], 4);
+		add_byte(&tracks[0], ts_num);
+		add_byte(&tracks[0], ts_dd);
+		add_byte(&tracks[0], 24); /* MIDI clocks per metronome click */
+		add_byte(&tracks[0], 8); /* notated 32nd-notes per MIDI quarter note */
+	}
+	
+	/* always write at least one track */
+	tracks[0].used = 1;
+
+	file = fopen(filename, "wb");
+	if (!file)
+		fatal("Cannot open %s - %s", filename, strerror(errno));
+
+	err = snd_seq_start_queue(seq, queue, NULL);
+	check_snd("start queue", err);
+	snd_seq_drain_output(seq);
+
+	err = snd_seq_nonblock(seq, 1);
+	check_snd("set nonblock mode", err);
+	
+	if (use_metronome) {
+		metronome_set_program();
+		metronome_pattern(0);
+	}
+
+	signal(SIGINT, sighandler);
+	signal(SIGTERM, sighandler);
+
+	npfds = snd_seq_poll_descriptors_count(seq, POLLIN);
+	pfds = alloca(sizeof(*pfds) * npfds);
+	for (;;) {
+		snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN);
+		if (poll(pfds, npfds, -1) < 0)
+			break;
+		do {
+			snd_seq_event_t *event;
+			err = snd_seq_event_input(seq, &event);
+			if (err < 0)
+				break;
+			if (event)
+				record_event(event);
+		} while (err > 0);
+		if (stop)
+			break;
+	}
+
+	finish_tracks();
+	write_file();
+
+	fclose(file);
+	snd_seq_close(seq);
+	return 0;
+}
diff --git a/seq/aseqdump/Makefile.am b/seq/aseqdump/Makefile.am
new file mode 100644
index 0000000..d918d9f
--- /dev/null
+++ b/seq/aseqdump/Makefile.am
@@ -0,0 +1,5 @@
+INCLUDES = -I$(top_srcdir)/include
+EXTRA_DIST = aseqdump.1
+
+bin_PROGRAMS = aseqdump
+man_MANS = aseqdump.1
diff --git a/seq/aseqdump/aseqdump.1 b/seq/aseqdump/aseqdump.1
new file mode 100644
index 0000000..f6f2aa9
--- /dev/null
+++ b/seq/aseqdump/aseqdump.1
@@ -0,0 +1,39 @@
+.TH ASEQDUMP 1 "19 Feb 2005"
+
+.SH NAME
+aseqdump \- show the events received at an ALSA sequencer port
+
+.SH SYNOPSIS
+.B aseqdump
+[\fI\-p client:port,...\fP]
+
+.SH DESCRIPTION
+.B aseqdump
+is a command-line utility that prints the sequencer events it receives as text.
+
+To stop receiving, press Ctrl+C.
+
+.SH OPTIONS
+
+.TP
+.I \-h,\-\-help
+Prints a list of options.
+
+.TP
+.I \-V,\-\-version
+Prints the current version.
+
+.TP
+.I \-l,\-\-list
+Prints a list of possible input ports.
+
+.TP
+.I \-p,\-\-port=client:port,...
+Sets the sequencer port(s) from which events are received.
+
+A client can be specified by its number, its name, or a prefix of its
+name.  A port is specified by its number; for port 0 of a client, the
+":0" part of the port specification can be omitted.
+
+.SH AUTHOR
+Clemens Ladisch <clemens@ladisch.de>
diff --git a/seq/aseqdump/aseqdump.c b/seq/aseqdump/aseqdump.c
new file mode 100644
index 0000000..24c5d21
--- /dev/null
+++ b/seq/aseqdump/aseqdump.c
@@ -0,0 +1,438 @@
+/*
+ * aseqdump.c - show the events received at an ALSA sequencer port
+ *
+ * Copyright (c) 2005 Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  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 <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/poll.h>
+#include <alsa/asoundlib.h>
+#include "aconfig.h"
+#include "version.h"
+
+static snd_seq_t *seq;
+static int port_count;
+static snd_seq_addr_t *ports;
+static volatile sig_atomic_t stop = 0;
+
+
+/* prints an error message to stderr, and dies */
+static void fatal(const char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	vfprintf(stderr, msg, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+	exit(EXIT_FAILURE);
+}
+
+/* memory allocation error handling */
+static void check_mem(void *p)
+{
+	if (!p)
+		fatal("Out of memory");
+}
+
+/* error handling for ALSA functions */
+static void check_snd(const char *operation, int err)
+{
+	if (err < 0)
+		fatal("Cannot %s - %s", operation, snd_strerror(err));
+}
+
+static void init_seq(void)
+{
+	int err;
+
+	/* open sequencer */
+	err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
+	check_snd("open sequencer", err);
+
+	/* set our client's name */
+	err = snd_seq_set_client_name(seq, "aseqdump");
+	check_snd("set client name", err);
+}
+
+/* parses one or more port addresses from the string */
+static void parse_ports(const char *arg)
+{
+	char *buf, *s, *port_name;
+	int err;
+
+	/* make a copy of the string because we're going to modify it */
+	buf = strdup(arg);
+	check_mem(buf);
+
+	for (port_name = s = buf; s; port_name = s + 1) {
+		/* Assume that ports are separated by commas.  We don't use
+		 * spaces because those are valid in client names. */
+		s = strchr(port_name, ',');
+		if (s)
+			*s = '\0';
+
+		++port_count;
+		ports = realloc(ports, port_count * sizeof(snd_seq_addr_t));
+		check_mem(ports);
+
+		err = snd_seq_parse_address(seq, &ports[port_count - 1], port_name);
+		if (err < 0)
+			fatal("Invalid port %s - %s", port_name, snd_strerror(err));
+	}
+
+	free(buf);
+}
+
+static void create_port(void)
+{
+	int err;
+
+	err = snd_seq_create_simple_port(seq, "aseqdump",
+					 SND_SEQ_PORT_CAP_WRITE |
+					 SND_SEQ_PORT_CAP_SUBS_WRITE,
+					 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+					 SND_SEQ_PORT_TYPE_APPLICATION);
+	check_snd("create port", err);
+}
+
+static void connect_ports(void)
+{
+	int i, err;
+
+	for (i = 0; i < port_count; ++i) {
+		err = snd_seq_connect_from(seq, 0, ports[i].client, ports[i].port);
+		if (err < 0)
+			fatal("Cannot connect from port %d:%d - %s",
+			      ports[i].client, ports[i].port, snd_strerror(err));
+	}
+}
+
+static void dump_event(const snd_seq_event_t *ev)
+{
+	printf("%3d:%-3d ", ev->source.client, ev->source.port);
+	switch (ev->type) {
+	case SND_SEQ_EVENT_NOTEON:
+		if (ev->data.note.velocity)
+			printf("Note on                %2d, note %d, velocity %d\n",
+			       ev->data.note.channel, ev->data.note.note, ev->data.note.velocity);
+		else
+			printf("Note off               %2d, note %d\n",
+			       ev->data.note.channel, ev->data.note.note);
+		break;
+	case SND_SEQ_EVENT_NOTEOFF:
+		printf("Note off               %2d, note %d, velocity %d\n",
+		       ev->data.note.channel, ev->data.note.note, ev->data.note.velocity);
+		break;
+	case SND_SEQ_EVENT_KEYPRESS:
+		printf("Polyphonic aftertouch  %2d, note %d, value %d\n",
+		       ev->data.note.channel, ev->data.note.note, ev->data.note.velocity);
+		break;
+	case SND_SEQ_EVENT_CONTROLLER:
+		printf("Control change         %2d, controller %d, value %d\n",
+		       ev->data.control.channel, ev->data.control.param, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_PGMCHANGE:
+		printf("Program change         %2d, program %d\n",
+		       ev->data.control.channel, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_CHANPRESS:
+		printf("Channel aftertouch     %2d, value %d\n",
+		       ev->data.control.channel, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_PITCHBEND:
+		printf("Pitch bend             %2d, value %d\n",
+		       ev->data.control.channel, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_CONTROL14:
+		printf("Control change         %2d, controller %d, value %5d\n",
+		       ev->data.control.channel, ev->data.control.param, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_NONREGPARAM:
+		printf("Non-reg. parameter     %2d, parameter %d, value %d\n",
+		       ev->data.control.channel, ev->data.control.param, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_REGPARAM:
+		printf("Reg. parameter         %2d, parameter %d, value %d\n",
+		       ev->data.control.channel, ev->data.control.param, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_SONGPOS:
+		printf("Song position pointer      value %d\n",
+		       ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_SONGSEL:
+		printf("Song select                value %d\n",
+		       ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_QFRAME:
+		printf("MTC quarter frame          %02xh\n",
+		       ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_TIMESIGN:
+		// XXX how is this encoded?
+		printf("SMF time signature         (%#010x)\n",
+		       ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_KEYSIGN:
+		// XXX how is this encoded?
+		printf("SMF key signature          (%#010x)\n",
+		       ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_START:
+		if (ev->source.client == SND_SEQ_CLIENT_SYSTEM &&
+		    ev->source.port == SND_SEQ_PORT_SYSTEM_TIMER)
+			printf("Queue start                queue %d\n",
+			       ev->data.queue.queue);
+		else
+			printf("Start\n");
+		break;
+	case SND_SEQ_EVENT_CONTINUE:
+		if (ev->source.client == SND_SEQ_CLIENT_SYSTEM &&
+		    ev->source.port == SND_SEQ_PORT_SYSTEM_TIMER)
+			printf("Queue continue             queue %d\n",
+			       ev->data.queue.queue);
+		else
+			printf("Continue\n");
+		break;
+	case SND_SEQ_EVENT_STOP:
+		if (ev->source.client == SND_SEQ_CLIENT_SYSTEM &&
+		    ev->source.port == SND_SEQ_PORT_SYSTEM_TIMER)
+			printf("Queue stop                 queue %d\n",
+			       ev->data.queue.queue);
+		else
+			printf("Stop\n");
+		break;
+	case SND_SEQ_EVENT_SETPOS_TICK:
+		printf("Set tick queue pos.        queue %d\n", ev->data.queue.queue);
+		break;
+	case SND_SEQ_EVENT_SETPOS_TIME:
+		printf("Set rt queue pos.          queue %d\n", ev->data.queue.queue);
+		break;
+	case SND_SEQ_EVENT_TEMPO:
+		printf("Set queue tempo            queue %d\n", ev->data.queue.queue);
+		break;
+	case SND_SEQ_EVENT_CLOCK:
+		printf("Clock\n");
+		break;
+	case SND_SEQ_EVENT_TICK:
+		printf("Tick\n");
+		break;
+	case SND_SEQ_EVENT_QUEUE_SKEW:
+		printf("Queue timer skew           queue %d\n", ev->data.queue.queue);
+		break;
+	case SND_SEQ_EVENT_TUNE_REQUEST:
+		printf("Tune request\n");
+		break;
+	case SND_SEQ_EVENT_RESET:
+		printf("Reset\n");
+		break;
+	case SND_SEQ_EVENT_SENSING:
+		printf("Active Sensing\n");
+		break;
+	case SND_SEQ_EVENT_CLIENT_START:
+		printf("Client start               client %d\n",
+		       ev->data.addr.client);
+		break;
+	case SND_SEQ_EVENT_CLIENT_EXIT:
+		printf("Client exit                client %d\n",
+		       ev->data.addr.client);
+		break;
+	case SND_SEQ_EVENT_CLIENT_CHANGE:
+		printf("Client changed             client %d\n",
+		       ev->data.addr.client);
+		break;
+	case SND_SEQ_EVENT_PORT_START:
+		printf("Port start                 %d:%d\n",
+		       ev->data.addr.client, ev->data.addr.port);
+		break;
+	case SND_SEQ_EVENT_PORT_EXIT:
+		printf("Port exit                  %d:%d\n",
+		       ev->data.addr.client, ev->data.addr.port);
+		break;
+	case SND_SEQ_EVENT_PORT_CHANGE:
+		printf("Port changed               %d:%d\n",
+		       ev->data.addr.client, ev->data.addr.port);
+		break;
+	case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+		printf("Port subscribed            %d:%d -> %d:%d\n",
+		       ev->data.connect.sender.client, ev->data.connect.sender.port,
+		       ev->data.connect.dest.client, ev->data.connect.dest.port);
+		break;
+	case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+		printf("Port unsubscribed          %d:%d -> %d:%d\n",
+		       ev->data.connect.sender.client, ev->data.connect.sender.port,
+		       ev->data.connect.dest.client, ev->data.connect.dest.port);
+		break;
+	case SND_SEQ_EVENT_SYSEX:
+		{
+			unsigned int i;
+			printf("System exclusive          ");
+			for (i = 0; i < ev->data.ext.len; ++i)
+				printf(" %02X", ((unsigned char*)ev->data.ext.ptr)[i]);
+			printf("\n");
+		}
+		break;
+	default:
+		printf("Event type %d\n",  ev->type);
+	}
+}
+
+static void list_ports(void)
+{
+	snd_seq_client_info_t *cinfo;
+	snd_seq_port_info_t *pinfo;
+
+	snd_seq_client_info_alloca(&cinfo);
+	snd_seq_port_info_alloca(&pinfo);
+
+	puts(" Port    Client name                      Port name");
+
+	snd_seq_client_info_set_client(cinfo, -1);
+	while (snd_seq_query_next_client(seq, cinfo) >= 0) {
+		int client = snd_seq_client_info_get_client(cinfo);
+
+		snd_seq_port_info_set_client(pinfo, client);
+		snd_seq_port_info_set_port(pinfo, -1);
+		while (snd_seq_query_next_port(seq, pinfo) >= 0) {
+			/* we need both READ and SUBS_READ */
+			if ((snd_seq_port_info_get_capability(pinfo)
+			     & (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ))
+			    != (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ))
+				continue;
+			printf("%3d:%-3d  %-32.32s %s\n",
+			       snd_seq_port_info_get_client(pinfo),
+			       snd_seq_port_info_get_port(pinfo),
+			       snd_seq_client_info_get_name(cinfo),
+			       snd_seq_port_info_get_name(pinfo));
+		}
+	}
+}
+
+static void help(const char *argv0)
+{
+	printf("Usage: %s [options]\n"
+		"\nAvailable options:\n"
+		"  -h,--help                  this help\n"
+		"  -V,--version               show version\n"
+		"  -l,--list                  list input ports\n"
+		"  -p,--port=client:port,...  source port(s)\n",
+		argv0);
+}
+
+static void version(void)
+{
+	puts("aseqdump version " SND_UTIL_VERSION_STR);
+}
+
+static void sighandler(int sig)
+{
+	stop = 1;
+}
+
+int main(int argc, char *argv[])
+{
+	static const char short_options[] = "hVlp:";
+	static const struct option long_options[] = {
+		{"help", 0, NULL, 'h'},
+		{"version", 0, NULL, 'V'},
+		{"list", 0, NULL, 'l'},
+		{"port", 1, NULL, 'p'},
+		{ }
+	};
+
+	int do_list = 0;
+	struct pollfd *pfds;
+	int npfds;
+	int c, err;
+
+	init_seq();
+
+	while ((c = getopt_long(argc, argv, short_options,
+				long_options, NULL)) != -1) {
+		switch (c) {
+		case 'h':
+			help(argv[0]);
+			return 0;
+		case 'V':
+			version();
+			return 0;
+		case 'l':
+			do_list = 1;
+			break;
+		case 'p':
+			parse_ports(optarg);
+			break;
+		default:
+			help(argv[0]);
+			return 1;
+		}
+	}
+	if (optind < argc) {
+		help(argv[0]);
+		return 1;
+	}
+
+	if (do_list) {
+		list_ports();
+		return 0;
+	}
+
+	create_port();
+	connect_ports();
+
+	err = snd_seq_nonblock(seq, 1);
+	check_snd("set nonblock mode", err);
+	
+	if (port_count > 0)
+		printf("Waiting for data.");
+	else
+		printf("Waiting for data at port %d:0.",
+		       snd_seq_client_id(seq));
+	printf(" Press Ctrl+C to end.\n");
+	printf("Source  Event                  Ch  Data\n");
+	
+	signal(SIGINT, sighandler);
+	signal(SIGTERM, sighandler);
+
+	npfds = snd_seq_poll_descriptors_count(seq, POLLIN);
+	pfds = alloca(sizeof(*pfds) * npfds);
+	for (;;) {
+		snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN);
+		if (poll(pfds, npfds, -1) < 0)
+			break;
+		do {
+			snd_seq_event_t *event;
+			err = snd_seq_event_input(seq, &event);
+			if (err < 0)
+				break;
+			if (event)
+				dump_event(event);
+		} while (err > 0);
+		fflush(stdout);
+		if (stop)
+			break;
+	}
+
+	snd_seq_close(seq);
+	return 0;
+}
diff --git a/seq/aseqnet/Makefile.am b/seq/aseqnet/Makefile.am
new file mode 100644
index 0000000..2ebc4a5
--- /dev/null
+++ b/seq/aseqnet/Makefile.am
@@ -0,0 +1,7 @@
+INCLUDES = -I$(top_srcdir)/include
+EXTRA_DIST = README.aseqnet aseqnet.1
+
+bin_PROGRAMS = aseqnet
+aseqnet_SOURCES = aseqnet.c
+aseqnet_LDADD = $(INTLLIBS)
+man_MANS = aseqnet.1
diff --git a/seq/aseqnet/README.aseqnet b/seq/aseqnet/README.aseqnet
new file mode 100644
index 0000000..bd0b68e
--- /dev/null
+++ b/seq/aseqnet/README.aseqnet
@@ -0,0 +1,53 @@
+================================================================
+	ALSA sequencer connectors over network
+		ver.0.1
+	Copyright (C) 1999-2000 Takashi Iwai
+================================================================
+
+* ASEQNET
+
+aseqnet is a sequencer client which sends/receives events over
+network.  Suppose two hosts (hostA and hostB) connected by network.
+You need to run ALSA system on both hosts.  Then, start aseqnet as a
+server on hostA:
+
+	hostA% aseqnet
+	sequencer opened: 128:0
+
+A user client 128 with port 0 was opened. (The client number may
+vary.)  At next, start client on hostB.  The argument is the hostname
+where server is running.
+
+	hostB% aseqnet hostA
+	sequencer opened: 132:0
+
+Now events sent to hostA:128:0 is transferred to hostB:132:0, and vice
+versa.
+
+You can connect these ports arbitrary to other sequencer ports.
+For example, connect hostB:132:0 to a MIDI output device 65:0.  The
+aconnect utility can be used for this:
+
+	hostB% aconnect 132:0 65:0
+
+Events to hostA:128:0 will be delivered indirectly to hostB:65:0.
+You'll hear MIDI sounds as following:
+
+	hostA% pmidi -p 128:0 foo.mid
+
+The multiple clients may exist simultaneously.  If hostC is connected
+as a client to hostA, events from from hostA are sent to all connected
+network clients, hostB and hostC.  However, only one connection is
+allowed from a client to a server.
+
+To disconnect network, stop all clients before server by ctrl-C or
+sending signal to them.  The server will automatically quit.
+
+The available options are:
+
+  -p port : specify the TCP port number or TCP service name.
+            Default value is 40002.
+  -s addr : explicit read-subscription to the given address
+            (client:addr).
+  -d addr : explicit write-subscription to the given address.
+  -v      : verbose mode.
diff --git a/seq/aseqnet/aseqnet.1 b/seq/aseqnet/aseqnet.1
new file mode 100644
index 0000000..a1dc1d3
--- /dev/null
+++ b/seq/aseqnet/aseqnet.1
@@ -0,0 +1,81 @@
+.TH aseqnet 1 "January 1, 2000"
+.LO 1
+.SH NAME
+aseqnet \- ALSA sequencer connectors over network
+
+.SH SYNOPSIS
+.B aseqnet
+[remotehost]
+
+.SH DESCRIPTION
+.B aseqnet
+is an ALSA sequencer client which sends and receives event packets
+over network.
+Suppose two hosts connected by network,
+.I hostA
+as a server
+and
+.I hostB
+as a client.
+The ALSA sequencer system must be running on both hosts.
+For creating the server port, run the following on hostA:
+.IP "" 4
+hostA% aseqnet
+.br
+sequencer opened: 128:0
+.PP
+Then a user client 128 with port 0 was opened on hostA.
+(The client number may vary.)
+For creating the (network-)client port, run
+.B aseqnet
+with the hostname of the server:
+.IP "" 4
+hostB% aseqnet hostA
+.br
+sequencer opened: 132:0
+.PP
+Now all events sent to hostA:128:0 are transferred to hostB:132:0, and vice
+versa.
+.PP
+The ports created by
+.B aseqnet
+can be connected arbitrary to other sequencer ports via
+.B aconnect(1).
+For example, to connect hostB:132:0 to a MIDI output device 65:0:
+.IP "" 4
+hostB% aconnect 132:0 65:0
+.PP
+Then events to hostA:128:0 will be delivered to hostB:65:0.
+The following command plays MIDI on
+.I hostB.
+.IP "" 4
+hostA% pmidi \-p 128:0 foo.mid
+.PP
+The multiple clients may exist simultaneously.  If
+.I hostC
+is connected as a client to hostA, events from from hostA are sent
+to all connected network clients, i.e. hostB and hostC.
+However, only one connection is allowed from a client to a server.
+.PP
+To disconnect network, stop all clients before server by ctrl-C or
+sending signal to them.  The server will automatically quit.
+
+.SH OPTIONS
+.TP
+.B \-p port
+Specify the TCP port number or TCP service name.
+.TP
+.B \-s addr
+Subscribe to the given address for read automatically.
+.TP
+.B \-d addr
+Subscribe to the given address for write automatically.
+.TP
+.B \-v
+Verbose mode.
+
+.SH "SEE ALSO"
+aconnect(1), pmidi(1)
+
+.SH AUTHOR
+Takashi Iwai <tiwai@suse.de>.
diff --git a/seq/aseqnet/aseqnet.c b/seq/aseqnet/aseqnet.c
new file mode 100644
index 0000000..e071ad9
--- /dev/null
+++ b/seq/aseqnet/aseqnet.c
@@ -0,0 +1,609 @@
+/*
+ * network server/client for ALSA sequencer
+ *   ver.0.1
+ *
+ * Copyright (C) 1999-2000 Takashi Iwai
+ * 
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ * 
+ *  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.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <locale.h>
+#include <alsa/asoundlib.h>
+#include <getopt.h>
+#include <signal.h>
+#include <assert.h>
+#include "aconfig.h"
+#include "gettext.h"
+
+/*
+ * prototypes
+ */
+static void usage(void);
+static void init_buf(void);
+static void init_pollfds(void);
+static void close_files(void);
+static void init_seq(char *source, char *dest);
+static int get_port(char *service);
+static void sigterm_exit(int sig);
+static void init_server(int port);
+static void init_client(char *server, int port);
+static void do_loop(void);
+static int copy_local_to_remote(void);
+static int copy_remote_to_local(int fd);
+
+/*
+ * default TCP port number
+ */
+#define DEFAULT_PORT	40002
+
+/*
+ * local input buffer
+ */
+static char *readbuf;
+static int max_rdlen;
+static char *writebuf;
+static int cur_wrlen, max_wrlen;
+
+#define MAX_BUF_EVENTS	200
+#define MAX_CONNECTION	10
+
+static snd_seq_t *handle;
+static struct pollfd *seqifds = NULL;
+static struct pollfd *seqofds = NULL;
+static struct pollfd *pollfds = NULL;
+static int seqifds_count = 0;
+static int seqofds_count = 0;
+static int pollfds_count = 0;
+static int sockfd, netfd[MAX_CONNECTION] = {[0 ... MAX_CONNECTION-1] = -1};
+static int max_connection;
+static int cur_connected;
+static int seq_port;
+
+static int server_mode;
+static int verbose = 0;
+static int info = 0;
+
+
+/*
+ * main routine
+ */
+
+static const struct option long_option[] = {
+	{"port", 1, NULL, 'p'},
+	{"source", 1, NULL, 's'},
+	{"dest", 1, NULL, 'd'},
+	{"help", 0, NULL, 'h'},
+	{"verbose", 0, NULL, 'v'},
+	{"info", 0, NULL, 'i'},
+	{NULL, 0, NULL, 0},
+};
+
+int main(int argc, char **argv)
+{
+	int c;
+	int port = DEFAULT_PORT;
+	char *source = NULL, *dest = NULL;
+
+#ifdef ENABLE_NLS
+	setlocale(LC_ALL, "");
+	textdomain(PACKAGE);
+#endif
+
+	while ((c = getopt_long(argc, argv, "p:s:d:vi", long_option, NULL)) != -1) {
+		switch (c) {
+		case 'p':
+			if (isdigit(*optarg))
+				port = atoi(optarg);
+			else
+				port = get_port(optarg);
+			break;
+		case 's':
+			source = optarg;
+			break;
+		case 'd':
+			dest = optarg;
+			break;
+		case 'v':
+			verbose++;
+			break;
+		case 'i':
+			info++;
+			break;
+		default:
+			usage();
+			exit(1);
+		}
+	}
+
+	signal(SIGINT, sigterm_exit);
+	signal(SIGTERM, sigterm_exit);
+
+	init_buf();
+	init_seq(source, dest);
+
+	if (optind >= argc) {
+		server_mode = 1;
+		max_connection = MAX_CONNECTION;
+		init_pollfds();
+		init_server(port);
+	} else {
+		server_mode = 0;
+		max_connection = 1;
+		init_pollfds();
+		init_client(argv[optind], port);
+	}
+
+	do_loop();
+
+	close_files();
+
+	return 0;
+}
+
+
+/*
+ * print usage
+ */
+static void usage(void)
+{
+	printf(_("aseqnet - network client/server on ALSA sequencer\n"));
+	printf(_("  Copyright (C) 1999 Takashi Iwai\n"));
+	printf(_("usage:\n"));
+	printf(_("  server mode: aseqnet [-options]\n"));
+	printf(_("  client mode: aseqnet [-options] server_host\n"));
+	printf(_("options:\n"));
+	printf(_("  -p,--port # : sepcify TCP port (digit or service name)\n"));
+	printf(_("  -s,--source addr : read from given addr (client:port)\n"));
+	printf(_("  -d,--dest addr : write to given addr (client:port)\n"));
+	printf(_("  -v, --verbose : print verbose messages\n"));
+	printf(_("  -i, --info : print certain received events\n"));
+}
+
+
+/*
+ * allocate and initialize buffers
+ */
+static void init_buf(void)
+{
+	max_wrlen = MAX_BUF_EVENTS * sizeof(snd_seq_event_t);
+	max_rdlen = MAX_BUF_EVENTS * sizeof(snd_seq_event_t);
+	writebuf = malloc(max_wrlen);
+	readbuf = malloc(max_rdlen);
+	if (writebuf == NULL || readbuf == NULL) {
+		fprintf(stderr, _("can't malloc\n"));
+		exit(1);
+	}
+	memset(writebuf, 0, max_wrlen);
+	memset(readbuf, 0, max_rdlen);
+	cur_wrlen = 0;
+}
+
+/*
+ * allocate and initialize poll array
+ */
+static void init_pollfds(void)
+{
+	pollfds_count = seqifds_count + seqofds_count + 1 + max_connection;
+	pollfds = (struct pollfd *)calloc(pollfds_count, sizeof(struct pollfd));
+	assert(pollfds);
+}
+
+/*
+ * close all files
+ */
+static void close_files(void)
+{
+	int i;
+	if (verbose)
+		fprintf(stderr, _("closing files..\n"));
+	for (i = 0; i < max_connection; i++) {
+		if (netfd[i] >= 0)
+			close(netfd[i]);
+	}
+	if (sockfd >= 0)
+		close(sockfd);
+}
+
+
+/*
+ * initialize sequencer
+ */
+static void init_seq(char *source, char *dest)
+{
+	snd_seq_addr_t addr;
+	int err, counti, counto;
+
+	if (snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
+		perror("snd_seq_open");
+		exit(1);
+	}
+	if (seqifds)
+		free(seqifds);
+	if (seqofds)
+		free(seqofds);
+	counti = seqifds_count = snd_seq_poll_descriptors_count(handle, POLLIN);
+	assert(counti > 0);
+	counto = seqofds_count = snd_seq_poll_descriptors_count(handle, POLLOUT);
+	assert(counto > 0);
+	seqifds = (struct pollfd *)calloc(counti, sizeof(struct pollfd));
+	assert(seqifds);
+	seqofds = (struct pollfd *)calloc(counto, sizeof(struct pollfd));
+	assert(seqofds);
+	err = snd_seq_poll_descriptors(handle, seqifds, counti, POLLIN);
+	assert(err == counti);
+	err = snd_seq_poll_descriptors(handle, seqofds, counto, POLLOUT);
+	assert(err == counto);
+
+	snd_seq_nonblock(handle, 1);
+
+	/* set client info */
+	if (server_mode)
+		snd_seq_set_client_name(handle, "Net Server");
+	else
+		snd_seq_set_client_name(handle, "Net Client");
+
+	/* create a port */
+	seq_port = snd_seq_create_simple_port(handle, "Network",
+					      SND_SEQ_PORT_CAP_READ |
+					      SND_SEQ_PORT_CAP_WRITE |
+					      SND_SEQ_PORT_CAP_SUBS_READ |
+					      SND_SEQ_PORT_CAP_SUBS_WRITE,
+					      SND_SEQ_PORT_TYPE_MIDI_GENERIC);
+	if (seq_port < 0) {
+		perror("create seq port");
+		exit(1);
+	}
+	if (verbose)
+		fprintf(stderr, _("sequencer opened: %d:%d\n"),
+			snd_seq_client_id(handle), seq_port);
+
+	/* explicit subscriptions */
+	if (source) {
+		/* read subscription */
+		if (snd_seq_parse_address(handle, &addr, source) < 0) {
+			fprintf(stderr, _("invalid source address %s\n"), source);
+			exit(1);
+		}
+		if (snd_seq_connect_from(handle, seq_port, addr.client, addr.port)) {
+			perror("read subscription");
+			exit(1);
+		}
+	}
+	if (dest) {
+		/* write subscription */
+		if (snd_seq_parse_address(handle, &addr, dest) < 0) {
+			fprintf(stderr, _("invalid destination address %s\n"), dest);
+			exit(1);
+		}
+		if (snd_seq_connect_to(handle, seq_port, addr.client, addr.port)) {
+			perror("write subscription");
+			exit(1);
+		}
+	}
+}
+
+
+/*
+ * convert from string to TCP port number
+ */
+static int get_port(char *service)
+{
+	struct servent *sp;
+
+	if ((sp = getservbyname(service, "tcp")) == NULL){
+		fprintf(stderr, _("service '%s' is not found in /etc/services\n"), service);
+		return -1;
+	}
+	return sp->s_port;
+}
+
+/*
+ * signal handler
+ */
+static void sigterm_exit(int sig)
+{
+	close_files();
+	exit(1);
+}
+
+
+/*
+ * initialize network server
+ */
+static void init_server(int port)
+{
+	int i;
+	int curstate = 1;
+	struct sockaddr_in addr;
+
+	memset(&addr, 0, sizeof(addr));
+
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = INADDR_ANY;
+	addr.sin_port = htons(port);
+
+	sockfd = socket(AF_INET, SOCK_STREAM, 0);
+	if (sockfd < 0)  {
+		perror("create socket");
+		exit(1);
+	}
+	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate));
+	/* the return value is ignored.. */
+
+	if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)  {
+		perror("can't bind");
+		exit(1);
+	}
+
+	if (listen(sockfd, 5) < 0)  {
+		perror("can't listen");
+		exit(1);
+	}
+
+	cur_connected = 0;
+	for (i = 0; i < max_connection; i++)
+		netfd[i] = -1;
+}
+
+/*
+ * start connection on server
+ */
+static void start_connection(void)
+{
+	struct sockaddr_in addr;
+	int i;
+	socklen_t addr_len;
+
+	for (i = 0; i < max_connection; i++) {
+		if (netfd[i] < 0)
+			break;
+	}
+	if (i >= max_connection) {
+		fprintf(stderr, _("too many connections!\n"));
+		exit(1);
+	}
+	memset(&addr, 0, sizeof(addr));
+	addr_len = sizeof(addr);
+	netfd[i] = accept(sockfd, (struct sockaddr *)&addr, &addr_len);
+	if (netfd[i] < 0) {
+		perror("accept");
+		exit(1);
+	}
+	if (verbose)
+		fprintf(stderr, _("accepted[%d]\n"), netfd[i]);
+	cur_connected++;
+}
+
+/*
+ * initialize network client
+ */
+static void init_client(char *server, int port)
+{
+	struct sockaddr_in addr;
+	struct hostent *host;
+	int curstate = 1;
+	int fd;
+
+	if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
+		perror("create socket");
+		exit(1);
+	}
+	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)) < 0) {
+		perror("setsockopt");
+		exit(1);
+	}
+	if ((host = gethostbyname(server)) == NULL){
+		fprintf(stderr, _("can't get address %s\n"), server);
+		exit(1);
+	}
+	addr.sin_port = htons(port);
+	addr.sin_family = AF_INET;
+	memcpy(&addr.sin_addr, host->h_addr, host->h_length);
+	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		perror("connect");
+		exit(1);
+	}
+	if (verbose)
+		fprintf(stderr, _("ok.. connected\n"));
+	netfd[0] = fd;
+	cur_connected = 1;
+}
+
+/*
+ * event loop
+ */
+static void do_loop(void)
+{
+	int i, rc, width;
+	int seqifd_ptr, sockfd_ptr = -1, netfd_ptr;
+
+	for (;;) {
+		memset(pollfds, 0, pollfds_count * sizeof(struct pollfd));
+		seqifd_ptr = 0;
+		memcpy(pollfds, seqifds, sizeof(*seqifds)*(width = seqifds_count));
+		if (server_mode) {
+			sockfd_ptr = width;
+			pollfds[width].fd = sockfd;
+			pollfds[width].events = POLLIN;
+			width++;
+		}
+		netfd_ptr = width;
+		for (i = 0; i < max_connection; i++) {
+			if (netfd[i] >= 0) {
+				pollfds[width].fd = netfd[i];
+				pollfds[width].events = POLLIN;
+				width++;
+			}
+		}
+		do {
+			rc = poll(pollfds, width, -1);
+		} while (rc <= 0 && errno == EINTR);
+		if (rc <= 0) {
+			perror("poll");
+			exit(1);
+		}
+		if (server_mode) {
+			if (pollfds[sockfd_ptr].revents & (POLLIN|POLLOUT))
+				start_connection();
+		}
+		for (i = 0; i < seqifds_count; i++)
+			if (pollfds[seqifd_ptr + i].revents & (POLLIN|POLLOUT)) {
+				if (copy_local_to_remote())
+					return;
+				break;
+			}
+		for (i = 0; i < max_connection; i++) {
+			if (netfd[i] < 0)
+				continue;
+			if (pollfds[netfd_ptr + i].revents & (POLLIN|POLLOUT)) {
+				if (copy_remote_to_local(netfd[i])) {
+					netfd[i] = -1;
+					cur_connected--;
+					if (cur_connected <= 0)
+						return;
+				}
+			}
+		}
+	}
+}
+
+
+/*
+ * flush write buffer - send data to the socket
+ */
+static void flush_writebuf(void)
+{
+	if (cur_wrlen) {
+		int i;
+		for (i = 0; i < max_connection; i++) {
+			if (netfd[i] >= 0)
+				write(netfd[i], writebuf, cur_wrlen);
+		}
+		cur_wrlen = 0;
+	}
+}
+
+/*
+ * get space from write buffer
+ */
+static char *get_writebuf(int len)
+{
+	char *buf;
+	if (cur_wrlen + len >= max_wrlen)
+		flush_writebuf();
+	buf = writebuf + cur_wrlen;
+	cur_wrlen += len;
+	return buf;
+}
+
+static void print_event(snd_seq_event_t *ev)
+{
+	switch (ev->type) {
+	case SND_SEQ_EVENT_CONTROLLER: 
+		printf(_("Channel %2d: Control event : %5d\n"),
+			ev->data.control.channel, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_PITCHBEND:
+		printf(_("Channel %2d: Pitchbender   : %5d\n"), 
+			ev->data.control.channel, ev->data.control.value);
+		break;
+	case SND_SEQ_EVENT_NOTEON:
+		printf(_("Channel %2d: Note On event : %5d\n"),
+			ev->data.control.channel, ev->data.note.note);
+		break;
+	case SND_SEQ_EVENT_NOTEOFF: 
+		printf(_("Channel %2d: Note Off event: %5d\n"),
+		       ev->data.control.channel, ev->data.note.note);           
+		break;
+	}
+}
+
+#define EVENT_PACKET_SIZE	32
+
+/*
+ * copy events from sequencer to port(s)
+ */
+static int copy_local_to_remote(void)
+{
+	int rc;
+	snd_seq_event_t *ev;
+	char *buf;
+
+	while ((rc = snd_seq_event_input(handle, &ev)) >= 0 && ev) {
+		if (ev->type >= SND_SEQ_EVENT_CLIENT_START &&
+		    ! snd_seq_ev_is_variable_type(ev)) {
+			snd_seq_free_event(ev);
+			continue;
+		}
+		if (snd_seq_ev_is_variable(ev)) {
+			int len;
+			len = EVENT_PACKET_SIZE + ev->data.ext.len;
+			buf = get_writebuf(len);
+			memcpy(buf, ev, sizeof(snd_seq_event_t));
+			memcpy(buf + EVENT_PACKET_SIZE, ev->data.ext.ptr, ev->data.ext.len);
+		} else {
+			buf = get_writebuf(EVENT_PACKET_SIZE);
+			memcpy(buf, ev, EVENT_PACKET_SIZE);
+		}
+		if (info)
+			print_event(ev);
+		snd_seq_free_event(ev);
+	}
+	flush_writebuf();
+	return 0;
+}
+
+/*
+ * copy events from a port to sequencer
+ */
+static int copy_remote_to_local(int fd)
+{
+	int count;
+	char *buf;
+	snd_seq_event_t *ev;
+
+	count = read(fd, readbuf, MAX_BUF_EVENTS * sizeof(snd_seq_event_t));
+	buf = readbuf;
+
+	if (count == 0) {
+		if (verbose)
+			fprintf(stderr, _("disconnected\n"));
+		return 1;
+	}
+
+	while (count > 0) {
+		ev = (snd_seq_event_t*)buf;
+		buf += EVENT_PACKET_SIZE;
+		count -= EVENT_PACKET_SIZE;
+		if (snd_seq_ev_is_variable(ev) && ev->data.ext.len > 0) {
+			ev->data.ext.ptr = buf;
+			buf += ev->data.ext.len;
+			count -= ev->data.ext.len;
+		}
+		snd_seq_ev_set_direct(ev);
+		snd_seq_ev_set_source(ev, seq_port);
+		snd_seq_ev_set_subs(ev);
+		if (info)
+			print_event(ev);
+		snd_seq_event_output(handle, ev);
+	}
+
+	snd_seq_drain_output(handle);
+	return 0;
+}
+
diff --git a/speaker-test/Makefile.am b/speaker-test/Makefile.am
new file mode 100644
index 0000000..e3551bc
--- /dev/null
+++ b/speaker-test/Makefile.am
@@ -0,0 +1,9 @@
+INCLUDES = -I$(top_srcdir)/include
+SUBDIRS= samples
+LDADD = $(LIBINTL) -lm
+
+bin_PROGRAMS = speaker-test
+speaker_test_SOURCES = speaker-test.c pink.c
+man_MANS = speaker-test.1
+EXTRA_DIST = readme.txt speaker-test.1 pink.h
+
diff --git a/speaker-test/pink.c b/speaker-test/pink.c
new file mode 100644
index 0000000..ef30755
--- /dev/null
+++ b/speaker-test/pink.c
@@ -0,0 +1,96 @@
+/*
+  patest_pink.c
+
+  generate Pink Noise using Gardner method.
+  Optimization suggested by James McCartney uses a tree
+  to select which random value to replace.
+
+  x x x x x x x x x x x x x x x x 
+  x   x   x   x   x   x   x   x   
+  x       x       x       x       
+  x               x               
+  x                               
+
+  Tree is generated by counting trailing zeros in an increasing index.
+  When the index is zero, no random number is selected.
+
+  This program uses the Portable Audio library which is under development.
+  For more information see:   http://www.audiomulch.com/portaudio/
+
+  Author: Phil Burk, http://www.softsynth.com
+	
+  Revision History:
+
+  Copyleft 1999 Phil Burk - No rights reserved.
+*/
+
+#include <stdio.h>
+#include <math.h>
+#include "pink.h"
+
+/************************************************************/
+/* Calculate pseudo-random 32 bit number based on linear congruential method. */
+static unsigned long generate_random_number( void )
+{
+    static unsigned long rand_seed = 22222;  /* Change this for different random sequences. */
+    rand_seed = (rand_seed * 196314165) + 907633515;
+    return rand_seed;
+}
+
+/* Setup PinkNoise structure for N rows of generators. */
+void initialize_pink_noise( pink_noise_t *pink, int num_rows )
+{
+    int i;
+    long pmax;
+    pink->pink_index = 0;
+    pink->pink_index_mask = (1<<num_rows) - 1;
+/* Calculate maximum possible signed random value. Extra 1 for white noise always added. */
+    pmax = (num_rows + 1) * (1<<(PINK_RANDOM_BITS-1));
+    pink->pink_scalar = 1.0f / pmax;
+/* Initialize rows. */
+    for( i=0; i<num_rows; i++ ) pink->pink_rows[i] = 0;
+    pink->pink_running_sum = 0;
+}
+
+/* generate Pink noise values between -1.0 and +1.0 */
+float generate_pink_noise_sample( pink_noise_t *pink )
+{
+    long new_random;
+    long sum;
+    float output;
+
+/* Increment and mask index. */
+    pink->pink_index = (pink->pink_index + 1) & pink->pink_index_mask;
+
+/* If index is zero, don't update any random values. */
+    if( pink->pink_index != 0 )
+    {
+	/* Determine how many trailing zeros in PinkIndex. */
+	/* This algorithm will hang if n==0 so test first. */
+	int num_zeros = 0;
+	int n = pink->pink_index;
+	while( (n & 1) == 0 )
+	{
+	    n = n >> 1;
+	    num_zeros++;
+	}
+
+	/* Replace the indexed ROWS random value.
+	 * Subtract and add back to Running_sum instead of adding all the random
+	 * values together. Only one changes each time.
+	 */
+	pink->pink_running_sum -= pink->pink_rows[num_zeros];
+	new_random = ((long)generate_random_number()) >> PINK_RANDOM_SHIFT;
+	pink->pink_running_sum += new_random;
+	pink->pink_rows[num_zeros] = new_random;
+    }
+	
+/* Add extra white noise value. */
+    new_random = ((long)generate_random_number()) >> PINK_RANDOM_SHIFT;
+    sum = pink->pink_running_sum + new_random;
+
+/* Scale to range of -1.0 to 0.9999. */
+    output = pink->pink_scalar * sum;
+
+    return output;
+}
diff --git a/speaker-test/pink.h b/speaker-test/pink.h
new file mode 100644
index 0000000..0d17f7a
--- /dev/null
+++ b/speaker-test/pink.h
@@ -0,0 +1,15 @@
+#define PINK_MAX_RANDOM_ROWS   (30)
+#define PINK_RANDOM_BITS       (24)
+#define PINK_RANDOM_SHIFT      ((sizeof(long)*8)-PINK_RANDOM_BITS)
+
+typedef struct
+{
+  long      pink_rows[PINK_MAX_RANDOM_ROWS];
+  long      pink_running_sum;   /* Used to optimize summing of generators. */
+  int       pink_index;        /* Incremented each sample. */
+  int       pink_index_mask;    /* Index wrapped by ANDing with this mask. */
+  float     pink_scalar;       /* Used to scale within range of -1.0 to +1.0 */
+} pink_noise_t;
+
+void initialize_pink_noise( pink_noise_t *pink, int num_rows );
+float generate_pink_noise_sample( pink_noise_t *pink );
diff --git a/speaker-test/readme.txt b/speaker-test/readme.txt
new file mode 100644
index 0000000..6774fe0
--- /dev/null
+++ b/speaker-test/readme.txt
@@ -0,0 +1,12 @@
+To make or build just type
+
+make
+
+To test: -
+1) Just stereo sound from one stereo jack: -
+./speaker-test -Dplug:front -c2
+2) A 4 speaker setup from two stereo jacks: -
+./speaker-test -Dplug:surround40 -c4
+3) A 5.1 speaker setup from three stereo jacks: -
+./speaker-test -Dplug:surround51 -c6
+
diff --git a/speaker-test/samples/Front_Center.wav b/speaker-test/samples/Front_Center.wav
new file mode 100644
index 0000000..4f12180
--- /dev/null
+++ b/speaker-test/samples/Front_Center.wav
Binary files differ
diff --git a/speaker-test/samples/Front_Left.wav b/speaker-test/samples/Front_Left.wav
new file mode 100644
index 0000000..9d6ebdd
--- /dev/null
+++ b/speaker-test/samples/Front_Left.wav
Binary files differ
diff --git a/speaker-test/samples/Front_Right.wav b/speaker-test/samples/Front_Right.wav
new file mode 100644
index 0000000..441565c
--- /dev/null
+++ b/speaker-test/samples/Front_Right.wav
Binary files differ
diff --git a/speaker-test/samples/Makefile.am b/speaker-test/samples/Makefile.am
new file mode 100644
index 0000000..8cc3546
--- /dev/null
+++ b/speaker-test/samples/Makefile.am
@@ -0,0 +1,20 @@
+sounddir = $(datadir)/sounds/alsa
+wav_files = Front_Left.wav \
+	Rear_Center.wav \
+	Rear_Right.wav \
+	Side_Right.wav \
+	Front_Center.wav \
+	Front_Right.wav \
+	Noise.wav \
+	Rear_Left.wav \
+	Side_Left.wav  
+sound_DATA = $(wav_files)
+
+alsadir = $(datadir)/alsa/speaker-test
+cfg_files = sample_map.csv
+
+alsa_DATA = $(cfg_files)
+
+EXTRA_DIST = \
+	$(wav_files) \
+	$(cfg_files)
diff --git a/speaker-test/samples/Noise.wav b/speaker-test/samples/Noise.wav
new file mode 100644
index 0000000..1898d34
--- /dev/null
+++ b/speaker-test/samples/Noise.wav
Binary files differ
diff --git a/speaker-test/samples/Rear_Center.wav b/speaker-test/samples/Rear_Center.wav
new file mode 100644
index 0000000..ee2c6da
--- /dev/null
+++ b/speaker-test/samples/Rear_Center.wav
Binary files differ
diff --git a/speaker-test/samples/Rear_Left.wav b/speaker-test/samples/Rear_Left.wav
new file mode 100644
index 0000000..57d0311
--- /dev/null
+++ b/speaker-test/samples/Rear_Left.wav
Binary files differ
diff --git a/speaker-test/samples/Rear_Right.wav b/speaker-test/samples/Rear_Right.wav
new file mode 100644
index 0000000..692e646
--- /dev/null
+++ b/speaker-test/samples/Rear_Right.wav
Binary files differ
diff --git a/speaker-test/samples/Side_Left.wav b/speaker-test/samples/Side_Left.wav
new file mode 100644
index 0000000..9741f2c
--- /dev/null
+++ b/speaker-test/samples/Side_Left.wav
Binary files differ
diff --git a/speaker-test/samples/Side_Right.wav b/speaker-test/samples/Side_Right.wav
new file mode 100644
index 0000000..b0e5b3a
--- /dev/null
+++ b/speaker-test/samples/Side_Right.wav
Binary files differ
diff --git a/speaker-test/samples/sample_map.csv b/speaker-test/samples/sample_map.csv
new file mode 100644
index 0000000..20d8ed2
--- /dev/null
+++ b/speaker-test/samples/sample_map.csv
@@ -0,0 +1,2 @@
+0, "Front Left", "/usr/share/alsa/samples/Front_Left.wav"
+1, "Front Right", "/usr/share/alsa/samples/Front_Right.wav"
diff --git a/speaker-test/speaker-test.1 b/speaker-test/speaker-test.1
new file mode 100644
index 0000000..6ef0814
--- /dev/null
+++ b/speaker-test/speaker-test.1
@@ -0,0 +1,169 @@
+.\" -*- nroff -*-
+.\" macros
+.de EX \" Begin Example
+.  IP
+.  ft CW
+.  nf
+.  ne \\$1
+..
+.de EE \" End Example
+.  ft P
+.  fi
+.  PP
+..
+.TH SPEAKER\-TEST 1 "April 2nd, 2011" speaker-test
+.SH NAME
+speaker\-test \- command-line speaker test tone generator for ALSA 
+.SH SYNOPSIS
+.B speaker\-test [\-options]
+
+.SH DESCRIPTION
+\fBspeaker\-test\fP generates a tone that can be used to test the speakers of a computer.
+
+\fBspeaker-test\fP by default will test the \fIdefault\fP device. If you
+want to test another sound device you will have first to get a list of
+all of the sound cards in your system and the devices associated with
+those cards. Notice that there might be for example, one device for
+analog sound, one for digital sound and one for HDMI sound.
+To get the list of available cards and devices you can run \fBaplay -L\fR.
+
+.P \fBaplay\fR's output will be similar to this one:
+
+.nf
+$ aplay -L
+null
+    Discard all samples (playback) or generate zero samples (capture)
+default:CARD=ICH5
+    Intel ICH5, Intel ICH5
+    Default Audio Device
+front:CARD=ICH5,DEV=0
+    Intel ICH5, Intel ICH5
+    Front speakers
+surround40:CARD=ICH5,DEV=0
+    Intel ICH5, Intel ICH5
+    4.0 Surround output to Front and Rear speakers
+(...)
+.fi
+
+.P Each of the devices is listed in the beginning of the definition so,
+in the above example, there are four devices listed: null, default, front
+and surround40. So, if you want to test the last device you can 
+run \fBspeaker-test -Dsurround40:ICH5 -c 6\fR. The \fB-c\fR option will
+indicate that the six audio channels in the device have to be tested.
+
+
+
+
+
+.SH OPTIONS
+
+.TP
+\fB\-c\fP | \fB\-\-channels\fP \fINUM\fP
+\fINUM\fP channels in stream
+
+.TP
+\fB\-D\fP | \fB\-\-device\fP \fINAME\fP
+PCM device name \fINAME\fP
+
+.TP
+\fB\-f\fP | \fB\-\-frequency\fP \fIFREQ\fP
+sine wave of \fIFREQ\fP Hz
+
+.TP
+\fB\-\-help\fP
+Print usage help
+
+.TP
+\fB\-b\fP | \fB\-\-buffer\fP \fITIME\fP
+Use buffer size of \fITIME\fP microseconds.
+When 0 is given, use the maximal buffer size.
+The default value is 0.
+
+.TP
+\fB\-p\fP | \fB\-\-period\fP \fITIME\fP
+Use period size of \fITIME\fP microseconds.
+When 0 is given, the periods given by \fB\-P\fP option is used.
+The default value is 0.
+
+.TP
+\fB\-P\fP | \fB\-\-nperiods\fP \fPERIODS\fP
+Use number of periods.  The default value is 4.
+
+.TP
+\fB\-r\fP | \fB\-\-rate\fP \fIRATE\fP
+stream of \fIRATE\fP Hz
+
+.TP
+\fB\-t\fP | \fB\-\-test\fP \fBpink\fP|\fBsine\fP|\fBwav\fP
+\fB\-t pink\fP means use pink noise (default).
+
+Pink noise is perceptually uniform noise -- that is, it sounds like every frequency at once.  If you can hear any tone it may indicate resonances in your speaker system or room.
+
+\fB\-t sine\fP means to use sine wave.
+
+\fB\-t wav\fP means to play WAV files, either pre-defined files or given via \fB\-w\fP option.
+
+You can pass the number from 1 to 3 as a backward compatibility.
+
+.TP
+\fB\-l\fP | \fB\-\-nloops\fP \fBCOUNT\fP
+
+Specifies the number of loops.  Zero means to run infinitely.
+
+When \fB\-s\fP option below with a valid channel is given, \fBspeaker\-test\fP will perform
+always a single-shot without looping.
+
+.TP
+\fB\-s\fP | \fB\-\-speaker\fP \fBCHANNEL\fP
+Do a single-shot speaker test for the given channel.  The channel number starts from 1.
+The channel number corresponds to left, right, rear-left, rear-right, center, LFE,
+side-left, side-right, and so on.
+
+For example, when 1 is passed, it tests the left channel only once rather than both channels
+with looping.
+
+.TP
+\fB\-w\fP | \fB\-\-wavfile\fP
+Use the given WAV file for the playback instead of pre-defined WAV files.
+
+.TP
+\fB\-W\fP | \fB\-\-wavdir\fP
+Specify the directory containing WAV files for playback.
+The default path is \fI/usr/share/sounds/alsa\fP.
+
+
+.SH USAGE EXAMPLES
+
+Produce stereo sound from one stereo jack:
+.EX
+  speaker-test -Dplug:front -c2
+.EE
+
+Produce 4 speaker sound from two stereo jacks:
+.EX
+  speaker-test -Dplug:surround40 -c4
+.EE
+
+Produce 5.1 speaker sound from three stereo jacks:
+.EX
+  speaker-test -Dplug:surround51 -c6
+.EE
+
+To send a nice low 75Hz tone to the Woofer and then exit without touching any other speakers:
+.EX
+  speaker-test -Dplug:surround51 -c6 -s1 -f75
+.EE
+
+To do a 2-speaker test using the spdif (coax or optical) output:
+.EX
+  speaker-test -Dplug:spdif -c2
+.EE
+
+
+.SH SEE ALSO
+.BR aplay(1)
+
+.SH AUTHOR
+The speaker-test program was written by James Courtier-Dutton.
+Pink noise support was added by Nathan Hurst.
+Further extensions by Takashi Iwai.
diff --git a/speaker-test/speaker-test.c b/speaker-test/speaker-test.c
new file mode 100644
index 0000000..3029110
--- /dev/null
+++ b/speaker-test/speaker-test.c
@@ -0,0 +1,1087 @@
+/*
+ * Copyright (C) 2000-2004 James Courtier-Dutton
+ * Copyright (C) 2005 Nathan Hurst
+ *
+ * This file is part of the speaker-test tool.
+ *
+ * This small program sends a simple sinusoidal wave to your speakers.
+ *
+ * speaker-test 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.
+ *
+ * speaker-test 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
+ *
+ *
+ * Main program by James Courtier-Dutton (including some source code fragments from the alsa project.)
+ * Some cleanup from Daniel Caujolle-Bert <segfault@club-internet.fr>
+ * Pink noise option added Nathan Hurst, 
+ *   based on generator by Phil Burk (pink.c)
+ *
+ * Changelog:
+ *   0.0.8 Added support for pink noise output.
+ * Changelog:
+ *   0.0.7 Added support for more than 6 channels.
+ * Changelog:
+ *   0.0.6 Added support for different sample formats.
+ *
+ * $Id: speaker_test.c,v 1.00 2003/11/26 19:43:38 jcdutton Exp $
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sched.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include <byteswap.h>
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+#include <alsa/asoundlib.h>
+#include <sys/time.h>
+#include <math.h>
+#include "pink.h"
+#include "aconfig.h"
+#include "gettext.h"
+#include "version.h"
+
+#ifdef ENABLE_NLS
+#include <locale.h>
+#endif
+
+enum {
+  TEST_PINK_NOISE = 1,
+  TEST_SINE,
+  TEST_WAV,
+  TEST_PATTERN,
+};
+
+#define MAX_CHANNELS	16
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define COMPOSE_ID(a,b,c,d)	((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
+#define LE_SHORT(v)		(v)
+#define LE_INT(v)		(v)
+#define BE_SHORT(v)		bswap_16(v)
+#define BE_INT(v)		bswap_32(v)
+#else /* __BIG_ENDIAN */
+#define COMPOSE_ID(a,b,c,d)	((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
+#define LE_SHORT(v)		bswap_16(v)
+#define LE_INT(v)		bswap_32(v)
+#define BE_SHORT(v)		(v)
+#define BE_INT(v)		(v)
+#endif
+
+static char              *device      = "default";       /* playback device */
+static snd_pcm_format_t   format      = SND_PCM_FORMAT_S16; /* sample format */
+static unsigned int       rate        = 48000;	            /* stream rate */
+static unsigned int       channels    = 1;	            /* count of channels */
+static unsigned int       speaker     = 0;	            /* count of channels */
+static unsigned int       buffer_time = 0;	            /* ring buffer length in us */
+static unsigned int       period_time = 0;	            /* period time in us */
+static unsigned int       nperiods    = 4;                  /* number of periods */
+static double             freq        = 440.0;              /* sinusoidal wave frequency in Hz */
+static int                test_type   = TEST_PINK_NOISE;    /* Test type. 1 = noise, 2 = sine wave */
+static pink_noise_t pink;
+static snd_pcm_uframes_t  buffer_size;
+static snd_pcm_uframes_t  period_size;
+static const char *given_test_wav_file = NULL;
+static char *wav_file_dir = SOUNDSDIR;
+static int debug = 0;
+
+static const char *const channel_name[MAX_CHANNELS] = {
+  /*  0 */ N_("Front Left"),
+  /*  1 */ N_("Front Right"),
+  /*  2 */ N_("Rear Left"),
+  /*  3 */ N_("Rear Right"),
+  /*  4 */ N_("Center"),
+  /*  5 */ N_("LFE"),
+  /*  6 */ N_("Side Left"),
+  /*  7 */ N_("Side Right"),
+  /*  8 */ N_("Channel 9"),
+  /*  9 */ N_("Channel 10"),
+  /* 10 */ N_("Channel 11"),
+  /* 11 */ N_("Channel 12"),
+  /* 12 */ N_("Channel 13"),
+  /* 13 */ N_("Channel 14"),
+  /* 14 */ N_("Channel 15"),
+  /* 15 */ N_("Channel 16")
+};
+
+static const int	channels4[] = {
+  0, /* Front Left  */
+  1, /* Front Right */
+  3, /* Rear Right  */
+  2, /* Rear Left   */
+};
+static const int	channels6[] = {
+  0, /* Front Left  */
+  4, /* Center      */
+  1, /* Front Right */
+  3, /* Rear Right  */
+  2, /* Rear Left   */
+  5, /* LFE         */
+};
+static const int	channels8[] = {
+  0, /* Front Left  */
+  4, /* Center      */
+  1, /* Front Right */
+  7, /* Side Right  */
+  3, /* Rear Right  */
+  2, /* Rear Left   */
+  6, /* Side Left   */
+  5, /* LFE         */
+};
+static const int	supported_formats[] = {
+  SND_PCM_FORMAT_S8,
+  SND_PCM_FORMAT_S16_LE,
+  SND_PCM_FORMAT_S16_BE,
+  SND_PCM_FORMAT_FLOAT_LE,
+  SND_PCM_FORMAT_S32_LE,
+  SND_PCM_FORMAT_S32_BE,
+  -1
+};
+
+static void generate_sine(uint8_t *frames, int channel, int count, double *_phase) {
+  double phase = *_phase;
+  double max_phase = 1.0 / freq;
+  double step = 1.0 / (double)rate;
+  double res;
+  float fres;
+  int    chn;
+  int32_t  ires;
+  int8_t *samp8 = (int8_t*) frames;
+  int16_t *samp16 = (int16_t*) frames;
+  int32_t *samp32 = (int32_t*) frames;
+  float   *samp_f = (float*) frames;
+
+  while (count-- > 0) {
+    for(chn=0;chn<channels;chn++) {
+      switch (format) {
+      case SND_PCM_FORMAT_S8:
+        if (chn==channel) {
+          res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
+          ires = res;
+          *samp8++ = ires >> 24;
+        } else {
+          *samp8++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S16_LE:
+        if (chn==channel) {
+          res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
+          ires = res;
+          *samp16++ = LE_SHORT(ires >> 16);
+        } else {
+          *samp16++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S16_BE:
+        if (chn==channel) {
+          res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
+          ires = res;
+          *samp16++ = BE_SHORT(ires >> 16);
+        } else {
+          *samp16++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_FLOAT_LE:
+        if (chn==channel) {
+          res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0.75 ; /* Don't use MAX volume */
+          fres = res;
+	  *samp_f++ = fres;
+        } else {
+	  *samp_f++ = 0.0;
+        }
+        break;
+      case SND_PCM_FORMAT_S32_LE:
+        if (chn==channel) {
+          res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
+          ires = res;
+          *samp32++ = LE_INT(ires);
+        } else {
+          *samp32++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S32_BE:
+        if (chn==channel) {
+          res = (sin((phase * 2 * M_PI) / max_phase - M_PI)) * 0x03fffffff; /* Don't use MAX volume */
+          ires = res;
+          *samp32++ = BE_INT(ires);
+        } else {
+          *samp32++ = 0;
+        }
+        break;
+      default:
+        ;
+      }
+    }
+
+    phase += step;
+    if (phase >= max_phase)
+      phase -= max_phase;
+  }
+
+  *_phase = phase;
+}
+
+/* Pink noise is a better test than sine wave because we can tell
+ * where pink noise is coming from more easily that a sine wave.
+ */
+
+
+static void generate_pink_noise( uint8_t *frames, int channel, int count) {
+  double   res;
+  int      chn;
+  int32_t  ires;
+  int8_t  *samp8 = (int8_t*) frames;
+  int16_t *samp16 = (int16_t*) frames;
+  int32_t *samp32 = (int32_t*) frames;
+
+  while (count-- > 0) {
+    for(chn=0;chn<channels;chn++) {
+      switch (format) {
+      case SND_PCM_FORMAT_S8:
+        if (chn==channel) {
+	  res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */
+	  ires = res;
+	  *samp8++ = ires >> 24;
+        } else {
+	  *samp8++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S16_LE:
+        if (chn==channel) {
+	  res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */
+	  ires = res;
+          *samp16++ = LE_SHORT(ires >> 16);
+        } else {
+	  *samp16++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S16_BE:
+        if (chn==channel) {
+          res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */
+          ires = res;
+          *samp16++ = BE_SHORT(ires >> 16);
+        } else {
+          *samp16++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S32_LE:
+        if (chn==channel) {
+          res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */
+          ires = res;
+          *samp32++ = LE_INT(ires);
+        } else {
+          *samp32++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S32_BE:
+        if (chn==channel) {
+	  res = generate_pink_noise_sample(&pink) * 0x03fffffff; /* Don't use MAX volume */
+	  ires = res;
+	  *samp32++ = BE_INT(ires);
+        } else {
+	  *samp32++ = 0;
+        }
+        break;
+      default:
+        ;
+      }
+    }
+  }
+}
+
+/*
+ * useful for tests
+ */
+static void generate_pattern(uint8_t *frames, int channel, int count, int *_pattern) {
+  int pattern = *_pattern;
+  int    chn;
+  int8_t *samp8 = (int8_t*) frames;
+  int16_t *samp16 = (int16_t*) frames;
+  int32_t *samp32 = (int32_t*) frames;
+  float   *samp_f = (float*) frames;
+
+  while (count-- > 0) {
+    for(chn=0;chn<channels;chn++,pattern++) {
+      switch (format) {
+      case SND_PCM_FORMAT_S8:
+        if (chn==channel) {
+          *samp8++ = pattern & 0xff;
+        } else {
+          *samp8++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S16_LE:
+        if (chn==channel) {
+          *samp16++ = LE_SHORT(pattern & 0xfffff);
+        } else {
+          *samp16++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S16_BE:
+        if (chn==channel) {
+          *samp16++ = BE_SHORT(pattern & 0xffff);
+        } else {
+          *samp16++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_FLOAT_LE:
+        if (chn==channel) {
+	  *samp_f++ = LE_INT(((double)pattern) / INT32_MAX);
+        } else {
+	  *samp_f++ = 0.0;
+        }
+        break;
+      case SND_PCM_FORMAT_S32_LE:
+        if (chn==channel) {
+          *samp32++ = LE_INT(pattern);
+        } else {
+          *samp32++ = 0;
+        }
+        break;
+      case SND_PCM_FORMAT_S32_BE:
+        if (chn==channel) {
+          *samp32++ = BE_INT(pattern);
+        } else {
+          *samp32++ = 0;
+        }
+        break;
+      default:
+        ;
+      }
+    }
+  }
+
+  *_pattern = pattern;
+}
+
+static int set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, snd_pcm_access_t access) {
+  unsigned int rrate;
+  int          err;
+  snd_pcm_uframes_t     period_size_min;
+  snd_pcm_uframes_t     period_size_max;
+  snd_pcm_uframes_t     buffer_size_min;
+  snd_pcm_uframes_t     buffer_size_max;
+
+  /* choose all parameters */
+  err = snd_pcm_hw_params_any(handle, params);
+  if (err < 0) {
+    fprintf(stderr, _("Broken configuration for playback: no configurations available: %s\n"), snd_strerror(err));
+    return err;
+  }
+
+  /* set the interleaved read/write format */
+  err = snd_pcm_hw_params_set_access(handle, params, access);
+  if (err < 0) {
+    fprintf(stderr, _("Access type not available for playback: %s\n"), snd_strerror(err));
+    return err;
+  }
+
+  /* set the sample format */
+  err = snd_pcm_hw_params_set_format(handle, params, format);
+  if (err < 0) {
+    fprintf(stderr, _("Sample format not available for playback: %s\n"), snd_strerror(err));
+    return err;
+  }
+
+  /* set the count of channels */
+  err = snd_pcm_hw_params_set_channels(handle, params, channels);
+  if (err < 0) {
+    fprintf(stderr, _("Channels count (%i) not available for playbacks: %s\n"), channels, snd_strerror(err));
+    return err;
+  }
+
+  /* set the stream rate */
+  rrate = rate;
+  err = snd_pcm_hw_params_set_rate(handle, params, rate, 0);
+  if (err < 0) {
+    fprintf(stderr, _("Rate %iHz not available for playback: %s\n"), rate, snd_strerror(err));
+    return err;
+  }
+
+  if (rrate != rate) {
+    fprintf(stderr, _("Rate doesn't match (requested %iHz, get %iHz, err %d)\n"), rate, rrate, err);
+    return -EINVAL;
+  }
+
+  printf(_("Rate set to %iHz (requested %iHz)\n"), rrate, rate);
+  /* set the buffer time */
+  err = snd_pcm_hw_params_get_buffer_size_min(params, &buffer_size_min);
+  err = snd_pcm_hw_params_get_buffer_size_max(params, &buffer_size_max);
+  err = snd_pcm_hw_params_get_period_size_min(params, &period_size_min, NULL);
+  err = snd_pcm_hw_params_get_period_size_max(params, &period_size_max, NULL);
+  printf(_("Buffer size range from %lu to %lu\n"),buffer_size_min, buffer_size_max);
+  printf(_("Period size range from %lu to %lu\n"),period_size_min, period_size_max);
+  if (period_time > 0) {
+    printf(_("Requested period time %u us\n"), period_time);
+    err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, NULL);
+    if (err < 0) {
+      fprintf(stderr, _("Unable to set period time %u us for playback: %s\n"),
+	     period_time, snd_strerror(err));
+      return err;
+    }
+  }
+  if (buffer_time > 0) {
+    printf(_("Requested buffer time %u us\n"), buffer_time);
+    err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, NULL);
+    if (err < 0) {
+      fprintf(stderr, _("Unable to set buffer time %u us for playback: %s\n"),
+	     buffer_time, snd_strerror(err));
+      return err;
+    }
+  }
+  if (! buffer_time && ! period_time) {
+    buffer_size = buffer_size_max;
+    if (! period_time)
+      buffer_size = (buffer_size / nperiods) * nperiods;
+    printf(_("Using max buffer size %lu\n"), buffer_size);
+    err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
+    if (err < 0) {
+      fprintf(stderr, _("Unable to set buffer size %lu for playback: %s\n"),
+	     buffer_size, snd_strerror(err));
+      return err;
+    }
+  }
+  if (! buffer_time || ! period_time) {
+    printf(_("Periods = %u\n"), nperiods);
+    err = snd_pcm_hw_params_set_periods_near(handle, params, &nperiods, NULL);
+    if (err < 0) {
+      fprintf(stderr, _("Unable to set nperiods %u for playback: %s\n"),
+	     nperiods, snd_strerror(err));
+      return err;
+    }
+  }
+
+  /* write the parameters to device */
+  err = snd_pcm_hw_params(handle, params);
+  if (err < 0) {
+    fprintf(stderr, _("Unable to set hw params for playback: %s\n"), snd_strerror(err));
+    return err;
+  }
+
+  snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
+  snd_pcm_hw_params_get_period_size(params, &period_size, NULL);
+  printf(_("was set period_size = %lu\n"),period_size);
+  printf(_("was set buffer_size = %lu\n"),buffer_size);
+  if (2*period_size > buffer_size) {
+    fprintf(stderr, _("buffer to small, could not use\n"));
+    return -EINVAL;
+  }
+
+  return 0;
+}
+
+static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams) {
+  int err;
+
+  /* get the current swparams */
+  err = snd_pcm_sw_params_current(handle, swparams);
+  if (err < 0) {
+    fprintf(stderr, _("Unable to determine current swparams for playback: %s\n"), snd_strerror(err));
+    return err;
+  }
+
+  /* start the transfer when a buffer is full */
+  err = snd_pcm_sw_params_set_start_threshold(handle, swparams, buffer_size);
+  if (err < 0) {
+    fprintf(stderr, _("Unable to set start threshold mode for playback: %s\n"), snd_strerror(err));
+    return err;
+  }
+
+  /* allow the transfer when at least period_size frames can be processed */
+  err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size);
+  if (err < 0) {
+    fprintf(stderr, _("Unable to set avail min for playback: %s\n"), snd_strerror(err));
+    return err;
+  }
+
+  /* write the parameters to the playback device */
+  err = snd_pcm_sw_params(handle, swparams);
+  if (err < 0) {
+    fprintf(stderr, _("Unable to set sw params for playback: %s\n"), snd_strerror(err));
+    return err;
+  }
+
+  return 0;
+}
+
+/*
+ *   Underrun and suspend recovery
+ */
+
+static int xrun_recovery(snd_pcm_t *handle, int err) {
+  if (err == -EPIPE) {	/* under-run */
+    err = snd_pcm_prepare(handle);
+    if (err < 0)
+      fprintf(stderr, _("Can't recovery from underrun, prepare failed: %s\n"), snd_strerror(err));
+    return 0;
+  } 
+  else if (err == -ESTRPIPE) {
+
+    while ((err = snd_pcm_resume(handle)) == -EAGAIN)
+      sleep(1);	/* wait until the suspend flag is released */
+
+    if (err < 0) {
+      err = snd_pcm_prepare(handle);
+      if (err < 0)
+        fprintf(stderr, _("Can't recovery from suspend, prepare failed: %s\n"), snd_strerror(err));
+    }
+
+    return 0;
+  }
+
+  return err;
+}
+
+/*
+ * Handle WAV files
+ */
+
+static const char *wav_file[MAX_CHANNELS];
+static int wav_file_size[MAX_CHANNELS];
+
+struct wave_header {
+  struct {
+    uint32_t magic;
+    uint32_t length;
+    uint32_t type;
+  } hdr;
+  struct {
+    uint32_t type;
+    uint32_t length;
+  } chunk1;
+  struct {
+    uint16_t format;
+    uint16_t channels;
+    uint32_t rate;
+    uint32_t bytes_per_sec;
+    uint16_t sample_size;
+    uint16_t sample_bits;
+  } body;
+  struct {
+    uint32_t type;
+    uint32_t length;
+  } chunk;
+};
+
+#define WAV_RIFF		COMPOSE_ID('R','I','F','F')
+#define WAV_WAVE		COMPOSE_ID('W','A','V','E')
+#define WAV_FMT			COMPOSE_ID('f','m','t',' ')
+#define WAV_DATA		COMPOSE_ID('d','a','t','a')
+#define WAV_PCM_CODE		1
+
+static const char *search_for_file(const char *name)
+{
+  char *file;
+  if (*name == '/')
+    return strdup(name);
+  file = malloc(strlen(wav_file_dir) + strlen(name) + 2);
+  if (file)
+    sprintf(file, "%s/%s", wav_file_dir, name);
+  return file;
+}
+
+static int check_wav_file(int channel, const char *name)
+{
+  struct wave_header header;
+  int fd;
+
+  wav_file[channel] = search_for_file(name);
+  if (! wav_file[channel]) {
+    fprintf(stderr, _("No enough memory\n"));
+    return -ENOMEM;
+  }
+
+  if ((fd = open(wav_file[channel], O_RDONLY)) < 0) {
+    fprintf(stderr, _("Cannot open WAV file %s\n"), wav_file[channel]);
+    return -EINVAL;
+  }
+  if (read(fd, &header, sizeof(header)) < (int)sizeof(header)) {
+    fprintf(stderr, _("Invalid WAV file %s\n"), wav_file[channel]);
+    goto error;
+  }
+  
+  if (header.hdr.magic != WAV_RIFF || header.hdr.type != WAV_WAVE) {
+    fprintf(stderr, _("Not a WAV file: %s\n"), wav_file[channel]);
+    goto error;
+  }
+  if (header.body.format != LE_SHORT(WAV_PCM_CODE)) {
+    fprintf(stderr, _("Unsupported WAV format %d for %s\n"),
+	    LE_SHORT(header.body.format), wav_file[channel]);
+    goto error;
+  }
+  if (header.body.channels != LE_SHORT(1)) {
+    fprintf(stderr, _("%s is not a mono stream (%d channels)\n"),
+	    wav_file[channel], LE_SHORT(header.body.channels)); 
+    goto error;
+  }
+  if (header.body.rate != LE_INT(rate)) {
+    fprintf(stderr, _("Sample rate doesn't match (%d) for %s\n"),
+	    LE_INT(header.body.rate), wav_file[channel]);
+    goto error;
+  }
+  if (header.body.sample_bits != LE_SHORT(16)) {
+    fprintf(stderr, _("Unsupported sample format bits %d for %s\n"),
+	    LE_SHORT(header.body.sample_bits), wav_file[channel]);
+    goto error;
+  }
+  if (header.chunk.type != WAV_DATA) {
+    fprintf(stderr, _("Invalid WAV file %s\n"), wav_file[channel]);
+    goto error;
+  }
+  wav_file_size[channel] = LE_INT(header.chunk.length);
+  close(fd);
+  return 0;
+
+ error:
+  close(fd);
+  return -EINVAL;
+}
+
+static int setup_wav_file(int chn)
+{
+  static const char *const wavs[MAX_CHANNELS] = {
+    "Front_Left.wav",
+    "Front_Right.wav",
+    "Rear_Left.wav",
+    "Rear_Right.wav",
+    "Front_Center.wav",
+    "Rear_Center.wav", /* FIXME: should be "Bass" or so */
+    "Side_Left.wav",
+    "Side_Right.wav",
+    "Channel_9.wav",
+    "Channel_10.wav",
+    "Channel_11.wav",
+    "Channel_12.wav",
+    "Channel_13.wav",
+    "Channel_14.wav",
+    "Channel_15.wav",
+    "Channel_16.wav"
+  };
+
+  if (given_test_wav_file)
+    return check_wav_file(chn, given_test_wav_file);
+  else
+    return check_wav_file(chn, wavs[chn]);
+}
+
+static int read_wav(uint16_t *buf, int channel, int offset, int bufsize)
+{
+  static FILE *wavfp = NULL;
+  int size;
+
+  if (! wav_file[channel]) {
+    fprintf(stderr, _("Undefined channel %d\n"), channel);
+    return -EINVAL;
+  }
+
+  if (offset >= wav_file_size[channel])
+   return 0; /* finished */
+
+  if (! offset) {
+    if (wavfp)
+      fclose(wavfp);
+    wavfp = fopen(wav_file[channel], "r");
+    if (! wavfp)
+      return -errno;
+    if (fseek(wavfp, sizeof(struct wave_header), SEEK_SET) < 0)
+      return -errno;
+  }
+  if (offset + bufsize > wav_file_size[channel])
+    bufsize = wav_file_size[channel] - offset;
+  bufsize /= channels;
+  for (size = 0; size < bufsize; size += 2) {
+    int chn;
+    for (chn = 0; chn < channels; chn++) {
+      if (chn == channel) {
+	if (fread(buf, 2, 1, wavfp) != 1)
+	  return size;
+      }
+      else
+	*buf = 0;
+      buf++;
+    }
+  }
+  return size;
+}
+
+
+/*
+ *   Transfer method - write only
+ */
+
+static int write_buffer(snd_pcm_t *handle, uint8_t *ptr, int cptr)
+{
+  int err;
+
+  while (cptr > 0) {
+
+    err = snd_pcm_writei(handle, ptr, cptr);
+
+    if (err == -EAGAIN)
+      continue;
+
+    if (err < 0) {
+      fprintf(stderr, _("Write error: %d,%s\n"), err, snd_strerror(err));
+      if (xrun_recovery(handle, err) < 0) {
+	fprintf(stderr, _("xrun_recovery failed: %d,%s\n"), err, snd_strerror(err));
+	return -1;
+      }
+      break;	/* skip one period */
+    }
+
+    ptr += snd_pcm_frames_to_bytes(handle, err);
+    cptr -= err;
+  }
+  return 0;
+}
+
+static int write_loop(snd_pcm_t *handle, int channel, int periods, uint8_t *frames)
+{
+  double phase = 0;
+  int	 pattern = 0;
+  int    err, n;
+
+  fflush(stdout);
+  if (test_type == TEST_WAV) {
+    int bufsize = snd_pcm_frames_to_bytes(handle, period_size);
+    n = 0;
+    while ((err = read_wav((uint16_t *)frames, channel, n, bufsize)) > 0) {
+      n += err;
+      if ((err = write_buffer(handle, frames,
+			      snd_pcm_bytes_to_frames(handle, err * channels))) < 0)
+	break;
+    }
+    if (buffer_size > n) {
+      snd_pcm_drain(handle);
+      snd_pcm_prepare(handle);
+    }
+    return err;
+  }
+    
+
+  if (periods <= 0)
+    periods = 1;
+
+  for(n = 0; n < periods; n++) {
+    if (test_type == TEST_PINK_NOISE)
+      generate_pink_noise(frames, channel, period_size);
+    else if (test_type == TEST_PATTERN)
+      generate_pattern(frames, channel, period_size, &pattern);
+    else
+      generate_sine(frames, channel, period_size, &phase);
+
+    if ((err = write_buffer(handle, frames, period_size)) < 0)
+      return err;
+  }
+  if (buffer_size > n * period_size) {
+    snd_pcm_drain(handle);
+    snd_pcm_prepare(handle);
+  }
+  return 0;
+}
+
+static void help(void)
+{
+  const int *fmt;
+
+  printf(
+	 _("Usage: speaker-test [OPTION]... \n"
+	   "-h,--help	help\n"
+	   "-D,--device	playback device\n"
+	   "-r,--rate	stream rate in Hz\n"
+	   "-c,--channels	count of channels in stream\n"
+	   "-f,--frequency	sine wave frequency in Hz\n"
+	   "-F,--format	sample format\n"
+	   "-b,--buffer	ring buffer size in us\n"
+	   "-p,--period	period size in us\n"
+	   "-P,--nperiods	number of periods\n"
+	   "-t,--test	pink=use pink noise, sine=use sine wave, wav=WAV file\n"
+	   "-l,--nloops	specify number of loops to test, 0 = infinite\n"
+	   "-s,--speaker	single speaker test. Values 1=Left, 2=right, etc\n"
+	   "-w,--wavfile	Use the given WAV file as a test sound\n"
+	   "-W,--wavdir	Specify the directory containing WAV files\n"
+	   "\n"));
+  printf(_("Recognized sample formats are:"));
+  for (fmt = supported_formats; *fmt >= 0; fmt++) {
+    const char *s = snd_pcm_format_name(*fmt);
+    if (s)
+      printf(" %s", s);
+  }
+
+  printf("\n\n");
+}
+
+int main(int argc, char *argv[]) {
+  snd_pcm_t            *handle;
+  int                   err, morehelp;
+  snd_pcm_hw_params_t  *hwparams;
+  snd_pcm_sw_params_t  *swparams;
+  uint8_t              *frames;
+  int                   chn;
+  const int	       *fmt;
+  double		time1,time2,time3;
+  unsigned int		n, nloops;
+  struct   timeval	tv1,tv2;
+
+  static const struct option long_option[] = {
+    {"help",      0, NULL, 'h'},
+    {"device",    1, NULL, 'D'},
+    {"rate",      1, NULL, 'r'},
+    {"channels",  1, NULL, 'c'},
+    {"frequency", 1, NULL, 'f'},
+    {"format",    1, NULL, 'F'},
+    {"buffer",    1, NULL, 'b'},
+    {"period",    1, NULL, 'p'},
+    {"nperiods",  1, NULL, 'P'},
+    {"test",      1, NULL, 't'},
+    {"nloops",    1, NULL, 'l'},
+    {"speaker",   1, NULL, 's'},
+    {"wavfile",   1, NULL, 'w'},
+    {"wavdir",    1, NULL, 'W'},
+    {"debug",	  0, NULL, 'd'},
+    {NULL,        0, NULL, 0  },
+  };
+
+#ifdef ENABLE_NLS
+  setlocale(LC_ALL, "");
+  textdomain(PACKAGE);
+#endif
+
+  snd_pcm_hw_params_alloca(&hwparams);
+  snd_pcm_sw_params_alloca(&swparams);
+ 
+  nloops = 0;
+  morehelp = 0;
+
+  printf("\nspeaker-test %s\n\n", SND_UTIL_VERSION_STR);
+  while (1) {
+    int c;
+    
+    if ((c = getopt_long(argc, argv, "hD:r:c:f:F:b:p:P:t:l:s:w:W:d", long_option, NULL)) < 0)
+      break;
+    
+    switch (c) {
+    case 'h':
+      morehelp++;
+      break;
+    case 'D':
+      device = strdup(optarg);
+      break;
+    case 'F':
+      format = snd_pcm_format_value(optarg);
+      for (fmt = supported_formats; *fmt >= 0; fmt++)
+        if (*fmt == format)
+          break;
+      if (*fmt < 0) {
+        fprintf(stderr, "Format %s is not supported...\n", snd_pcm_format_name(format));
+        exit(EXIT_FAILURE);
+      }
+      break;
+    case 'r':
+      rate = atoi(optarg);
+      rate = rate < 4000 ? 4000 : rate;
+      rate = rate > 196000 ? 196000 : rate;
+      break;
+    case 'c':
+      channels = atoi(optarg);
+      channels = channels < 1 ? 1 : channels;
+      channels = channels > 1024 ? 1024 : channels;
+      break;
+    case 'f':
+      freq = atof(optarg);
+      freq = freq < 30.0 ? 30.0 : freq;
+      freq = freq > 5000.0 ? 5000.0 : freq;
+      break;
+    case 'b':
+      buffer_time = atoi(optarg);
+      buffer_time = buffer_time > 1000000 ? 1000000 : buffer_time;
+      break;
+    case 'p':
+      period_time = atoi(optarg);
+      period_time = period_time > 1000000 ? 1000000 : period_time;
+      break;
+    case 'P':
+      nperiods = atoi(optarg);
+      if (nperiods < 2 || nperiods > 1024) {
+	fprintf(stderr, _("Invalid number of periods %d\n"), nperiods);
+	exit(1);
+      }
+      break;
+    case 't':
+      if (*optarg == 'p')
+	test_type = TEST_PINK_NOISE;
+      else if (*optarg == 's')
+	test_type = TEST_SINE;
+      else if (*optarg == 'w')
+	test_type = TEST_WAV;
+      else if (*optarg == 't')
+	test_type = TEST_PATTERN;
+      else if (isdigit(*optarg)) {
+	test_type = atoi(optarg);
+	if (test_type < TEST_PINK_NOISE || test_type > TEST_PATTERN) {
+	  fprintf(stderr, _("Invalid test type %s\n"), optarg);
+	  exit(1);
+	}
+      } else {
+	fprintf(stderr, _("Invalid test type %s\n"), optarg);
+	exit(1);
+      }
+      break;
+    case 'l':
+      nloops = atoi(optarg);
+      break;
+    case 's':
+      speaker = atoi(optarg);
+      speaker = speaker < 1 ? 0 : speaker;
+      speaker = speaker > channels ? 0 : speaker;
+      if (speaker==0) {
+        fprintf(stderr, _("Invalid parameter for -s option.\n"));
+        exit(EXIT_FAILURE);
+      }  
+      break;
+    case 'w':
+      given_test_wav_file = optarg;
+      break;
+    case 'W':
+      wav_file_dir = optarg;
+      break;
+    case 'd':
+      debug = 1;
+      break;
+    default:
+      fprintf(stderr, _("Unknown option '%c'\n"), c);
+      exit(EXIT_FAILURE);
+      break;
+    }
+  }
+
+  if (morehelp) {
+    help();
+    exit(EXIT_SUCCESS);
+  }
+
+  if (test_type == TEST_WAV)
+    format = SND_PCM_FORMAT_S16_LE; /* fixed format */
+
+  printf(_("Playback device is %s\n"), device);
+  printf(_("Stream parameters are %iHz, %s, %i channels\n"), rate, snd_pcm_format_name(format), channels);
+  switch (test_type) {
+  case TEST_PINK_NOISE:
+    printf(_("Using 16 octaves of pink noise\n"));
+    break;
+  case TEST_SINE:
+    printf(_("Sine wave rate is %.4fHz\n"), freq);
+    break;
+  case TEST_WAV:
+    printf(_("WAV file(s)\n"));
+    break;
+
+  }
+
+  if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+    printf(_("Playback open error: %d,%s\n"), err,snd_strerror(err));
+    exit(EXIT_FAILURE);
+  }
+
+  if ((err = set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+    printf(_("Setting of hwparams failed: %s\n"), snd_strerror(err));
+    snd_pcm_close(handle);
+    exit(EXIT_FAILURE);
+  }
+  if ((err = set_swparams(handle, swparams)) < 0) {
+    printf(_("Setting of swparams failed: %s\n"), snd_strerror(err));
+    snd_pcm_close(handle);
+    exit(EXIT_FAILURE);
+  }
+  if (debug) {
+    snd_output_t *log;
+    err = snd_output_stdio_attach(&log, stderr, 0);
+    if (err >= 0) {
+      snd_pcm_dump(handle, log);
+      snd_output_close(log);
+    }
+  }
+
+  frames = malloc(snd_pcm_frames_to_bytes(handle, period_size));
+  if (test_type == TEST_PINK_NOISE)
+    initialize_pink_noise(&pink, 16);
+  
+  if (frames == NULL) {
+    fprintf(stderr, _("No enough memory\n"));
+    exit(EXIT_FAILURE);
+  }
+  if (speaker==0) {
+
+    if (test_type == TEST_WAV) {
+      for (chn = 0; chn < channels; chn++) {
+	if (setup_wav_file(chn) < 0)
+	  exit(EXIT_FAILURE);
+      }
+    }
+
+    for (n = 0; ! nloops || n < nloops; n++) {
+
+      gettimeofday(&tv1, NULL);
+      for(chn = 0; chn < channels; chn++) {
+	int channel=chn;
+	if (channels == 4) {
+	    channel=channels4[chn];
+	}
+	if (channels == 6) {
+	    channel=channels6[chn];
+	}
+	if (channels == 8) {
+	    channel=channels8[chn];
+	}
+        printf(" %d - %s\n", channel, gettext(channel_name[channel]));
+
+        err = write_loop(handle, channel, ((rate*3)/period_size), frames);
+
+        if (err < 0) {
+          fprintf(stderr, _("Transfer failed: %s\n"), snd_strerror(err));
+          free(frames);
+          snd_pcm_close(handle);
+          exit(EXIT_SUCCESS);
+        }
+      }
+      gettimeofday(&tv2, NULL);
+      time1 = (double)tv1.tv_sec + ((double)tv1.tv_usec / 1000000.0);
+      time2 = (double)tv2.tv_sec + ((double)tv2.tv_usec / 1000000.0);
+      time3 = time2 - time1;
+      printf(_("Time per period = %lf\n"), time3 );
+    }
+  } else {
+    if (test_type == TEST_WAV) {
+      if (setup_wav_file(speaker - 1) < 0)
+	exit(EXIT_FAILURE);
+    }
+
+    printf("  - %s\n", gettext(channel_name[speaker-1]));
+    err = write_loop(handle, speaker-1, ((rate*5)/period_size), frames);
+
+    if (err < 0) {
+      fprintf(stderr, _("Transfer failed: %s\n"), snd_strerror(err));
+    }
+  }
+
+
+  free(frames);
+  snd_pcm_close(handle);
+
+  exit(EXIT_SUCCESS);
+}
diff --git a/usr/bin/aconnect b/usr/bin/aconnect
deleted file mode 100755
index 8531bd0..0000000
--- a/usr/bin/aconnect
+++ /dev/null
Binary files differ
diff --git a/usr/bin/alsaloop b/usr/bin/alsaloop
deleted file mode 100755
index 687af13..0000000
--- a/usr/bin/alsaloop
+++ /dev/null
Binary files differ
diff --git a/usr/bin/alsamixer b/usr/bin/alsamixer
deleted file mode 100755
index 535ed30..0000000
--- a/usr/bin/alsamixer
+++ /dev/null
Binary files differ
diff --git a/usr/bin/alsaucm b/usr/bin/alsaucm
deleted file mode 100755
index 83fae41..0000000
--- a/usr/bin/alsaucm
+++ /dev/null
Binary files differ
diff --git a/usr/bin/amidi b/usr/bin/amidi
deleted file mode 100755
index c92cfb6..0000000
--- a/usr/bin/amidi
+++ /dev/null
Binary files differ
diff --git a/usr/bin/amixer b/usr/bin/amixer
deleted file mode 100755
index dbf81a2..0000000
--- a/usr/bin/amixer
+++ /dev/null
Binary files differ
diff --git a/usr/bin/aplay b/usr/bin/aplay
deleted file mode 100755
index a5ab809..0000000
--- a/usr/bin/aplay
+++ /dev/null
Binary files differ
diff --git a/usr/bin/aplaymidi b/usr/bin/aplaymidi
deleted file mode 100755
index d6240ca..0000000
--- a/usr/bin/aplaymidi
+++ /dev/null
Binary files differ
diff --git a/usr/bin/arecord b/usr/bin/arecord
deleted file mode 120000
index 82fbd43..0000000
--- a/usr/bin/arecord
+++ /dev/null
@@ -1 +0,0 @@
-aplay
\ No newline at end of file
diff --git a/usr/bin/arecordmidi b/usr/bin/arecordmidi
deleted file mode 100755
index 947a02d..0000000
--- a/usr/bin/arecordmidi
+++ /dev/null
Binary files differ
diff --git a/usr/bin/aseqdump b/usr/bin/aseqdump
deleted file mode 100755
index 103b4df..0000000
--- a/usr/bin/aseqdump
+++ /dev/null
Binary files differ
diff --git a/usr/bin/aseqnet b/usr/bin/aseqnet
deleted file mode 100755
index d246296..0000000
--- a/usr/bin/aseqnet
+++ /dev/null
Binary files differ
diff --git a/usr/bin/iecset b/usr/bin/iecset
deleted file mode 100755
index 29469c0..0000000
--- a/usr/bin/iecset
+++ /dev/null
Binary files differ
diff --git a/usr/sbin/alsactl b/usr/sbin/alsactl
deleted file mode 100755
index 7a303c8..0000000
--- a/usr/sbin/alsactl
+++ /dev/null
Binary files differ
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644
index 0000000..083e5b4
--- /dev/null
+++ b/utils/Makefile.am
@@ -0,0 +1,4 @@
+EXTRA_DIST = buildrpm
+
+rpm: buildrpm alsa-lib.spec
+	VERSION=$(VERSION) $(srcdir)/buildrpm
diff --git a/utils/alsa-utils.spec.in b/utils/alsa-utils.spec.in
new file mode 100644
index 0000000..8daf3ba
--- /dev/null
+++ b/utils/alsa-utils.spec.in
@@ -0,0 +1,76 @@
+%define ver      @VERSION@
+%define rel      1
+
+Summary: Advanced Linux Sound Architecture (ALSA) - Utils
+Name: alsa-utils
+Version: %ver
+Release: %rel
+Copyright: GPL
+Group: System/Libraries
+Source: ftp://ftp.alsa-project.org/pub/utils/alsa-utils-%{ver}.tar.bz2
+BuildRoot: %{_tmppath}/%{name}-%{version}-root
+URL: http://www.alsa-project.org
+Requires: alsa-lib ncurses
+BuildRequires: alsa-lib-devel ncurses-devel gettext
+
+%description
+
+Advanced Linux Sound Architecture (ALSA) - Utils
+
+%changelog
+* Sun Oct  1 2006 Jaroslav Kysela <perex@perex.cz>
+- add gettext to BuildRequires
+- add more files (see alsa bug#2139)
+
+* Tue Nov 25 2003 Ronny V. Vindenes <sublett@amigascne.org>
+- include all manpages
+
+* Thu Mar  6 2003 Ronny V. Vindenes <sublett@dc-s.com>
+
+- removed wrongly included doc file
+- changed BuildRoot from /var/tmp to _tmppath
+- use standard rpm macros for build & install section
+- updated dependencies
+
+* Tue Nov 20 2001 Jaroslav Kysela <perex@perex.cz>
+
+- changed BuildRoot from /tmp to /var/tmp
+- _prefix and _mandir macros are used for configure and mkdir
+- DESTDIR is used for make install
+
+* Sun Nov 11 2001 Miroslav Benes <mbenes@tenez.cz>
+
+- dangerous command "rpm -rf $RPM_BUILD_ROOT" checks $RPM_BUILD_ROOT variable
+- unset key "Docdir" - on some new systems are documentation in /usr/share/doc
+
+* Mon May 28 1998 Helge Jensen <slog@slog.dk>
+
+- Made SPEC file
+
+%prep
+%setup
+%build
+%configure
+make
+
+%install
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+%makeinstall
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-, root, root)
+
+%doc ChangeLog COPYING README
+
+%{_prefix}/sbin/*
+%{_prefix}/bin/*
+%{_mandir}/man?/*
+%{_mandir}/fr/man?/*
+%{_prefix}/share/alsa/speaker-test/*
+%{_prefix}/share/locale/ja/LC_MESSAGES/*
+%{_prefix}/share/locale/ru/LC_MESSAGES/*
+%{_prefix}/share/man/fr/man8/alsaconf.8.gz
+%{_prefix}/share/sounds/alsa/*
diff --git a/utils/buildrpm b/utils/buildrpm
new file mode 100644
index 0000000..3a9ab86
--- /dev/null
+++ b/utils/buildrpm
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+source=.
+version=`cat $source/../version`
+package=$source/../alsa-utils-$version.tar.bz2
+packagedir=/usr/src/redhat
+xrpmbuild=rpm
+rpmbuild --usage 2> /dev/null > /dev/null && xrpmbuild=rpmbuild
+
+# SuSE path
+if [ -d /usr/src/packages ]; then
+  packagedir=/usr/src/packages
+fi
+
+make -C .. clean
+make -C .. dist
+
+if [ ! -r $package ]; then
+  echo "Error: wrong package: $package"
+  exit 1
+fi
+
+cp -fv $package ${packagedir}/SOURCES
+
+if [ ! -r $source/buildrpm ]; then
+  echo "Error: invalid directory: $source"
+  exit 1
+fi
+
+if [ ! -d ${packagedir} ]; then
+  echo "Error: ${packagedir} directory not found"
+  exit 1
+fi
+
+if [ ! -r $source/alsadriver.spec ]; then
+  cd $source/..
+  ./configure
+  cd utils
+fi
+
+cp -fv $source/alsa-utils.spec ${packagedir}/SPECS
+cd ${packagedir}/SPECS
+$xrpmbuild -ba alsa-utils.spec
+cd ${packagedir}