]> git.alsa-project.org Git - alsa-tools.git/commitdiff
Add echomixer
authorTakashi Iwai <tiwai@suse.de>
Wed, 29 Dec 2004 13:02:53 +0000 (13:02 +0000)
committerTakashi Iwai <tiwai@suse.de>
Wed, 29 Dec 2004 13:02:53 +0000 (13:02 +0000)
Added echomixer by Giuliano Pochini <pochini@shiny.it>
Mixer app for echoaudio drivers.

Makefile
echomixer/AUTHORS [new file with mode: 0644]
echomixer/COPYING [new file with mode: 0644]
echomixer/ChangeLog [new file with mode: 0644]
echomixer/Makefile.am [new file with mode: 0644]
echomixer/README [new file with mode: 0644]
echomixer/configure.in [new file with mode: 0644]
echomixer/configure.in-gtk2 [new file with mode: 0644]
echomixer/cvscompile [new file with mode: 0644]
echomixer/echomixer.c [new file with mode: 0644]

index 324dabbd52999cf60d1d10897e3c7415023e257f..79546a0ad9f9bec995f57b4db03e04b5a12b54d0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ VERSION = 1.0.8rc1
 TOP = .
 SUBDIRS = ac3dec as10k1 envy24control hdsploader hdspconf hdspmixer \
        mixartloader pcxhrloader rmedigicontrol sb16_csp seq sscape_ctl us428control \
-       usx2yloader vxloader
+       usx2yloader vxloader echomixer
 
 all:
        @for i in $(SUBDIRS); do cd $(TOP)/$$i; ./cvscompile $(CVSCOMPILE_ARGS); cd ..; make -C $$i; done
diff --git a/echomixer/AUTHORS b/echomixer/AUTHORS
new file mode 100644 (file)
index 0000000..60fff23
--- /dev/null
@@ -0,0 +1,2 @@
+Giuliano Pochini <pochini@shiny.it>
+
diff --git a/echomixer/COPYING b/echomixer/COPYING
new file mode 100644 (file)
index 0000000..d60c31a
--- /dev/null
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    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.)
+\f
+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
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+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.
+\f
+  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
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  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
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/echomixer/ChangeLog b/echomixer/ChangeLog
new file mode 100644 (file)
index 0000000..efc0d0e
--- /dev/null
@@ -0,0 +1,4 @@
+
+Version 1.4 (2004/12/29) :
+       First release in alsa-tools.
+
diff --git a/echomixer/Makefile.am b/echomixer/Makefile.am
new file mode 100644 (file)
index 0000000..92e6117
--- /dev/null
@@ -0,0 +1,13 @@
+AM_CFLAGS = @ECHOMIXER_CFLAGS@
+bin_PROGRAMS = echomixer
+man_MANS = 
+echomixer_SOURCES = echomixer.c 
+echomixer_LDFLAGS = @ECHOMIXER_LIBS@
+EXTRA_DIST = configure.in-gtk2
+AUTOMAKE_OPTIONS = foreign
+
+alsa-dist: distdir
+       @rm -rf ../distdir/echomixer
+       @mkdir -p ../distdir/echomixer
+       @cp -RLpv $(distdir)/* ../distdir/echomixer
+       @rm -rf $(distdir)
diff --git a/echomixer/README b/echomixer/README
new file mode 100644 (file)
index 0000000..de9ac8e
--- /dev/null
@@ -0,0 +1,19 @@
+
+
+                               Echomixer
+
+
+Emixer is the Linux equivalent of the Echoaudio console application from Echoaudio.
+It is a tool to control all the features of any Echoaudio soundcard. This includes
+clock sources, input and output gains, mixers, etc.
+
+The interface is quite different than Echoaudio console. There are no pans: you set
+the channels gains directly on both mixers and volume controls. Emixer manages
+master, PCM and monitors gains separately and it provides emulation of master volume
+for cards that don't support it in hardware (all non-Vmixer cards). Furthermore it
+has a matrix mixer which is more easy to use than the Echoaudio console interface.
+
+Emixer requires GTK+ 1.2 or above.
+
+For more informations:  http://xoomer.virgilio.it/g_pochini/ea-emixer.html
+
diff --git a/echomixer/configure.in b/echomixer/configure.in
new file mode 100644 (file)
index 0000000..ae78097
--- /dev/null
@@ -0,0 +1,43 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT(echomixer.c)
+AM_INIT_AUTOMAKE(echomixer, 1.0.2)
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_HEADER_STDC
+
+AM_PATH_GTK(1.2.0)
+AM_PATH_ALSA(1.0.0)
+ECHOMIXER_CFLAGS="$CFLAGS $ALSA_CFLAGS $GTK_CFLAGS"
+ECHOMIXER_LIBS="$LIBS $ALSA_LIBS $GTK_LIBS"
+AC_SUBST(ECHOMIXER_CFLAGS)
+AC_SUBST(ECHOMIXER_LIBS)
+#AC_SUBST(ALSACTL)
+
+dnl add the mkdir program 
+AC_ARG_WITH(mkdir-prog,
+[  --with-mkdir-prog=PROG  Complete path and name from mkdir(optional)],
+[mkdir_prog="$withval"], [mkdir_prog=""])
+AC_MSG_CHECKING(for MKDIR)
+if test "$mkdir_prog" != "" ; then
+       MKDIR="$mkdir_prog"
+else
+       if test -x "/bin/mkdir" ; then
+               MKDIR="/bin/mkdir"
+       elif test -x "${bindir}/mkdir" ; then
+               MKDIR="${bindir}/mkdir"
+       elif test -x "${sbindir}/mkdir" ; then
+               MKDIR="${sbindir}/mkdir" 
+       elif test -x "/sbin/mkdir" ; then
+               MKDIR="/sbin/mkdir"
+       elif test -x "/usr/bin/mkdir" ; then
+               MKDIR="/usr/bin/mkdir"
+       elif test -x "/usr/sbin/mkdir" ; then
+               MKDIR="/usr/sbin/mkdir"
+       else
+               MKDIR="not found."
+       fi
+fi
+AC_MSG_RESULT($MKDIR)
+AC_SUBST(MKDIR)
+
+AC_OUTPUT(Makefile)
diff --git a/echomixer/configure.in-gtk2 b/echomixer/configure.in-gtk2
new file mode 100644 (file)
index 0000000..5106a53
--- /dev/null
@@ -0,0 +1,9 @@
+AC_INIT(echomixer.c)
+AM_INIT_AUTOMAKE(echomixer, 0.5.0)
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_HEADER_STDC
+
+PKG_CHECK_MODULES(ECHOMIXER, gtk+-2.0 alsa >= 1.0.0)
+
+AC_OUTPUT(Makefile)
diff --git a/echomixer/cvscompile b/echomixer/cvscompile
new file mode 100644 (file)
index 0000000..3a5537d
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+if test "x$AUTOMAKE_DIR" = "x"; then
+  if test -d /usr/local/share/automake; then
+    AUTOMAKE_DIR=/usr/local/share/automake
+  fi
+  if test -d /usr/share/automake; then
+    AUTOMAKE_DIR="/usr/share/automake"
+  fi
+fi
+
+for f in install-sh mkinstalldirs missing; do
+  cp -av $AUTOMAKE_DIR/$f .
+done
+
+aclocal $ACLOCAL_FLAGS
+automake --add-missing --copy
+touch depcomp
+autoconf
+export CFLAGS='-O2 -Wall -pipe -g'
+echo "CFLAGS=$CFLAGS"
+echo "./configure $@"
+./configure $@
+unset CFLAGS
+make
diff --git a/echomixer/echomixer.c b/echomixer/echomixer.c
new file mode 100644 (file)
index 0000000..d84e8db
--- /dev/null
@@ -0,0 +1,2835 @@
+/*
+ *  ALSA mixer console for Echoaudio soundcards.
+ *  Copyright (C) 2003 Giuliano Pochini <pochini@shiny.it>
+ *
+ *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define EM_VERSION "%s Echomixer v1.0.2"
+
+
+/*******
+  Remove the "//" if you want to compile Echomixer in reverse mode.
+*******/
+
+//#define REVERSE
+
+
+/*******
+ Constants marked with *M* can be modified to customize the interface.
+*******/
+
+
+#define BORDER         6                       // *M* Inner border of GTK containers
+#define SPACING                8                       // *M* Spacing of control sections
+
+// Graphic mixer constants
+#define GM_BARWIDTH    5                       // *M* Width of meters bars
+#define XCELLBORDER    2                       // *M* Space between the grid lines and the content of the cell
+#define YCELLBORDER    2                       // *M*
+#define XCELLDIM       20                      // *M* Width of the cell
+#define YCELLDIM       32                      // Height of the cell
+#define XCELLTOT (1+XCELLBORDER*2+XCELLDIM)    // line + left border + cell + right border
+#define YCELLTOT (1+YCELLBORDER*2+YCELLDIM)
+#define XVOLUME (1+XCELLBORDER+3)              // Position of the volume slider
+#define XMETER (1+XCELLBORDER-GM_BARWIDTH/2+13)        // Position of the VU bar
+
+// VU-meter constants
+#define VU_XGRAF       30                      // Left margin of the graphic
+#define VU_YGRAF       20                      // Top margin
+#define VU_BARWIDTH    6                       // *M* Width of VU-meters bars
+#define VU_BARSEP      2                       // *M* Space between bars
+
+#define SHORTSTEP      1                       // *M* 1dB (when the users moves a slider with cursor keys)
+#define LONGSTEP       6                       // *M* 6dB (with Page up/down or clicking the background)
+#define DIGITAL_MODES  16                      // Max number of digital modes
+#define ECHO_CLOCKS    8                       // Max number of clock sources
+
+#define INPUT 0
+#define OUTPUT 1
+#define ECHO_MAXAUDIO_IOS      32              // The maximum number of inputs + outputs
+#define ECHO_MAXAUDIOINPUTS    32              // Max audio input channels
+#define ECHO_MAXAUDIOOUTPUTS   32              // Max audio output channels
+#define ECHOGAIN_MUTED         (-128)          // Minimum possible gain
+#define ECHOGAIN_MINOUT                (-128)          // Min output gain (unit is 1dB)
+#define ECHOGAIN_MAXOUT                6               // Max output gain (unit is 1dB)
+#define ECHOGAIN_MININP                (-50)           // Min input gain (unit is 0.5dB)
+#define ECHOGAIN_MAXINP                50              // Max input gain (unit is 0.5dB)
+
+// GTK+ adjustment widgets have the mininum value at top and maximum at bottom,
+// position, but we need the opposite. This function puts the scale upside-down.
+#define INVERT(x)      (ECHOGAIN_MINOUT+ECHOGAIN_MAXOUT-(x))
+#define IN_INVERT(x)   (ECHOGAIN_MININP+ECHOGAIN_MAXINP-(x))
+
+// REAL is for debugging only.
+#define REAL
+
+#define CTLID_DEBUG(x) printf x
+//#define CTLID_DEBUG(x)
+
+#define UI_DEBUG(x)
+//#define UI_DEBUG(x) printf x
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <alsa/asoundlib.h>
+
+
+char card[64], cardId[16];
+char dmodeName[DIGITAL_MODES][64], clocksrcName[DIGITAL_MODES][64], spdifmodeName[DIGITAL_MODES][64];
+int nLOut, nIn, fdIn, fdOut, nPOut, ClockMask;
+int ndmodes, nclocksrc, nspdifmodes;
+int GMixerRow, GMixerColumn, Gang;
+int lineinId, pcmoutId, lineoutId, mixerId, vmixerId, p4InId, p4OutId, dmodeId, clocksrcId, spdifmodeId, vuswitchId, vumetersId, channelsId, phantomId;
+int metersStreams, metersNumber, metersTypes;
+int outvolCount;
+int mouseY, mouseButton;
+int dmodeVal, clocksrcVal, spdifmodeVal;
+int VUwidth, VUheight, Mixwidth, Mixheight;
+
+#define NOPOS 999999
+struct geometry {
+  int st;              // window status: 0 = hidden ; 1 = visible ; NOPOS = no stored setting
+  GtkWidget *toggler;  // The toggle button that controls this window
+  int x, y;
+  int w, h;
+} Mainw_geom, Miscw_geom, PVw_geom, LVw_geom, Mixerw_geom, Vmixerw_geom, VUw_geom, GMw_geom;
+
+// This structure contains the first and the last row of each section of the graphic mixer window
+struct {
+  int Monitor; // The first is always 0
+  int VmixerFirst, VmixerLast;
+  int LineOut; // There is only one row
+} GMixerSection;
+
+struct mixel {
+  int id;
+  int Gain;
+};
+
+snd_ctl_t *ctlhandle;
+
+
+#if __GNUC__ == 3              // gcc 2.x doesn't like unnamed unions inside structures
+
+struct mixerControl_s {
+  union {               // Currently selected channels
+    int vchannel;
+    int input;
+  };
+  union {               // Number of channels
+    int vchannels;
+    int inputs;
+  };
+  int output, outputs;
+  int id;
+  GtkWidget *window;
+  GtkWidget *volume[ECHO_MAXAUDIOOUTPUTS];
+  GtkWidget *label[ECHO_MAXAUDIOOUTPUTS];
+  GtkObject *adj[ECHO_MAXAUDIOOUTPUTS];
+  GtkWidget *outsel[ECHO_MAXAUDIOOUTPUTS];
+  union {
+    GtkWidget *inpsel[ECHO_MAXAUDIOINPUTS];
+    GtkWidget *vchsel[ECHO_MAXAUDIOOUTPUTS];
+  };
+  struct mixel mixer[ECHO_MAXAUDIOOUTPUTS][ECHO_MAXAUDIOOUTPUTS];
+} mixerControl, vmixerControl;
+
+#else
+
+struct mixerControl_s {
+  int vchannel;
+  int input;
+  int vchannels;
+  int inputs;
+  int output, outputs;
+  int id;
+  GtkWidget *window;
+  GtkWidget *volume[ECHO_MAXAUDIOOUTPUTS];
+  GtkWidget *label[ECHO_MAXAUDIOOUTPUTS];
+  GtkObject *adj[ECHO_MAXAUDIOOUTPUTS];
+  GtkWidget *outsel[ECHO_MAXAUDIOOUTPUTS];
+  GtkWidget *inpsel[ECHO_MAXAUDIOINPUTS];
+  GtkWidget *vchsel[ECHO_MAXAUDIOOUTPUTS];
+  struct mixel mixer[ECHO_MAXAUDIOOUTPUTS][ECHO_MAXAUDIOOUTPUTS];
+} mixerControl, vmixerControl;
+
+#endif
+
+
+struct VolumeControl {
+  int input, output;                           // Currently selected channels
+  int inputs, outputs;
+  int id;
+  GtkWidget *window;
+  GtkWidget *volume[ECHO_MAXAUDIOOUTPUTS];
+  GtkWidget *label[ECHO_MAXAUDIOOUTPUTS];
+  GtkObject *adj[ECHO_MAXAUDIOOUTPUTS];
+  int Gain[ECHO_MAXAUDIOOUTPUTS];
+} lineinControl, lineoutControl, pcmoutControl;
+
+GtkWidget *p4dbuOut[ECHO_MAXAUDIOOUTPUTS], *p4dbuIn[ECHO_MAXAUDIOINPUTS];      // +4dBu/-10dBV toggles
+GtkWidget *clocksrc_menuitem[ECHO_CLOCKS];
+GtkWidget *dmodeOpt, *clocksrcOpt, *spdifmodeOpt, *phantomToggle;
+GtkWidget *window, *Mainwindow, *Miscwindow, *LVwindow, *VUwindow, *GMwindow;
+GtkWidget *VUdarea, *Mixdarea;
+gint VUtimer, Mixtimer, clocksrctimer;
+
+GdkGC *gc=0;
+static GdkPixmap *VUpixmap = NULL;
+static GdkPixmap *Mixpixmap = NULL;
+GdkFont *fnt;
+
+
+int CountBits(int n) {
+  int c;
+
+  c=0;
+  while (n) {
+    c++;
+    n&=(n-1);
+  }
+  return(c);
+}
+
+
+
+void ClampOutputVolume(int *v) {
+
+  if (*v>ECHOGAIN_MAXOUT)
+    *v=ECHOGAIN_MAXOUT;
+  else if (*v<ECHOGAIN_MINOUT)
+    *v=ECHOGAIN_MINOUT;
+}
+
+
+
+void ClampInputVolume(int *v) {
+
+  if (*v>ECHOGAIN_MAXINP)
+    *v=ECHOGAIN_MAXINP;
+  else if (*v<ECHOGAIN_MININP)
+    *v=ECHOGAIN_MININP;
+}
+
+
+
+// -128 dB means muted, that is -infinite dB
+int Add_dB (int a, int b) {
+
+  if (a==ECHOGAIN_MINOUT || b==ECHOGAIN_MINOUT)
+    return(ECHOGAIN_MINOUT);
+  a+=b;
+  if (a<ECHOGAIN_MINOUT)
+    return(ECHOGAIN_MINOUT);
+  return(a);
+}
+
+
+
+char *strOutGain(char *s, int g) {
+
+  if (g==ECHOGAIN_MINOUT)
+    strcpy(s, "mute");
+  else
+    sprintf(s, "%+d", g);
+  return(s);
+}
+
+
+
+int ADATmode() {
+  return(!memcmp(dmodeName[dmodeVal], "ADAT", 4));
+}
+
+
+
+// Write an enumerated ALSA control
+int SetEnum(int numid, int val) {
+  int err;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, numid==clocksrcId ? SND_CTL_ELEM_IFACE_PCM : SND_CTL_ELEM_IFACE_CARD);
+  snd_ctl_elem_id_set_numid(id, numid);
+  snd_ctl_elem_value_set_id(control, id);
+  snd_ctl_elem_value_set_enumerated(control, 0, val);
+  if ((err=snd_ctl_elem_write(ctlhandle, control)) < 0)
+    printf("Control %s element write error: %s\n", card, snd_strerror(err));
+  return(err);
+}
+
+
+
+// Read an enumerated ALSA control
+int GetEnum(int numid) {
+  int err, val;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, numid==clocksrcId ? SND_CTL_ELEM_IFACE_PCM : SND_CTL_ELEM_IFACE_CARD);
+  snd_ctl_elem_id_set_numid(id, numid);
+  snd_ctl_elem_value_set_id(control, id);
+  if ((err=snd_ctl_elem_read(ctlhandle, control)) < 0)
+    printf("Control %s element read error: %s\n", card, snd_strerror(err));
+  val=snd_ctl_elem_value_get_enumerated(control, 0);
+  return(val);
+}
+
+
+
+// Turn VU-meters on/off
+void SetVUmeters(int onoff) {
+  static signed char oncount=0;
+  int err;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+  if (onoff)
+    oncount++;
+  else
+    if (--oncount<0)
+      oncount=0;
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
+  snd_ctl_elem_id_set_numid(id, vuswitchId);
+  snd_ctl_elem_value_set_id(control, id);
+  snd_ctl_elem_value_set_integer(control, 0, !!oncount);
+  if ((err=snd_ctl_elem_write(ctlhandle, control)) < 0) {
+    printf("Control %s element write error: %s\n", card, snd_strerror(err));
+  }
+}
+
+
+
+void GetVUmeters(int *InLevel, int *InPeak, int *OutLevel, int *OutPeak, int *VirLevel, int *VirPeak) {
+  int err, i, m;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  snd_ctl_elem_id_set_numid(id, vumetersId);
+  snd_ctl_elem_value_set_id(control, id);
+  if ((err = snd_ctl_elem_read(ctlhandle, control)) < 0) {
+    printf("Control %s element read error: %s\n", card, snd_strerror(err));
+    return;
+  }
+
+  m=0;
+  for (i=0; i<nLOut; i++) {
+    OutLevel[i]=snd_ctl_elem_value_get_integer(control, m++);
+    OutPeak[i]=snd_ctl_elem_value_get_integer(control, m++);
+  }
+
+  m=1*metersNumber*metersTypes;
+  for (i=0; i<nIn; i++) {
+    InLevel[i]=snd_ctl_elem_value_get_integer(control, m++);
+    InPeak[i]=snd_ctl_elem_value_get_integer(control, m++);
+  }
+  
+  if (metersStreams==3) {      // Has PCM levels (Mia only) ?
+    m=2*metersNumber*metersTypes;
+#ifdef REAL
+    for (i=0; i<nPOut; i++) {
+      VirLevel[i]=snd_ctl_elem_value_get_integer(control, m++);
+      VirPeak[i]=snd_ctl_elem_value_get_integer(control, m++);
+    }
+#else
+    for (i=0; i<nPOut; i++) {
+      VirLevel[i]=-20;
+      VirPeak[i]=-10;
+    }
+#endif
+  }
+}
+
+
+
+#ifdef REVERSE
+
+// Enable/disable widgets that control ADAT digital channels
+void SetSensitivity(int enable) {
+  int i;
+
+  for (i=fdOut+2; i<nLOut; i++) {
+    if (!enable && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mixerControl.outsel[i])))
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mixerControl.outsel[0]), TRUE);
+    if (mixerId)
+      gtk_widget_set_sensitive(mixerControl.outsel[i], enable);
+    if (vmixerId) {
+      if (!enable && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(vmixerControl.outsel[i])))
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vmixerControl.outsel[0]), TRUE);
+      gtk_widget_set_sensitive(vmixerControl.outsel[i], enable);
+    }
+    if (pcmoutId) {
+      gtk_widget_set_sensitive(pcmoutControl.label[i], enable);
+      gtk_widget_set_sensitive(pcmoutControl.volume[i], enable);
+    }
+    // Line-out control is always present
+    gtk_widget_set_sensitive(lineoutControl.label[i], enable);
+    gtk_widget_set_sensitive(lineoutControl.volume[i], enable);
+  }
+  for (i=fdIn+2; i<nIn; i++) {
+    gtk_widget_set_sensitive(mixerControl.label[i], enable);
+    gtk_widget_set_sensitive(mixerControl.volume[i], enable);
+  }
+  if (!enable && mixerControl.input>=fdIn+2)
+    mixerControl.input=0;
+}
+
+#else // REVERSE
+
+// Enable/disable widgets that control ADAT digital channels
+void SetSensitivity(int enable) {
+  int i;
+
+  for (i=fdIn+2; i<nIn; i++) {
+    if (!enable && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mixerControl.inpsel[i])))
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mixerControl.inpsel[0]), TRUE);
+    gtk_widget_set_sensitive(mixerControl.inpsel[i], enable);
+  }
+  for (i=fdOut+2; i<nLOut; i++) {
+    if (mixerId) {
+      gtk_widget_set_sensitive(mixerControl.label[i], enable);
+      gtk_widget_set_sensitive(mixerControl.volume[i], enable);
+    }
+    if (vmixerId) {
+      gtk_widget_set_sensitive(vmixerControl.label[i], enable);
+      gtk_widget_set_sensitive(vmixerControl.volume[i], enable);
+    }
+    if (pcmoutId) {
+      gtk_widget_set_sensitive(pcmoutControl.label[i], enable);
+      gtk_widget_set_sensitive(pcmoutControl.volume[i], enable);
+    }
+    // Line-out control is always present
+    gtk_widget_set_sensitive(lineoutControl.label[i], enable);
+    gtk_widget_set_sensitive(lineoutControl.volume[i], enable);
+  }
+  if (!enable && mixerControl.output>=fdOut+2)
+    mixerControl.output=0;
+  if (vmixerId && !enable && vmixerControl.output>=fdOut+2)
+    vmixerControl.output=0;
+}
+
+#endif // REVERSE
+
+
+// At startup this functions reads the current nominal levels and sets the switches accordingly.
+void InitNominalLevelGUI(int numid) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  int err, i, n;
+  GtkWidget **w;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  if (numid==p4InId) {
+    snd_ctl_elem_id_set_numid(id, p4InId);
+    n=fdIn;
+    w=p4dbuIn;
+  } else {
+    snd_ctl_elem_id_set_numid(id, p4OutId);
+    n=fdOut;
+    w=p4dbuOut;
+  }
+  snd_ctl_elem_value_set_id(control, id);
+  if ((err=snd_ctl_elem_read(ctlhandle, control))<0)
+    printf("Control %s element read error: %s\n", card, snd_strerror(err));
+  for (i=0; i<n; i++)
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w[i]), !snd_ctl_elem_value_get_integer(control, i));        // FALSE is +4 here
+}
+
+
+
+// At startup this functions reads if the dithering is enabled sets the button accordingly.
+void InitPhantomPowerGUI(int numid) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  int err;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
+  snd_ctl_elem_id_set_numid(id, phantomId);
+  snd_ctl_elem_value_set_id(control, id);
+  if ((err=snd_ctl_elem_read(ctlhandle, control))<0)
+    printf("Control %s element read error: %s\n", card, snd_strerror(err));
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(phantomToggle), snd_ctl_elem_value_get_integer(control, 0));
+}
+
+
+
+// Read current control settings.
+void ReadControl(int *vol, int channels, int volId) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  int err, ch;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  snd_ctl_elem_id_set_numid(id, volId);
+  snd_ctl_elem_value_set_id(control, id);
+  if ((err=snd_ctl_elem_read(ctlhandle, control))<0) {
+    printf("Control %s element read error: %s\n", card, snd_strerror(err));
+    return;
+  }
+
+  for (ch=0; ch<channels; ch++)
+    vol[ch]=snd_ctl_elem_value_get_integer(control, ch);
+}
+
+
+
+int SetMixerGain(struct mixel *mxl, int Gain) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  int err;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  snd_ctl_elem_id_set_numid(id, mxl->id);
+  snd_ctl_elem_value_set_id(control, id);
+  snd_ctl_elem_value_set_integer(control, 0, Gain);
+  if ((err = snd_ctl_elem_write(ctlhandle, control)) < 0) {
+    printf("Control %s element write error: %s\n", card, snd_strerror(err));
+    return(err);
+  }
+  return(0);
+}
+
+
+
+// Read current (v)mixer settings.
+void ReadMixer(struct mixerControl_s *mixer) {
+  int err, in, out;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+#ifndef REAL
+    return;
+#endif
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+
+  for (out=0; out<mixer->outputs; out++) {
+    for (in=0; in<mixer->inputs; in++) {
+      snd_ctl_elem_id_set_numid(id, mixer->mixer[out][in].id);
+      snd_ctl_elem_value_set_id(control, id);
+      if ((err=snd_ctl_elem_read(ctlhandle, control))<0)
+        printf("InitMixer - Control %s element read error: %s\n", card, snd_strerror(err));
+      mixer->mixer[out][in].Gain=snd_ctl_elem_value_get_integer(control, 0);
+    }
+  }
+}
+
+
+
+void GetChannels(void) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  int err;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_HWDEP);
+  snd_ctl_elem_id_set_numid(id, channelsId);
+  snd_ctl_elem_value_set_id(control, id);
+  if ((err = snd_ctl_elem_read(ctlhandle, control)) < 0) {
+    printf("GetChannels() read error: %s\n", snd_strerror(err));
+    exit(1);
+  }
+  if (!nIn) {  // Only read the first time (mainly for debugging, see #define REAL)
+    nIn=snd_ctl_elem_value_get_integer(control, 0);    // Number of input channels
+    fdIn=snd_ctl_elem_value_get_integer(control, 1);   // First digital in (= number of analog input channels)
+    nLOut=snd_ctl_elem_value_get_integer(control, 2);  // Number of output channels
+    fdOut=snd_ctl_elem_value_get_integer(control, 3);  // First digital out
+    nPOut=snd_ctl_elem_value_get_integer(control, 4);  // Number of virtual output channels (==nLOut on non-vmixer cards)
+  }
+  ClockMask=snd_ctl_elem_value_get_integer(control, 5);        // Bitmask of available input clocks
+}
+
+
+
+// Read what input clocks are valid and sets the pop-down menu accordingly
+gint CheckInputs(gpointer unused) {
+  int i;
+
+  GetChannels();
+  for (i=0; i<nclocksrc; i++)
+    gtk_widget_set_sensitive(clocksrc_menuitem[i], !!(ClockMask & (1<<i)));
+  return(TRUE);
+}
+
+
+
+// Draw the matrix mixer
+gint DrawMixer(gpointer unused) {
+  GdkRectangle update_rect;
+  int InLevel[ECHO_MAXAUDIOINPUTS];
+  int InPeak[ECHO_MAXAUDIOINPUTS];
+  int OutLevel[ECHO_MAXAUDIOOUTPUTS];
+  int OutPeak[ECHO_MAXAUDIOOUTPUTS];
+  int VirLevel[ECHO_MAXAUDIOOUTPUTS];//maxpipes
+  int VirPeak[ECHO_MAXAUDIOOUTPUTS];
+  static int InClip[ECHO_MAXAUDIOINPUTS];
+  static int OutClip[ECHO_MAXAUDIOOUTPUTS];
+  char str[8];
+  int i, o, y, dB, db, inchannels, outchannels;
+  GdkColor Grid={0x787878, 0, 0, 0};
+  GdkColor Labels={0x9694C4, 0, 0, 0};
+  GdkColor Bars={0x00FF00, 0, 0, 0};
+  GdkColor Bars1={0x000000, 0, 0, 0};
+  GdkColor Peak={0x1BABFF, 0, 0, 0};
+  GdkColor Level={0xC0B000, 0, 0, 0};
+  GdkColor Hilight={0x000078, 0, 0, 0};
+  GdkColor Hilight2={0x600000, 0, 0, 0};
+
+  if (ADATmode()) {
+    inchannels=nIn;
+    outchannels=nLOut;
+  } else {
+    inchannels=fdIn+2;
+    outchannels=fdOut+2;
+  }
+
+  if (!Mixpixmap)
+    return(TRUE);
+
+  update_rect.x = 0;
+  update_rect.y = 0;
+  update_rect.width = Mixwidth;
+  update_rect.height = Mixheight;
+  GetVUmeters(InLevel, InPeak, OutLevel, OutPeak, VirLevel, VirPeak);
+
+  if (!gc) {
+    gc=gdk_gc_new(gtk_widget_get_parent_window(Mixdarea));
+    for (i=0; i<nIn; i++)
+      InClip[i]=0;
+    for (i=0; i<nLOut; i++)
+      OutClip[i]=0;
+  }
+
+  gdk_draw_rectangle(Mixpixmap, Mixdarea->style->black_gc, TRUE, 0, 0, Mixwidth, Mixheight);
+
+  // Highlight
+  gdk_gc_set_foreground(gc, &Hilight);
+  gdk_draw_rectangle(Mixpixmap, gc, TRUE, 0, YCELLTOT*mixerControl.input, XCELLTOT*(mixerControl.output+1), YCELLTOT);
+  gdk_draw_rectangle(Mixpixmap, gc, TRUE, XCELLTOT*(mixerControl.output+1), YCELLTOT*mixerControl.input, XCELLTOT, Mixheight);
+  if (vmixerId) {
+    gdk_gc_set_foreground(gc, &Hilight2);
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, 0, YCELLTOT*(GMixerSection.VmixerFirst+vmixerControl.vchannel), XCELLTOT*(vmixerControl.output+1), YCELLTOT);
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, XCELLTOT*(vmixerControl.output+1), YCELLTOT*(GMixerSection.VmixerFirst+vmixerControl.vchannel), XCELLTOT, Mixheight);
+  }
+
+  // Draw the grid
+  gdk_gc_set_font(gc, fnt);
+  // Horizontal lines and input channel labels
+  for (i=0; i<GMixerSection.LineOut; i++) {
+    gdk_gc_set_foreground(gc, &Grid);
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, 0, YCELLTOT*(i+1)-1, Mixwidth, 1);
+    if (i<fdIn)
+      sprintf(str, "A%d", i);          // Analog
+    else if (i<nIn)
+      sprintf(str, "D%d", i-fdIn);     // Digital
+    else
+      sprintf(str, "V%d", i-nIn);      // Virtual
+    gdk_gc_set_foreground(gc, &Labels);
+    gdk_draw_string(Mixpixmap, fnt, gc, 1, YCELLTOT*i+(YCELLTOT/2)+4, str);
+  }
+  // Vertical lines and output channel labels
+  for (o=0; o<nLOut; o++) {
+    gdk_gc_set_foreground(gc, &Grid);
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, XCELLTOT*(o+1), 0, 1, Mixheight);
+    if (o<fdOut)
+      sprintf(str, "A%d", o);
+    else
+      sprintf(str, "D%d", o-fdOut);
+    gdk_gc_set_foreground(gc, &Labels);
+    gdk_draw_string(Mixpixmap, fnt, gc, XCELLTOT*(o+1)+(XCELLTOT/2)-6, YCELLTOT*GMixerSection.LineOut+YCELLTOT+8, str);
+  }
+  gdk_draw_string(Mixpixmap, fnt, gc, 1, 8, "In");
+  gdk_draw_string(Mixpixmap, fnt, gc, 1, YCELLTOT*GMixerSection.LineOut+YCELLTOT+8, "Out");
+  gdk_gc_set_foreground(gc, &Grid);
+  gdk_draw_rectangle(Mixpixmap, gc, TRUE, 0, YCELLTOT*(GMixerSection.LineOut+1)-1, Mixwidth, 1);
+
+  // Draw input levels and peaks
+  for (i=0; i<inchannels; i++) {
+    y=YCELLTOT*i+YCELLBORDER;
+    dB=InLevel[i];
+    db=dB>>2;
+    gdk_gc_set_foreground(gc, &Bars);
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER, y-db, GM_BARWIDTH, YCELLDIM+db);
+    if ((Bars1.pixel=(((dB&3)*0x40)<<8))) {
+      gdk_gc_set_foreground(gc, &Bars1);
+      gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER, y-db-1, GM_BARWIDTH, 1);
+    }
+  }
+  gdk_gc_set_foreground(gc, &Peak);
+  for (i=0; i<inchannels; i++) {
+    db=InPeak[i]>>2;
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER, YCELLTOT*i+YCELLBORDER-db, GM_BARWIDTH, 1);
+  }
+
+  // Draw vchannels levels and peaks (Vmixer cards only)
+  if (vmixerId) {
+    for (i=0; i<vmixerControl.vchannels; i++) {
+      y=YCELLTOT*(i+GMixerSection.VmixerFirst)+YCELLBORDER;
+      dB=VirLevel[i];
+      db=dB>>2;
+      gdk_gc_set_foreground(gc, &Bars);
+      gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER, y-db, GM_BARWIDTH, YCELLDIM+db);
+      if ((Bars1.pixel=(((dB&3)*0x40)<<8))) {
+        gdk_gc_set_foreground(gc, &Bars1);
+        gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER, y-db-1, GM_BARWIDTH, 1);
+      }
+    }
+    gdk_gc_set_foreground(gc, &Peak);
+    for (i=0; i<vmixerControl.vchannels; i++) {
+      db=VirPeak[i]>>2;
+      gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER, YCELLTOT*(i+GMixerSection.VmixerFirst)+YCELLBORDER-db, GM_BARWIDTH, 1);
+    }
+  }
+
+  // Draw output levels, peaks and volumes
+  y=YCELLTOT*GMixerSection.LineOut+YCELLBORDER;
+  for (o=0; o<outchannels; o++) {
+    dB=OutLevel[o];
+    db=dB>>2;
+    gdk_gc_set_foreground(gc, &Bars);
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER+XCELLTOT*(o+1), y-db, GM_BARWIDTH, YCELLDIM+db);
+    if ((Bars1.pixel=(((dB&3)*0x40)<<8))) {
+      gdk_gc_set_foreground(gc, &Bars1);
+      gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER+XCELLTOT*(o+1), y-db-1, GM_BARWIDTH, 1);
+    }
+    db=OutPeak[o]>>2;
+    gdk_gc_set_foreground(gc, &Peak);
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER+XCELLTOT*(o+1), y-db, GM_BARWIDTH, 1);
+    gdk_gc_set_foreground(gc, &Level);
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, XVOLUME+XCELLTOT*(o+1), y, 1, YCELLDIM);
+    db=lineoutControl.Gain[o]>>2;
+    gdk_draw_rectangle(Mixpixmap, gc, TRUE, XVOLUME-2+XCELLTOT*(o+1), y-db, 5, 1);
+  }
+
+  // Draw monitor mixer elements
+  for (o=0; o<outchannels; o++) {
+    for (i=0; i<inchannels; i++) {
+      y=YCELLTOT*i+YCELLBORDER;
+      dB=Add_dB(mixerControl.mixer[o][i].Gain, InLevel[i]);
+      db=dB>>2;
+      if (db<-YCELLDIM)
+        db=-YCELLDIM;
+      gdk_gc_set_foreground(gc, &Bars);
+      gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER+XCELLTOT*(o+1), y-db, GM_BARWIDTH, YCELLDIM+db);
+      if (dB>-(YCELLDIM<<2))
+        if ((Bars1.pixel=(((dB&3)*0x40)<<8))) {
+          gdk_gc_set_foreground(gc, &Bars1);
+          gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER+XCELLTOT*(o+1), y-db-1, GM_BARWIDTH, 1);
+        }
+      gdk_gc_set_foreground(gc, &Level);
+      gdk_draw_rectangle(Mixpixmap, gc, TRUE, XVOLUME+XCELLTOT*(o+1), y, 1, YCELLDIM);
+      db=mixerControl.mixer[o][i].Gain>>2;
+      gdk_draw_rectangle(Mixpixmap, gc, TRUE, XVOLUME-2+XCELLTOT*(o+1), y-db, 5, 1);
+    }
+  }
+
+  // Draw vmixer elements
+  if (vmixerId) {
+    for (o=0; o<outchannels; o++) {
+      for (i=0; i<vmixerControl.vchannels; i++) {
+        y=YCELLTOT*(i+GMixerSection.VmixerFirst)+YCELLBORDER;
+        dB=Add_dB(vmixerControl.mixer[o][i].Gain, VirLevel[i]);
+        db=dB>>2;
+        if (db<-YCELLDIM)
+          db=-YCELLDIM;
+        gdk_gc_set_foreground(gc, &Bars);
+        gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER+XCELLTOT*(o+1), y-db, GM_BARWIDTH, YCELLDIM+db);
+        if (dB>-(YCELLDIM<<2))
+          if ((Bars1.pixel=(((dB&3)*0x40)<<8))) {
+            gdk_gc_set_foreground(gc, &Bars1);
+            gdk_draw_rectangle(Mixpixmap, gc, TRUE, XMETER+XCELLTOT*(o+1), y-db-1, GM_BARWIDTH, 1);
+          }
+        gdk_gc_set_foreground(gc, &Level);
+        gdk_draw_rectangle(Mixpixmap, gc, TRUE, XVOLUME+XCELLTOT*(o+1), y, 1, YCELLDIM);
+        db=vmixerControl.mixer[o][i].Gain>>2;
+        gdk_draw_rectangle(Mixpixmap, gc, TRUE, XVOLUME-2+XCELLTOT*(o+1), y-db, 5, 1);
+      }
+    }
+  }
+
+  gtk_widget_draw(Mixdarea, &update_rect);
+  return(TRUE);
+}
+
+
+
+// Draw the VU-meter
+gint DrawVUmeters(gpointer unused) {
+  GdkRectangle update_rect;
+  int InLevel[ECHO_MAXAUDIOINPUTS];
+  int InPeak[ECHO_MAXAUDIOINPUTS];
+  int OutLevel[ECHO_MAXAUDIOOUTPUTS];
+  int OutPeak[ECHO_MAXAUDIOOUTPUTS];
+  int VirLevel[ECHO_MAXAUDIOOUTPUTS];//maxpipes
+  int VirPeak[ECHO_MAXAUDIOOUTPUTS];
+  static int InClip[ECHO_MAXAUDIOINPUTS];
+  static int OutClip[ECHO_MAXAUDIOOUTPUTS];
+  int i, x, dB;
+  char str[16];
+  GdkColor Selected={0xC86060, 0, 0, 0};
+  GdkColor Grid={0x9694C4, 0, 0, 0};
+  GdkColor Grid2={0x646383, 0, 0, 0};
+  GdkColor dBValues={0x00B000, 0, 0, 0};
+  GdkColor AnBars={0x00E0B8, 0, 0, 0};
+  GdkColor DiBars={0x98E000, 0, 0, 0};
+  GdkColor ClipPeak={0, 0, 0, 0};
+  GdkColor Peak={0x00FF00, 0, 0, 0};
+
+  if (!VUpixmap)
+    return(TRUE);
+
+  update_rect.x = 0;
+  update_rect.y = 0;
+  update_rect.width = VUwidth;
+  update_rect.height = VUheight;
+  GetVUmeters(InLevel, InPeak, OutLevel, OutPeak, VirLevel, VirPeak);
+
+  if (!gc) {
+    gc=gdk_gc_new(gtk_widget_get_parent_window(VUdarea));
+    for (i=0; i<nIn; i++)
+      InClip[i]=0;
+    for (i=0; i<nLOut; i++)
+      OutClip[i]=0;
+  }
+
+  // Clear the image
+  gdk_draw_rectangle(VUpixmap, VUdarea->style->black_gc, TRUE, 0, 0, VUwidth, VUheight);
+
+  // Draw the dB scale and the grid
+  gdk_gc_set_font(gc, fnt);
+  gdk_gc_set_foreground(gc, &Peak);
+  gdk_draw_string(VUpixmap, fnt, gc, 2, VU_YGRAF-12+4, "  dB");
+  for (i=0; i<=120; i+=12) {
+    sprintf(str, "%4d", -i);
+    gdk_gc_set_foreground(gc, &dBValues);
+    gdk_draw_string(VUpixmap, fnt, gc, 2, VU_YGRAF+i+4, str);
+    gdk_gc_set_foreground(gc, &Grid);
+    gdk_draw_rectangle(VUpixmap, gc, TRUE, VU_XGRAF, VU_YGRAF+i, VUwidth-VU_XGRAF, 1);
+  }
+  gdk_gc_set_foreground(gc, &Grid2);
+  gdk_draw_rectangle(VUpixmap, gc, TRUE, VU_XGRAF, VU_YGRAF+128, VUwidth-VU_XGRAF, 1);
+
+  x=VU_XGRAF+VU_BARSEP;
+
+  // Draw inputs
+  for (i=0; i<nIn; i++) {
+    if (i<fdIn)
+      gdk_gc_set_foreground(gc, &AnBars);
+    else
+      gdk_gc_set_foreground(gc, &DiBars);
+    dB=InLevel[i];
+    gdk_draw_rectangle(VUpixmap, gc, TRUE, x, VU_YGRAF-dB, VU_BARWIDTH, 129+VU_YGRAF-(VU_YGRAF-dB));
+
+    dB=InPeak[i];
+    if (dB==0)
+      InClip[i]=64;
+    if (InClip[i]) {
+      InClip[i]--;
+      ClipPeak.pixel=(InClip[i]<<18)+((255-(InClip[i]*3))<<8);
+      gdk_gc_set_foreground(gc, &ClipPeak);
+    } else {
+      gdk_gc_set_foreground(gc, &Peak);
+    }
+    gdk_draw_rectangle(VUpixmap, gc, TRUE, x, VU_YGRAF-dB, VU_BARWIDTH, 1);
+    if (mixerControl.input==i) {
+      gdk_gc_set_foreground(gc, &Selected);
+      gdk_draw_rectangle(VUpixmap, gc, TRUE, x+1, VU_YGRAF+128+3, VU_BARWIDTH-2, 1);
+      gdk_draw_rectangle(VUpixmap, gc, TRUE, x, VU_YGRAF+128+4, VU_BARWIDTH, 1);
+    }
+    x+=VU_BARWIDTH+VU_BARSEP;
+  }
+
+  // Draw outputs
+  x+=VU_BARWIDTH+VU_BARSEP;
+  for (i=0; i<nLOut; i++) {
+    if (i<fdOut)
+      gdk_gc_set_foreground(gc, &AnBars);
+    else
+      gdk_gc_set_foreground(gc, &DiBars);
+    dB=OutLevel[i];
+    gdk_draw_rectangle(VUpixmap, gc, TRUE, x, VU_YGRAF-dB, VU_BARWIDTH, 129+VU_YGRAF-(VU_YGRAF-dB));
+
+    dB=OutPeak[i];
+    if (dB==0)
+      OutClip[i]=64;
+    if (OutClip[i]) {
+      OutClip[i]--;
+      ClipPeak.pixel=(OutClip[i]<<18)+((255-(OutClip[i]*3))<<8);
+      gdk_gc_set_foreground(gc, &ClipPeak);
+    } else {
+      gdk_gc_set_foreground(gc, &Peak);
+    }
+    gdk_draw_rectangle(VUpixmap, gc, TRUE, x, VU_YGRAF-dB, VU_BARWIDTH, 1);
+    if (mixerControl.output==i) {
+      gdk_gc_set_foreground(gc, &Selected);
+      gdk_draw_rectangle(VUpixmap, gc, TRUE, x+1, VU_YGRAF+128+3, VU_BARWIDTH-2, 1);
+      gdk_draw_rectangle(VUpixmap, gc, TRUE, x, VU_YGRAF+128+4, VU_BARWIDTH, 1);
+    }
+    x+=VU_BARWIDTH+VU_BARSEP;
+  }
+
+  gtk_widget_draw(VUdarea, &update_rect);
+
+  return(TRUE);
+}
+
+
+
+///////////////////// GUI events
+
+
+#ifdef REVERSE
+
+void Mixer_Output_selector_clicked(GtkWidget *widget, gpointer och) {
+  int ich, val;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+  if (mixerControl.output==(int)och)
+    return;
+
+  mixerControl.output=(int)och;
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  for (ich=0; ich<nIn; ich++) {
+    val=INVERT(mixerControl.mixer[mixerControl.output][ich].Gain);
+    gtk_adjustment_set_value(GTK_ADJUSTMENT(mixerControl.adj[ich]), (gfloat)val);
+  }
+}
+
+#else // REVERSE
+
+void Mixer_Input_selector_clicked(GtkWidget *widget, gpointer ich) {
+  int och, val;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+  if (mixerControl.input==(int)ich)
+    return;
+
+  mixerControl.input=(int)ich;
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  for (och=0; och<nLOut; och++) {
+    val=INVERT(mixerControl.mixer[och][mixerControl.input].Gain);
+    gtk_adjustment_set_value(GTK_ADJUSTMENT(mixerControl.adj[och]), (gfloat)val);
+  }
+}
+
+#endif // REVERSE
+
+
+#ifdef REVERSE
+
+static gint Gmixer_button_press(GtkWidget *widget, GdkEventButton *event) {
+
+  GMixerRow=(int)event->y/YCELLTOT;
+  GMixerColumn=(int)event->x/XCELLTOT-1;
+
+  /* grab_focus must follow set_active because the latter causes
+     Vmixer_*_selector_clicked() to be called and, in turn,
+     Vmixer_volume_changed() which changes mixerControl.input
+     (or .output in non-reverse mode). grab_focus then causes
+     Vmixer_volume_clicked() to be called and that event handler
+     finally sets the correct value of mixerControl.input */
+  if (GMixerRow<=GMixerSection.Monitor) {
+    if (GMixerColumn!=mixerControl.output && GMixerColumn>=0) {
+      if (GMixerColumn<fdOut+2 || (ADATmode() && GMixerColumn<nLOut))
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mixerControl.outsel[GMixerColumn]), TRUE);
+    }
+    if (GMixerRow!=mixerControl.input)
+      gtk_widget_grab_focus(GTK_WIDGET(mixerControl.volume[GMixerRow]));
+  } else if (GMixerRow>=GMixerSection.VmixerFirst && GMixerRow<=GMixerSection.VmixerLast) {
+    if (GMixerColumn!=vmixerControl.output && GMixerColumn>=0) {
+      if (GMixerColumn<fdOut+2 || (ADATmode() && GMixerColumn<vmixerControl.outputs))
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vmixerControl.outsel[GMixerColumn]), TRUE);
+    }
+    if (GMixerRow!=vmixerControl.vchannel)
+      gtk_widget_grab_focus(GTK_WIDGET(vmixerControl.volume[GMixerRow-GMixerSection.VmixerFirst]));
+  }
+
+  if (event->button==1) {
+    mouseY=event->y;
+    mouseButton=1;
+  }
+  return(TRUE);
+}
+
+#else //REVERSE
+
+static gint Gmixer_button_press(GtkWidget *widget, GdkEventButton *event) {
+
+  GMixerRow=(int)event->y/YCELLTOT;
+  GMixerColumn=(int)event->x/XCELLTOT-1;
+
+  // See the note above
+  if (GMixerRow<=GMixerSection.Monitor) {
+    if (GMixerRow!=mixerControl.input)
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mixerControl.inpsel[GMixerRow]), TRUE);
+    if (GMixerColumn!=mixerControl.output && GMixerColumn>=0) {
+      if (GMixerColumn<fdOut+2 || (ADATmode() && GMixerColumn<nLOut))
+        gtk_widget_grab_focus(GTK_WIDGET(mixerControl.volume[GMixerColumn]));
+    }
+  } else if (GMixerRow>=GMixerSection.VmixerFirst && GMixerRow<=GMixerSection.VmixerLast) {
+    if (GMixerRow!=vmixerControl.vchannel)
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vmixerControl.vchsel[GMixerRow-GMixerSection.VmixerFirst]), TRUE);
+    if (GMixerColumn!=vmixerControl.output && GMixerColumn>=0) {
+      if (GMixerColumn<fdOut+2 || (ADATmode() && GMixerColumn<vmixerControl.outputs))
+        gtk_widget_grab_focus(GTK_WIDGET(vmixerControl.volume[GMixerColumn]));
+    }
+  }
+
+  if (event->button==1) {
+    mouseY=event->y;
+    mouseButton=1;
+  }
+  return(TRUE);
+}
+
+#endif //REVERSE
+
+
+
+static gint Gmixer_button_release(GtkWidget *widget, GdkEventButton *event) {
+
+  if (event->state & GDK_BUTTON1_MASK)
+    mouseButton=0;
+  return TRUE;
+}
+
+
+
+static gint Gmixer_motion_notify(GtkWidget *widget, GdkEventMotion *event) {
+  int x, y;
+  GdkModifierType state;
+  float val;
+
+  if (event->is_hint)
+    gdk_window_get_pointer(event->window, &x, &y, &state);
+  else {
+    x=event->x;
+    y=event->y;
+    state=event->state;
+  }
+
+  // Check if the button is still pressed because the release event can fall in another window, so we may miss it.
+  if (!(state & GDK_BUTTON1_MASK))
+    mouseButton=0;
+
+  if (GMixerRow<=GMixerSection.Monitor) {
+    if (mouseButton && Mixpixmap != NULL) {
+      val=INVERT(mixerControl.mixer[mixerControl.output][mixerControl.input].Gain);
+      val+=y-mouseY;
+      mouseY=y;
+      // Gtk already limits the range of "val"
+#ifdef REVERSE
+      gtk_adjustment_set_value(GTK_ADJUSTMENT(mixerControl.adj[mixerControl.input]), (gfloat)val);
+#else
+      gtk_adjustment_set_value(GTK_ADJUSTMENT(mixerControl.adj[mixerControl.output]), (gfloat)val);
+#endif
+    }
+  } else if (GMixerRow>=GMixerSection.VmixerFirst && GMixerRow<=GMixerSection.VmixerLast) {
+    if (mouseButton && Mixpixmap != NULL) {
+      val=INVERT(vmixerControl.mixer[vmixerControl.output][vmixerControl.vchannel].Gain);
+      val+=y-mouseY;
+      mouseY=y;
+      // Gtk already limits the range of "val"
+#ifdef REVERSE
+      gtk_adjustment_set_value(GTK_ADJUSTMENT(vmixerControl.adj[vmixerControl.vchannel]), (gfloat)val);
+#else
+      gtk_adjustment_set_value(GTK_ADJUSTMENT(vmixerControl.adj[vmixerControl.output]), (gfloat)val);
+#endif
+    }
+  } else if (GMixerRow==GMixerSection.LineOut) {
+    if (mouseButton && Mixpixmap != NULL) {
+      val=INVERT(lineoutControl.Gain[GMixerColumn]);
+      val+=y-mouseY;
+      mouseY=y;
+      // Gtk already limits the range of "val"
+      gtk_adjustment_set_value(GTK_ADJUSTMENT(lineoutControl.adj[GMixerColumn]), (gfloat)val);
+    }
+  }
+
+  return(TRUE);
+}
+
+
+
+void Monitor_volume_changed(GtkWidget *widget, gpointer cnl) {
+  int val, rval, ch;
+  int i, o;
+  char str[16];
+
+  UI_DEBUG(("Monitor_volume_changed()  %d %d\n",mixerControl.input,mixerControl.output));
+  val=rval=INVERT((int)GTK_ADJUSTMENT(widget)->value);
+
+  ch=(int)cnl;
+
+#ifdef REVERSE
+  i=ch;
+  o=mixerControl.output;
+#else
+  i=mixerControl.input;
+  o=ch;
+#endif
+
+  // Emulate the line-out volume if this card can't do it in hw.
+  if (!lineoutId) {
+    rval=Add_dB(val, lineoutControl.Gain[o]);
+    ClampOutputVolume(&rval);
+  }
+
+  SetMixerGain(&mixerControl.mixer[o][i], rval);       //@ we should restore the old adj position on error
+  mixerControl.mixer[o][i].Gain=val;
+
+  if (Gang) {
+    SetMixerGain(&mixerControl.mixer[o^1][i^1], rval);
+    mixerControl.mixer[o^1][i^1].Gain=val;
+  }
+
+  gtk_label_set_text(GTK_LABEL(mixerControl.label[ch]), strOutGain(str, val));
+}
+
+
+
+void Monitor_volume_clicked(GtkWidget *widget, gpointer ch) {
+
+#ifdef REVERSE
+  mixerControl.input=(int)ch;
+#else
+  mixerControl.output=(int)ch;
+#endif
+}
+
+
+
+void Gang_button_toggled(GtkWidget *widget, gpointer unused) {
+
+  Gang=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+}
+
+
+
+void PCM_volume_changed(GtkWidget *widget, gpointer ch) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  char str[16];
+  int err, channel, val, rval;
+  struct VolumeControl *vol;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+
+  if ((int)ch<ECHO_MAXAUDIOINPUTS) {
+    // Input
+    channel=(int)ch;
+    vol=&lineinControl;
+    rval=val=IN_INVERT((int)GTK_ADJUSTMENT(widget)->value);
+    sprintf(str, "%+4.1f", 0.5*val);
+  } else {
+    // Output
+    channel=(int)ch-ECHO_MAXAUDIOINPUTS;
+    vol=&pcmoutControl;
+    val=rval=INVERT((int)GTK_ADJUSTMENT(widget)->value);
+    pcmoutControl.Gain[channel]=val;
+    // Emulate the line-out volume if this card can't do it in hw.
+    if (!lineoutId) {
+      rval=Add_dB(val, lineoutControl.Gain[channel]);
+      ClampOutputVolume(&rval);
+    }
+    strOutGain(str, val);
+  }
+
+  gtk_label_set_text(GTK_LABEL(vol->label[channel]), str);
+
+  snd_ctl_elem_id_set_numid(id, vol->id);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  snd_ctl_elem_value_set_id(control, id);
+
+  if ((err=snd_ctl_elem_read(ctlhandle, control))<0) {
+    printf("Control %s element read error: %s\n", card, snd_strerror(err));
+    return;
+  }
+
+  snd_ctl_elem_value_set_integer(control, channel, rval);
+  if ((err=snd_ctl_elem_write(ctlhandle, control))<0) {
+    printf("Control %s element write error: %s\n", card, snd_strerror(err));
+  } else {
+    vol->Gain[channel]=val;
+  }
+  if (Gang)
+    gtk_adjustment_set_value(GTK_ADJUSTMENT(vol->adj[channel^1]), (gfloat)GTK_ADJUSTMENT(widget)->value);
+}
+
+
+
+// Changes the PCM volume according to the current Line-out volume for non-vmixer cards
+void UpdatePCMVolume(int outchannel) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  int err, val;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_numid(id, pcmoutId);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  snd_ctl_elem_value_set_id(control, id);
+
+  if ((err=snd_ctl_elem_read(ctlhandle, control))<0)
+    printf("Control %s element read error: %s\n", card, snd_strerror(err));
+
+  val=Add_dB(pcmoutControl.Gain[outchannel], lineoutControl.Gain[outchannel]);
+  ClampOutputVolume(&val);
+
+  snd_ctl_elem_value_set_integer(control, outchannel, val);
+  if ((err=snd_ctl_elem_write(ctlhandle, control))<0)
+    printf("Control %s element write error: %s\n", card, snd_strerror(err));
+}
+
+
+
+// Changes the monitor mixer volume according to the current Line-out volume for non-vmixer cards.
+void UpdateMixerVolume(int outchannel) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  int err, val, ch;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+
+  for (ch=0; ch<nIn; ch++) {
+    val=Add_dB(mixerControl.mixer[outchannel][ch].Gain, lineoutControl.Gain[outchannel]);
+    ClampOutputVolume(&val);
+    snd_ctl_elem_id_set_numid(id, mixerControl.mixer[outchannel][ch].id);
+    snd_ctl_elem_value_set_id(control, id);
+    snd_ctl_elem_value_set_integer(control, 0, val);
+    if ((err = snd_ctl_elem_write(ctlhandle, control)) < 0)
+      printf("Control %s element write error: %s\n", card, snd_strerror(err));
+  }
+}
+
+
+
+void LineOut_volume_changed(GtkWidget *widget, gpointer ch) {
+  char str[16];
+  int err, channel, val;
+
+  channel=(int)ch;
+
+  val=INVERT((int)GTK_ADJUSTMENT(widget)->value);
+  lineoutControl.Gain[channel]=val;
+
+  gtk_label_set_text(GTK_LABEL(lineoutControl.label[channel]), strOutGain(str, val));
+
+  if (lineoutId) {     // If this card has the line-out control, use it
+    snd_ctl_elem_id_t *id;
+    snd_ctl_elem_value_t *control;
+
+    snd_ctl_elem_id_alloca(&id);
+    snd_ctl_elem_value_alloca(&control);
+    snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+    snd_ctl_elem_id_set_numid(id, lineoutId);
+    snd_ctl_elem_value_set_id(control, id);
+
+    if ((err=snd_ctl_elem_read(ctlhandle, control))<0)
+      printf("Control %s element read error: %s\n", card, snd_strerror(err));
+     snd_ctl_elem_value_set_integer(control, channel, val);
+     if ((err=snd_ctl_elem_write(ctlhandle, control))<0)
+       printf("Control %s element write error: %s\n", card, snd_strerror(err));
+  } else {             // Otherwise we have to emulate it.
+    UpdatePCMVolume(channel);
+    UpdateMixerVolume(channel);
+  }
+
+  if (Gang)
+    gtk_adjustment_set_value(GTK_ADJUSTMENT(lineoutControl.adj[channel^1]), (gfloat)GTK_ADJUSTMENT(widget)->value);
+}
+
+
+
+void Vmixer_volume_changed(GtkWidget *widget, gpointer ch) {
+  char str[16];
+  int val, channel;
+  int o, v;
+
+  channel=(int)ch;
+
+#ifdef REVERSE
+  v=channel;
+  o=vmixerControl.output;
+#else
+  v=vmixerControl.vchannel;
+  o=channel;
+#endif
+
+  val=INVERT((int)GTK_ADJUSTMENT(widget)->value);
+
+  SetMixerGain(&vmixerControl.mixer[o][v], val);
+  vmixerControl.mixer[o][v].Gain=val;
+
+  if (Gang) {
+    SetMixerGain(&vmixerControl.mixer[o^1][v^1], val);
+    vmixerControl.mixer[o^1][v^1].Gain=val;
+  }
+  
+  gtk_label_set_text(GTK_LABEL(vmixerControl.label[channel]), strOutGain(str, val));
+}
+
+
+
+void Vmixer_volume_clicked(GtkWidget *widget, gpointer ch) {
+
+#ifdef REVERSE
+  vmixerControl.vchannel=(int)ch;
+  UI_DEBUG(("Vmixer_volume_clicked vch=%d\n",vmixerControl.vchannel));
+#else
+  vmixerControl.output=(int)ch;
+  UI_DEBUG(("Vmixer_volume_clicked out=%d\n",vmixerControl.output));
+#endif
+}
+
+
+
+#ifdef REVERSE
+
+void Vmixer_output_selector_clicked(GtkWidget *widget, gpointer ch) {
+  int c, val;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+  if (vmixerControl.output==(int)ch)
+    return;
+  vmixerControl.output=(int)ch;
+
+  UI_DEBUG(("Vmixer_selector_clicked out=%d\n",vmixerControl.output));
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  for (c=vmixerControl.vchannels-1; c>=0; c--) {
+    val=INVERT(vmixerControl.mixer[vmixerControl.output][c].Gain);
+    gtk_adjustment_set_value(GTK_ADJUSTMENT(vmixerControl.adj[c]), (gfloat)val);
+  }
+}
+
+#else
+
+void Vmixer_vchannel_selector_clicked(GtkWidget *widget, gpointer ch) {
+  int c, val;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+
+  if (vmixerControl.vchannel==(int)ch)
+    return;
+  vmixerControl.vchannel=(int)ch;
+
+  UI_DEBUG(("Vmixer_selector_clicked vch=%d\n",vmixerControl.vchannel));
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  for (c=vmixerControl.outputs-1; c>=0; c--) {
+    val=INVERT(vmixerControl.mixer[c][vmixerControl.vchannel].Gain);
+    gtk_adjustment_set_value(GTK_ADJUSTMENT(vmixerControl.adj[c]), (gfloat)val);
+  }
+}
+
+#endif
+
+
+
+void Nominal_level_toggled(GtkWidget *widget, gpointer ch) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  GtkWidget **button;
+  int err, val, channel;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+
+  val=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+  if ((int)ch<ECHO_MAXAUDIOINPUTS) {
+    channel=(int)ch;
+    button=p4dbuIn;
+    snd_ctl_elem_id_set_numid(id, p4InId);
+  } else {
+    channel=(int)ch-ECHO_MAXAUDIOINPUTS;
+    button=p4dbuOut;
+    snd_ctl_elem_id_set_numid(id, p4OutId);
+  }
+  snd_ctl_elem_value_set_id(control, id);
+  if ((err=snd_ctl_elem_read(ctlhandle, control))<0)
+    printf("Control %s element read error: %s\n", card, snd_strerror(err));
+  snd_ctl_elem_value_set_integer(control, channel, !val);      // FALSE is +4
+  if ((err=snd_ctl_elem_write(ctlhandle, control))<0)
+    printf("Control %s element write error: %s\n", card, snd_strerror(err));
+  if (Gang)
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button[channel^1]), val);
+}
+
+
+
+void PhantomPower_toggled(GtkWidget *widget, gpointer unused) {
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_value_t *control;
+  int err, val;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_value_alloca(&control);
+
+  val=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+  snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD);
+  snd_ctl_elem_id_set_numid(id, phantomId);
+  snd_ctl_elem_value_set_id(control, id);
+  snd_ctl_elem_value_set_integer(control, 0, val);
+  if ((err=snd_ctl_elem_write(ctlhandle, control))<0) {
+    printf("Control %s element write error: %s\n", card, snd_strerror(err));
+  }
+}
+
+
+
+void Digital_mode_activate(GtkWidget *widget, gpointer mode) {
+  int adat;
+
+  if (SetEnum(dmodeId, (int)mode)<0)
+    // Restore old value if it failed
+    gtk_option_menu_set_history(GTK_OPTION_MENU(dmodeOpt), dmodeVal);
+  else {
+    dmodeVal=(int)mode;
+    // When I change the digital mode, the clock source can change too
+    gtk_option_menu_set_history(GTK_OPTION_MENU(clocksrcOpt), clocksrcVal=GetEnum(clocksrcId));
+  }
+  adat=ADATmode();
+  SetSensitivity(adat);
+  if (adat)
+    GMixerSection.Monitor=nIn-1;
+  else
+    GMixerSection.Monitor=fdIn+2-1;    // S/PDIF has only 2 channels
+}
+
+
+
+void Clock_source_activate(GtkWidget *widget, gpointer clk) {
+
+  if (SetEnum(clocksrcId, (int)clk)<0)
+    gtk_option_menu_set_history(GTK_OPTION_MENU(clocksrcOpt), clocksrcVal);
+  else
+    clocksrcVal=(int)clk;
+}
+
+
+
+void SPDIF_mode_activate(GtkWidget *widget, gpointer mode) {
+
+  SetEnum(spdifmodeId, (int)mode);     // This one should never fail
+  spdifmodeVal=(int)mode;
+}
+
+
+
+// Create a new backing pixmap of the appropriate size
+static gint VU_configure_event(GtkWidget *widget, GdkEventConfigure *event) {
+
+  if (VUpixmap)
+    gdk_pixmap_unref(VUpixmap);
+  VUpixmap=gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1);
+  gdk_draw_rectangle(VUpixmap, widget->style->black_gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height);
+  return(TRUE);
+}
+
+
+
+// Redraw the screen from the backing pixmap
+static gint VU_expose(GtkWidget *widget, GdkEventExpose *event) {
+
+  if (VUpixmap)
+    gdk_draw_pixmap(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], VUpixmap,
+                    event->area.x, event->area.y,
+                    event->area.x, event->area.y,
+                    event->area.width, event->area.height);
+  return(FALSE);
+}
+
+
+
+// Create a new backing pixmap of the appropriate size
+static gint Gmixer_configure_event(GtkWidget *widget, GdkEventConfigure *event) {
+  
+  if (Mixpixmap)
+    gdk_pixmap_unref(Mixpixmap);
+  Mixpixmap=gdk_pixmap_new(widget->window, widget->allocation.width, widget->allocation.height, -1);
+  gdk_draw_rectangle(Mixpixmap, widget->style->black_gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height);
+  return(TRUE);
+}
+
+
+
+// Redraw the screen from the backing pixmap
+static gint Gmixer_expose(GtkWidget *widget, GdkEventExpose *event) {
+
+  if (Mixpixmap)
+    gdk_draw_pixmap(widget->window, widget->style->fg_gc[GTK_WIDGET_STATE(widget)], Mixpixmap,
+                    event->area.x, event->area.y,
+                    event->area.x, event->area.y,
+                    event->area.width, event->area.height);
+  return(FALSE);
+}
+
+
+
+gint CloseWindow(GtkWidget *widget, GdkEvent *event, gpointer geom) {
+  struct geometry *g=geom;
+
+  gdk_window_get_root_origin(widget->window, &g->x, &g->y);
+  gdk_window_get_size(widget->window, &g->w, &g->h);
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->toggler), FALSE);  // This hides the window
+  //gtk_widget_set_uposition(widget, g->x, g->y);
+  return(TRUE);                // Do not destroy it
+}
+
+
+
+gint Mainwindow_delete(GtkWidget *widget, GdkEvent *event, gpointer geom) {
+  struct geometry *g=geom;
+
+  if (VUwindow) {
+    gdk_window_get_root_origin(VUwindow->window, &VUw_geom.x, &VUw_geom.y);
+    gtk_widget_destroy(VUwindow);
+  }
+  if (GMwindow) {
+    gdk_window_get_root_origin(GMwindow->window, &GMw_geom.x, &GMw_geom.y);
+    gtk_widget_destroy(GMwindow);
+  }
+  gdk_window_get_root_origin(Mainwindow->window, &g->x, &g->y);
+  gtk_main_quit();
+  return(FALSE);
+}
+
+
+
+gint VUwindow_delete(GtkWidget *widget, GdkEvent *event, gpointer geom) {
+  struct geometry *g=geom;
+
+  gdk_window_get_root_origin(widget->window, &g->x, &g->y);
+  g->st=0;
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->toggler), FALSE);
+  return(FALSE);
+}
+
+
+
+gint VUwindow_destroy(GtkWidget *widget, gpointer unused) {
+
+  SetVUmeters(0);
+  gtk_timeout_remove(VUtimer);
+  //@@@del gc and fnt
+  VUwindow=0;
+  return(TRUE);
+}
+
+
+
+gint GMwindow_delete(GtkWidget *widget, GdkEvent *event, gpointer geom) {
+  struct geometry *g=geom;
+
+  gdk_window_get_root_origin(widget->window, &g->x, &g->y);
+  g->st=0;
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->toggler), FALSE);
+  return(FALSE);
+}
+
+
+
+gint GMwindow_destroy(GtkWidget *widget, gpointer unused) {
+
+  SetVUmeters(0);
+  gtk_timeout_remove(Mixtimer);
+  //@@@del gc and fnt
+  GMwindow=0;
+  return(TRUE);
+}
+
+
+
+void VUmeters_button_click(GtkWidget *widget, gpointer unused) {
+  char str[64];
+
+  if (VUwindow && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
+    VUw_geom.st=0;
+    gtk_widget_destroy(VUwindow);
+  } else if (!VUwindow && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
+    // Create VU-meter window
+    VUwidth=VU_XGRAF+(VU_BARWIDTH+VU_BARSEP)*(nIn+nLOut+1)+VU_BARSEP;
+    VUheight=160;
+    SetVUmeters(1);
+    VUwindow=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    sprintf(str, "%s VU-meters", cardId);
+    gtk_window_set_title (GTK_WINDOW (VUwindow), str);
+    gtk_window_set_wmclass(GTK_WINDOW(VUwindow), "vumeters", "Emixer");
+    gtk_signal_connect(GTK_OBJECT(VUwindow), "destroy", GTK_SIGNAL_FUNC(VUwindow_destroy), NULL);
+    gtk_signal_connect(GTK_OBJECT(VUwindow), "delete_event", GTK_SIGNAL_FUNC(VUwindow_delete), (gpointer)&VUw_geom);
+    gtk_window_set_policy(GTK_WINDOW(VUwindow), FALSE, FALSE, TRUE);
+    if (VUw_geom.st!=NOPOS)
+      gtk_widget_set_uposition(VUwindow, VUw_geom.x, VUw_geom.y);
+    gtk_widget_show(VUwindow);
+
+    VUdarea=gtk_drawing_area_new();
+    gtk_widget_set_events(VUdarea, GDK_EXPOSURE_MASK);
+    gtk_drawing_area_size(GTK_DRAWING_AREA(VUdarea), VUwidth, VUheight);
+    gtk_container_add(GTK_CONTAINER(VUwindow), VUdarea);
+
+    gtk_widget_show(VUdarea);
+    gtk_signal_connect(GTK_OBJECT(VUdarea), "expose_event", (GtkSignalFunc)VU_expose, NULL);
+    gtk_signal_connect(GTK_OBJECT(VUdarea), "configure_event", (GtkSignalFunc)VU_configure_event, NULL);
+    VUtimer=gtk_timeout_add(30, DrawVUmeters, 0);      // The hw updates the meters about 30 times/s
+    gdk_window_clear_area(VUdarea->window, 0, 0, VUwidth, VUheight);
+    VUw_geom.st=1;
+  }
+}
+
+
+
+void GMixer_button_click(GtkWidget *widget, gpointer unused) {
+  char str[64];
+
+  if (GMwindow && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
+    GMw_geom.st=0;
+    gtk_widget_destroy(GMwindow);
+  } else if (!GMwindow && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
+    // Create graphic mixer window
+    Mixwidth=XCELLTOT*(nLOut+1);
+    Mixheight=YCELLTOT*(GMixerSection.LineOut+1)+9;
+    SetVUmeters(1);
+    GMwindow=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    sprintf(str, "%s Mixer", cardId);
+    gtk_window_set_title (GTK_WINDOW (GMwindow), str);
+    gtk_window_set_wmclass(GTK_WINDOW(GMwindow), "gridmixer", "Emixer");
+    gtk_signal_connect(GTK_OBJECT(GMwindow), "destroy", GTK_SIGNAL_FUNC(GMwindow_destroy), NULL);
+    gtk_signal_connect(GTK_OBJECT(GMwindow), "delete_event", GTK_SIGNAL_FUNC(GMwindow_delete), (gpointer)&GMw_geom);
+    gtk_window_set_policy(GTK_WINDOW(GMwindow), FALSE, FALSE, TRUE);
+    if (GMw_geom.st!=NOPOS)
+      gtk_widget_set_uposition(GMwindow, GMw_geom.x, GMw_geom.y);
+    gtk_widget_show(GMwindow);
+
+    Mixdarea=gtk_drawing_area_new();
+    gtk_widget_set_events(Mixdarea, GDK_EXPOSURE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
+    gtk_drawing_area_size(GTK_DRAWING_AREA(Mixdarea), Mixwidth, Mixheight);
+    gtk_container_add(GTK_CONTAINER(GMwindow), Mixdarea);
+
+    gtk_widget_show(Mixdarea);
+    gtk_signal_connect(GTK_OBJECT(Mixdarea), "expose_event", (GtkSignalFunc)Gmixer_expose, NULL);
+    gtk_signal_connect(GTK_OBJECT(Mixdarea), "configure_event", (GtkSignalFunc)Gmixer_configure_event, NULL);
+    gtk_signal_connect(GTK_OBJECT(Mixdarea), "motion_notify_event", (GtkSignalFunc)Gmixer_motion_notify, NULL);
+    gtk_signal_connect(GTK_OBJECT(Mixdarea), "button_press_event", (GtkSignalFunc)Gmixer_button_press, NULL);
+    gtk_signal_connect(GTK_OBJECT(Mixdarea), "button_release_event", (GtkSignalFunc)Gmixer_button_release, NULL);
+    Mixtimer=gtk_timeout_add(30, DrawMixer, 0);                // The hw updates the meters about 30 times/s
+    gdk_window_clear_area(Mixdarea->window, 0, 0, Mixwidth, Mixheight);
+    GMw_geom.st=1;
+  }
+}
+
+
+
+void ToggleWindow(GtkWidget *widget, gpointer window) {
+
+  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
+    gtk_widget_show(GTK_WIDGET(window));
+  else
+    gtk_widget_hide(GTK_WIDGET(window));
+}
+
+
+
+/*
+int LockControls(snd_ctl_elem_id_t *id, int first_id, int num) {
+  int i, err;
+
+  for (i=0; i<num; i++) {
+    snd_ctl_elem_id_set_numid(id, first_id+i);
+    err=snd_ctl_elem_lock(ctlhandle, id);
+    if (err)
+      return(err);
+  }
+  return(0);
+}
+
+int LockControls(int first_id, int num) {
+  int i, err;
+  snd_ctl_elem_id_t *id;
+
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_id_set_interface(id, first_id==clocksrcId ? SND_CTL_ELEM_IFACE_PCM : SND_CTL_ELEM_IFACE_CARD);
+
+  for (i=0; i<num; i++) {
+    snd_ctl_elem_id_set_numid(id, first_id+i);
+    err=snd_ctl_elem_lock(ctlhandle, id);
+    if (err)
+      return(err);
+  }
+  return(0);
+}
+*/
+
+
+
+// Scan all controls and store their ID's for later use.
+int OpenControls(const char *card, const char *cardname) {
+  int err, i, o;
+  int numid, count, items, item;
+  snd_hctl_t *handle;
+  snd_hctl_elem_t *elem;
+  snd_ctl_elem_id_t *id;
+  snd_ctl_elem_info_t *info;
+
+  pcmoutId=lineoutId=vmixerId=p4InId=p4OutId=dmodeId=clocksrcId=spdifmodeId=vuswitchId=vumetersId=mixerId=0;
+  memset(&vmixerControl, 0, sizeof(vmixerControl));
+  memset(&mixerControl, 0, sizeof(mixerControl));
+  memset(&lineoutControl, 0, sizeof(struct VolumeControl));
+  memset(&lineinControl, 0, sizeof(struct VolumeControl));
+  memset(&pcmoutControl, 0, sizeof(struct VolumeControl));
+  ndmodes=nclocksrc=nspdifmodes=0;
+  snd_ctl_elem_id_alloca(&id);
+  snd_ctl_elem_info_alloca(&info);
+
+  if ((err=snd_hctl_open(&handle, card, 0))<0) {
+    printf("Control %s open error: %s", card, snd_strerror(err));
+    return err;
+  }
+  if ((err=snd_hctl_load(handle))<0) {
+    printf("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) {
+      printf("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err));
+      return err;
+    }
+    if (snd_ctl_elem_info_is_inactive(info))
+      continue;
+    snd_hctl_elem_get_id(elem, id);
+    numid=snd_ctl_elem_id_get_numid(id);
+    count=snd_ctl_elem_info_get_count(info);
+    if (!strcmp("Monitor Mixer Volume", snd_ctl_elem_id_get_name(id))) {
+      if (!mixerId) {
+        mixerId=numid;
+        CTLID_DEBUG(("First Mixer id=%d\n", mixerId));
+        mixerControl.outputs=snd_ctl_elem_info_get_dimension(info, 0);
+        mixerControl.inputs=snd_ctl_elem_info_get_dimension(info, 1);
+      }
+    } else if (!strcmp("VMixer Volume", snd_ctl_elem_id_get_name(id))) {
+      if (!vmixerId) {
+        vmixerId=vmixerControl.id=numid;
+        CTLID_DEBUG(("First Vmixer id=%d\n", vmixerId));
+        vmixerControl.outputs=snd_ctl_elem_info_get_dimension(info, 0);
+        vmixerControl.vchannels=snd_ctl_elem_info_get_dimension(info, 1);
+      }
+    } else if (!strcmp("PCM Playback Volume", snd_ctl_elem_id_get_name(id))) {
+      pcmoutId=pcmoutControl.id=numid;
+//printf("*** %d\n", LockControls(id, numid, 1));
+//#warning ************* usare id giร  pronto ?  controllo errori, eccc.
+      CTLID_DEBUG(("PCM Playback Volume id=%d [%d]\n", pcmoutId, count));
+    } else if (!strcmp("Line Playback Volume", snd_ctl_elem_id_get_name(id))) {
+      lineoutId=numid;
+      CTLID_DEBUG(("Line Volume id=%d\n", lineoutId));
+    } else if (!strcmp("Line Capture Volume", snd_ctl_elem_id_get_name(id))) {
+      lineinId=lineinControl.id=numid;
+      CTLID_DEBUG(("Capture Volume id=%d [%d]\n", lineinId, count));
+    } else if (!strcmp("Line Playback Switch (-10dBV)", snd_ctl_elem_id_get_name(id))) {
+      p4OutId=numid;
+      CTLID_DEBUG(("Playback nominal id=%d [%d]\n", p4OutId, count));
+    } else if (!strcmp("Line Capture Switch (-10dBV)", snd_ctl_elem_id_get_name(id))) {
+      p4InId=numid;
+      CTLID_DEBUG(("Capture nominal id=%d [%d]\n", p4InId, count));
+    } else if (!strcmp("Digital mode Switch", snd_ctl_elem_id_get_name(id))) {
+      dmodeId=numid;
+      items=snd_ctl_elem_info_get_items(info);
+      ndmodes=items;
+      for (item=0; item<items; item++) {
+        snd_ctl_elem_info_set_item(info, item);
+        if ((err=snd_hctl_elem_info(elem, info)) < 0) {
+          printf("Control %s element info error: %s\n", card, snd_strerror(err));
+          exit(err);
+        }
+        strncpy(dmodeName[item], snd_ctl_elem_info_get_item_name(info), 63);
+        dmodeName[item][63]=0;
+        CTLID_DEBUG(("Digital Mode id=%d item #%u '%s'\n", numid, item, snd_ctl_elem_info_get_item_name(info)));
+      }
+    } else if (!strcmp("Sample Clock Source", snd_ctl_elem_id_get_name(id))) {
+      clocksrcId=numid;
+      items=snd_ctl_elem_info_get_items(info);
+      nclocksrc=items;
+      for (item=0; item<items; item++) {
+        snd_ctl_elem_info_set_item(info, item);
+        if ((err=snd_hctl_elem_info(elem, info))<0) {
+          printf("Control %s element info error: %s\n", card, snd_strerror(err));
+          exit(err);
+        }
+        strncpy(clocksrcName[item], snd_ctl_elem_info_get_item_name(info), 63);
+        clocksrcName[item][63]=0;
+        CTLID_DEBUG(("Clock source id=%d item #%u '%s'\n", numid, item, snd_ctl_elem_info_get_item_name(info)));
+      }
+    } else if (!strcmp("S/PDIF mode Switch", snd_ctl_elem_id_get_name(id))) {
+      spdifmodeId=numid;
+      items=snd_ctl_elem_info_get_items(info);
+      nspdifmodes=items;
+      for (item=0; item<items; item++) {
+        snd_ctl_elem_info_set_item(info, item);
+        if ((err=snd_hctl_elem_info(elem, info)) < 0) {
+          printf("Control %s element info error: %s\n", card, snd_strerror(err));
+        }
+        strncpy(spdifmodeName[item], snd_ctl_elem_info_get_item_name(info), 63);
+        spdifmodeName[item][63]=0;
+        CTLID_DEBUG(("S/PDIF Mode id=%d item #%u '%s'\n", numid, item, snd_ctl_elem_info_get_item_name(info)));
+      }
+    } else if (!strcmp("Phantom power Switch", snd_ctl_elem_id_get_name(id))) {
+      phantomId=numid;
+      CTLID_DEBUG(("Phantom power Switch id=%d\n", numid));
+    } else if (!strcmp("VU-meters Switch", snd_ctl_elem_id_get_name(id))) {
+      vuswitchId=numid;
+      CTLID_DEBUG(("VU-meter switch id=%d\n", numid));
+    } else if (!strcmp("VU-meters", snd_ctl_elem_id_get_name(id))) {
+      vumetersId=numid;
+      metersStreams=snd_ctl_elem_info_get_dimension(info, 0);  // 2 or 3: output, input and (vmixer cards only) pcm
+      metersNumber=snd_ctl_elem_info_get_dimension(info, 1);   // Number of channels
+      metersTypes=snd_ctl_elem_info_get_dimension(info, 2);    // 2: level and peak
+      CTLID_DEBUG(("VU-meters id=%d\n", numid));
+    } else if (!strcmp("Channels info", snd_ctl_elem_id_get_name(id))) {
+      channelsId=numid;
+      CTLID_DEBUG(("Channels info id=%d\n", numid));
+    }
+  }
+
+  GetChannels();
+  CTLID_DEBUG(("Input channels = %d (analog=%d digital=%d)\n", nIn, fdIn, nIn-fdIn));
+  CTLID_DEBUG(("Output channels = %d (analog=%d digital=%d)\n", nLOut, fdOut, nLOut-fdOut));
+  CTLID_DEBUG(("PCM channels out = %d\n", nPOut));
+
+#ifndef REAL
+vmixerId=1000;
+vmixerControl.vchannels=12;
+vmixerControl.outputs=mixerControl.outputs=10;
+metersStreams=3;
+metersNumber=16;
+metersTypes=2;
+nPOut=12;
+nLOut=10;
+fdOut=2;
+nIn=10;
+fdIn=2;
+printf("nIn=%d fdIn=%d nLOut=%d nPOut=%d fdOut=%d\n", nIn,fdIn,nLOut,nPOut, fdOut);
+#endif
+
+  if (mixerId && (mixerControl.inputs!=nIn || mixerControl.outputs!=nLOut)) {
+    printf("** Error - Mixer/channels mismatch !!  nIn=%d mnIn=%d    nLOut=%d mnLOut=%d\n", nIn, mixerControl.inputs, nLOut, mixerControl.outputs);
+    return(1);
+  }
+  if (lineoutId && !vmixerId)
+    printf("** Warning - Vmixer cards without LineOut volume control are not supported !\n");
+
+  if (vmixerId) {
+    if (vmixerControl.vchannels!=nPOut || vmixerControl.outputs!=nLOut) {
+      printf("** Error - vmixer/channels mismatch:  vmp=%d npo=%d    vmo=%d nlo=%d !!\n", vmixerControl.vchannels, nPOut, vmixerControl.outputs, nLOut);
+      return(1);
+    }
+  }
+
+  //@ Assumes all mixer and vmixer controls are contiguous
+  if (mixerId)
+    for (o=0, numid=mixerId; o<nLOut; o++) {
+      for (i=0; i<nIn; i++) {
+        mixerControl.mixer[o][i].id=numid++;
+      }
+    }
+
+  if (vmixerId)
+    for (o=0, numid=vmixerId; o<vmixerControl.outputs; o++) {
+      for (i=0; i<vmixerControl.vchannels; i++) {
+        vmixerControl.mixer[o][i].id=numid++;
+      }
+    }
+
+  snd_hctl_close(handle);
+  return(0);
+}
+
+
+
+int main(int argc, char *argv[]) {
+  gchar str[256];
+  GtkWidget *hbox, *vbox;
+  GtkWidget *mainbox;
+  GtkWidget *vbsel, *frame, *button;
+  GtkWidget *label, *menu, *menuitem;
+  GSList *bgroup;
+  int err, i, o, n, cardnum;
+  char hwname[8], cardname[32], load, save;
+  snd_ctl_card_info_t *hw_info;
+
+  load=save=1;
+
+  // Scans all installed cards
+  snd_ctl_card_info_alloca(&hw_info);
+  cardnum=-1;
+  ctlhandle=0;
+
+  if (argc>1)
+    cardnum=atoi(argv[1])-1;
+
+  while (snd_card_next(&cardnum)>=0 && cardnum>=0) {
+    sprintf(hwname, "hw:%d", cardnum);
+    if ((err=snd_ctl_open(&ctlhandle, hwname, 0))<0) {
+      printf("snd_ctl_open(%s) Error: %s\n", hwname, snd_strerror(err));
+      continue;
+    }
+    if ((err=snd_ctl_card_info(ctlhandle, hw_info))>=0) {
+      if (!strncmp(snd_ctl_card_info_get_driver(hw_info), "Echoaudio", 9)) {
+        strncpy(card, hwname, 7);
+        hwname[7]=0;
+        strncpy(cardname, snd_ctl_card_info_get_name(hw_info), 31);
+        cardname[31]=0;
+        strncpy(cardId, snd_ctl_card_info_get_name(hw_info), 15);
+        cardId[15]=0;
+        CTLID_DEBUG(("Card found: %s  (%s)\n", snd_ctl_card_info_get_longname(hw_info), hwname));
+/*printf("card       = %d\n", snd_ctl_card_info_get_card(hw_info));
+printf("id         = %s\n", snd_ctl_card_info_get_id(hw_info));
+printf("driver     = %s\n", snd_ctl_card_info_get_driver(hw_info));
+printf("name       = %s\n", snd_ctl_card_info_get_name(hw_info));
+printf("longname   = %s\n", snd_ctl_card_info_get_longname(hw_info));
+printf("mixername  = %s\n", snd_ctl_card_info_get_mixername(hw_info));
+printf("components = %s\n", snd_ctl_card_info_get_components(hw_info));*/
+        break;
+      }
+    } else {
+      printf("snd_ctl_card_info(%s) Error: %s\n", hwname, snd_strerror(err));
+    }
+    snd_ctl_close(ctlhandle);
+    ctlhandle=0;
+  }
+
+  if (!ctlhandle) {
+    printf("No Echoaudio cards found, sorry.\n");
+    return(0);
+  }
+
+  // Reads available controls
+  if (OpenControls(card, cardname))
+    exit(1);
+
+  mouseButton=0;
+  Gang=0;              // Set the gang button off, because has annoying side effects during initialization
+  Mainw_geom.st=NOPOS;
+  PVw_geom.st=NOPOS;
+  LVw_geom.st=NOPOS;
+  VUw_geom.st=NOPOS;
+  Mixerw_geom.st=NOPOS;
+  Vmixerw_geom.st=NOPOS;
+  VUwindow=GMwindow=0;
+  GMixerSection.Monitor=fdIn+2-1;      // The correct value is set by Digital_mode_activate()
+  GMixerSection.VmixerFirst=nIn;
+  GMixerSection.VmixerLast=nIn+vmixerControl.vchannels-1;
+  GMixerSection.LineOut=GMixerSection.VmixerLast+1;
+  // Read current mixer setting.
+  if (mixerId)
+    ReadMixer(&mixerControl);
+  if (vmixerId)
+    ReadMixer(&vmixerControl);
+  if (pcmoutId)
+    ReadControl(pcmoutControl.Gain, nPOut, pcmoutControl.id);
+  if (lineinId)
+    ReadControl(lineinControl.Gain, nIn, lineinControl.id);
+  if (lineoutId)
+    ReadControl(lineoutControl.Gain, nLOut, lineoutId);
+
+  //@@ check the values
+  if (load) {
+    FILE *f;
+    snprintf(str, 255, "%s/.Emixer_%s", getenv("HOME"), cardId);
+    str[255]=0;
+    if ((f=fopen(str, "r"))) {
+      str[255]=0;
+      while (fgets(str, 255, f)) {
+        if (!strncmp("LineOut ", str, 8)) {
+          sscanf(str+8, "%d %d", &o, &n);
+          if (o>=0 && o<nLOut)
+            lineoutControl.Gain[o]=n;
+        } else if (!strncmp("LineIn ", str, 7)) {
+          sscanf(str+7, "%d %d", &i, &n);
+          if (i>=0 && i<nIn)
+            lineinControl.Gain[i]=n;
+        } else if (!strncmp("PcmOut ", str, 7)) {
+          sscanf(str+7, "%d %d", &o, &n);
+          if (o>=0 && o<nPOut)
+            pcmoutControl.Gain[o]=n;
+        } else if (!strncmp("Mixer ", str, 6)) {
+          sscanf(str+6, "%d %d %d", &o, &i, &n);
+          if (o>=0 && o<nLOut && i>=0 && i<nIn)
+            mixerControl.mixer[o][i].Gain=n;
+        } else if (!strncmp("Vmixer ", str, 7)) {
+          sscanf(str+7, "%d %d %d", &o, &i, &n);
+          if (o>=0 && o<nLOut && i>=0 && i<nPOut)
+            vmixerControl.mixer[o][i].Gain=n;
+        } else if (!strncmp("MainWindow ", str, 11)) {
+          sscanf(str+11, "%d %d %d %d", &Mainw_geom.x, &Mainw_geom.y, &Mainw_geom.w, &Mainw_geom.h);
+        } else if (!strncmp("VUmetersWindow ", str, 15)) {
+          sscanf(str+15, "%d %d %d", &VUw_geom.x, &VUw_geom.y, &VUw_geom.st);
+        } else if (!strncmp("GfxMixerWindow ", str, 15)) {
+          sscanf(str+15, "%d %d %d", &GMw_geom.x, &GMw_geom.y, &GMw_geom.st);
+        } else if (!strncmp("PcmVolumeWindow ", str, 16)) {
+          sscanf(str+16, "%d %d %d %d %d", &PVw_geom.x, &PVw_geom.y, &PVw_geom.w, &PVw_geom.h, &PVw_geom.st);
+        } else if (!strncmp("LineVolumeWindow ", str, 17)) {
+          sscanf(str+17, "%d %d %d %d %d", &LVw_geom.x, &LVw_geom.y, &LVw_geom.w, &LVw_geom.h, &LVw_geom.st);
+        } else if (!strncmp("MixerWindow ", str, 12)) {
+          sscanf(str+12, "%d %d %d %d %d", &Mixerw_geom.x, &Mixerw_geom.y, &Mixerw_geom.w, &Mixerw_geom.h, &Mixerw_geom.st);
+        } else if (!strncmp("VmixerWindow ", str, 13)) {
+          sscanf(str+13, "%d %d %d %d %d", &Vmixerw_geom.x, &Vmixerw_geom.y, &Vmixerw_geom.w, &Vmixerw_geom.h, &Vmixerw_geom.st);
+        } else if (!strncmp("MiscControlsWindow ", str, 19)) {
+          sscanf(str+19, "%d %d %d %d %d", &Miscw_geom.x, &Miscw_geom.y, &Miscw_geom.w, &Miscw_geom.h, &Miscw_geom.st);
+        }
+      }
+    }
+  }
+  gtk_init(&argc, &argv);
+  fnt=gdk_font_load("-misc-fixed-medium-r-*-*-10-*-*-*-*-*-*-*");
+  if (!fnt) {
+    printf("Cannot find the font\n");
+    exit(1);
+  }
+
+  /* Now assemble the control windows */
+
+
+  /* ********** Misc controls window ********** */
+
+  Miscwindow=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  sprintf(str, "%s Misc controls", cardId);
+  gtk_window_set_title(GTK_WINDOW(Miscwindow), str);
+  gtk_window_set_wmclass(GTK_WINDOW(Miscwindow), "misc", "Emixer");
+  gtk_signal_connect(GTK_OBJECT(Miscwindow), "delete_event", GTK_SIGNAL_FUNC(CloseWindow), (gpointer)&Miscw_geom);
+  gtk_container_set_border_width(GTK_CONTAINER(Miscwindow), BORDER);
+  if (Miscw_geom.st!=NOPOS) {
+    gtk_widget_set_uposition(Miscwindow, Miscw_geom.x, Miscw_geom.y);
+    gtk_window_set_default_size(GTK_WINDOW(Miscwindow), Miscw_geom.w, Miscw_geom.h);
+  }
+
+  mainbox=gtk_vbox_new(FALSE, SPACING);
+  gtk_widget_show(mainbox);
+  gtk_container_add(GTK_CONTAINER(Miscwindow), mainbox);
+
+
+  if (p4InId) {
+    // Consumer/professional analog input switches
+    frame=gtk_frame_new("Input +4dBu");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0);
+    hbox=gtk_hbox_new(FALSE, 0);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<fdIn; i++) {
+      sprintf(str, "%d", i);
+      p4dbuIn[i]=gtk_toggle_button_new_with_label(str);
+      gtk_box_pack_start(GTK_BOX(hbox), p4dbuIn[i], TRUE, FALSE, 1);
+      gtk_widget_show(p4dbuIn[i]);
+      gtk_signal_connect(GTK_OBJECT(p4dbuIn[i]), "toggled", GTK_SIGNAL_FUNC(Nominal_level_toggled), (gpointer)i);
+    }
+    InitNominalLevelGUI(p4InId);
+  }
+
+
+  if (p4OutId) {
+    // Consumer/professional analog output switches
+    frame=gtk_frame_new("Output +4dBu");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0);
+    hbox=gtk_hbox_new(FALSE, 0);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<fdOut; i++) {
+      sprintf(str, "%d", i);
+      p4dbuOut[i]=gtk_toggle_button_new_with_label(str);
+      gtk_box_pack_start(GTK_BOX(hbox), p4dbuOut[i], TRUE, FALSE, 1);
+      gtk_widget_show(p4dbuOut[i]);
+      gtk_signal_connect(GTK_OBJECT(p4dbuOut[i]), "toggled", GTK_SIGNAL_FUNC(Nominal_level_toggled), (gpointer)(i+ECHO_MAXAUDIOINPUTS));
+    }
+    InitNominalLevelGUI(p4OutId);
+  }
+
+
+  if (dmodeId && ndmodes>1) {
+    // Digital mode switch
+    frame=gtk_frame_new("Digital mode");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0);
+    hbox=gtk_hbox_new(FALSE, 0);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    dmodeOpt=gtk_option_menu_new();
+    gtk_widget_show(dmodeOpt);
+    menu=gtk_menu_new();
+    gtk_widget_show(menu);
+    for (i=0; i<ndmodes; i++) {
+      menuitem=gtk_menu_item_new_with_label(dmodeName[i]);
+      gtk_widget_show(menuitem);
+      gtk_signal_connect(GTK_OBJECT(menuitem), "activate", Digital_mode_activate, (gpointer)i);
+      gtk_menu_append(GTK_MENU(menu), menuitem);
+    }
+    gtk_option_menu_set_menu(GTK_OPTION_MENU(dmodeOpt), menu);
+    gtk_box_pack_start(GTK_BOX(hbox), dmodeOpt, TRUE, TRUE, 0);
+    gtk_option_menu_set_history(GTK_OPTION_MENU(dmodeOpt), dmodeVal=GetEnum(dmodeId));
+  }
+
+
+  if (clocksrcId && nclocksrc>1) {
+    // Clock source switch
+    frame=gtk_frame_new("Clock source");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0);
+    hbox=gtk_hbox_new(FALSE, 0);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    clocksrcOpt=gtk_option_menu_new();
+    gtk_widget_show(clocksrcOpt);
+    menu=gtk_menu_new();
+    gtk_widget_show(menu);
+    for (i=0; i<nclocksrc; i++) {
+      clocksrc_menuitem[i]=gtk_menu_item_new_with_label(clocksrcName[i]);
+      gtk_widget_show(clocksrc_menuitem[i]);
+      gtk_widget_set_sensitive(clocksrc_menuitem[i], FALSE);
+      gtk_signal_connect(GTK_OBJECT(clocksrc_menuitem[i]), "activate", Clock_source_activate, (gpointer)i);
+      gtk_menu_append(GTK_MENU(menu), clocksrc_menuitem[i]);
+    }
+    gtk_option_menu_set_menu(GTK_OPTION_MENU(clocksrcOpt), menu);
+    gtk_box_pack_start(GTK_BOX(hbox), clocksrcOpt, TRUE, TRUE, 0);
+    gtk_option_menu_set_history(GTK_OPTION_MENU(clocksrcOpt), clocksrcVal=GetEnum(clocksrcId));
+    clocksrctimer=gtk_timeout_add(2000, CheckInputs, 0);
+  }
+
+
+  if (spdifmodeId && nspdifmodes>1) {
+    // S/PDIF mode switch
+    frame=gtk_frame_new("S/PDIF mode");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0);
+    hbox=gtk_hbox_new(FALSE, 0);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    spdifmodeOpt=gtk_option_menu_new();
+    gtk_widget_show(spdifmodeOpt);
+    menu=gtk_menu_new();
+    gtk_widget_show(menu);
+    for (i=0; i<nspdifmodes; i++) {
+      menuitem=gtk_menu_item_new_with_label(spdifmodeName[i]);
+      gtk_widget_show(menuitem);
+      gtk_signal_connect(GTK_OBJECT(menuitem), "activate", SPDIF_mode_activate, (gpointer)i);
+      gtk_menu_append(GTK_MENU(menu), menuitem);
+    }
+    gtk_option_menu_set_menu(GTK_OPTION_MENU(spdifmodeOpt), menu);
+    gtk_box_pack_start(GTK_BOX(hbox), spdifmodeOpt, TRUE, TRUE, 0);
+    gtk_option_menu_set_history(GTK_OPTION_MENU(spdifmodeOpt), spdifmodeVal=GetEnum(spdifmodeId));
+  }
+
+
+  if (phantomId) {
+    // Phantom power switch
+    frame=gtk_frame_new("Phantom power");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, FALSE, 0);
+    hbox=gtk_hbox_new(FALSE, 0);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    phantomToggle=gtk_toggle_button_new_with_label("On");
+    gtk_widget_show(phantomToggle);
+    gtk_box_pack_start(GTK_BOX(hbox), phantomToggle, TRUE, FALSE, 0);
+    gtk_signal_connect(GTK_OBJECT(phantomToggle), "toggled", PhantomPower_toggled, (gpointer)0);
+    InitPhantomPowerGUI(phantomId);
+  }
+
+
+/* ********** PCM volume window ********** */
+
+  pcmoutControl.window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  sprintf(str, "%s PCM volume", cardId);
+  gtk_window_set_title(GTK_WINDOW (pcmoutControl.window), str);
+  gtk_window_set_wmclass(GTK_WINDOW(pcmoutControl.window), "pcm", "Emixer");
+  gtk_signal_connect(GTK_OBJECT(pcmoutControl.window), "delete_event", GTK_SIGNAL_FUNC(CloseWindow), (gpointer)&PVw_geom);
+  gtk_container_set_border_width(GTK_CONTAINER(pcmoutControl.window), BORDER);
+  if (PVw_geom.st!=NOPOS) {
+    gtk_widget_set_uposition(pcmoutControl.window, PVw_geom.x, PVw_geom.y);
+    gtk_window_set_default_size(GTK_WINDOW(pcmoutControl.window), PVw_geom.w, PVw_geom.h);
+  }
+
+  mainbox=gtk_hbox_new(FALSE, SPACING);
+  gtk_widget_show(mainbox);
+  gtk_container_add(GTK_CONTAINER(pcmoutControl.window), mainbox);
+
+
+  if (pcmoutId) {
+    // PCM Output volume widgets
+    frame=gtk_frame_new("PCM Output volume");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, TRUE, 0);
+    hbox=gtk_hbox_new(TRUE, 1);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<nPOut; i++) {
+      vbox=gtk_vbox_new(FALSE, 0);
+      gtk_widget_show(vbox);
+      gtk_container_add(GTK_CONTAINER(hbox), vbox);
+      // Channel label
+      if (i<fdOut)
+        sprintf(str, "A%d", i);
+      else
+        sprintf(str, "D%d", i-fdOut);
+      label=gtk_label_new(str);
+      gtk_widget_show(label);
+      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+      // Volume
+      pcmoutControl.adj[i]=gtk_adjustment_new(999, ECHOGAIN_MINOUT, ECHOGAIN_MAXOUT, SHORTSTEP, LONGSTEP, 0);
+      pcmoutControl.volume[i]=gtk_vscale_new(GTK_ADJUSTMENT(pcmoutControl.adj[i]));
+      gtk_widget_show(pcmoutControl.volume[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), pcmoutControl.volume[i], TRUE, TRUE, 0);
+      gtk_scale_set_draw_value(GTK_SCALE(pcmoutControl.volume[i]), 0);
+      gtk_signal_connect(GTK_OBJECT(pcmoutControl.adj[i]), "value_changed", GTK_SIGNAL_FUNC(PCM_volume_changed), (gpointer)(i+ECHO_MAXAUDIOINPUTS));
+      // Value label
+      pcmoutControl.label[i]=gtk_label_new("xxx");
+      gtk_widget_show(pcmoutControl.label[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), pcmoutControl.label[i], FALSE, FALSE, 0);
+      gtk_adjustment_set_value(GTK_ADJUSTMENT(pcmoutControl.adj[i]), INVERT(pcmoutControl.Gain[i]));
+    }
+    gtk_widget_set_usize(GTK_WIDGET(pcmoutControl.volume[0]), 0, 170);         // Set minimum y size
+  }
+
+
+/* ********** Line volume window ********** */
+
+  LVwindow=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  sprintf(str, "%s Line volume", cardId);
+  gtk_window_set_title(GTK_WINDOW (LVwindow), str);
+  gtk_window_set_wmclass(GTK_WINDOW(LVwindow), "line", "Emixer");
+  gtk_signal_connect(GTK_OBJECT(LVwindow), "delete_event", GTK_SIGNAL_FUNC(CloseWindow), (gpointer)&LVw_geom);
+  gtk_container_set_border_width(GTK_CONTAINER(LVwindow), BORDER);
+  if (LVw_geom.st!=NOPOS) {
+    gtk_widget_set_uposition(LVwindow, LVw_geom.x, LVw_geom.y);
+    gtk_window_set_default_size(GTK_WINDOW(LVwindow), LVw_geom.w, LVw_geom.h);
+  }
+
+  mainbox=gtk_hbox_new(FALSE, SPACING);
+  gtk_widget_show(mainbox);
+  gtk_container_add(GTK_CONTAINER(LVwindow), mainbox);
+
+  // Line input volume widgets
+  if (lineinId) {
+    frame=gtk_frame_new("Analog input volume");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, TRUE, 0);
+    hbox=gtk_hbox_new(TRUE, 1);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<fdIn; i++) {
+      vbox=gtk_vbox_new(FALSE, 0);
+      gtk_widget_show(vbox);
+      gtk_container_add(GTK_CONTAINER(hbox), vbox);
+      // Channel label
+      sprintf(str, "%d", i);
+      label=gtk_label_new(str);
+      gtk_widget_show(label);
+      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+      // Volume (resolution is 0.5 dB)
+      lineinControl.adj[i]=gtk_adjustment_new(999, ECHOGAIN_MININP, ECHOGAIN_MAXINP, SHORTSTEP, LONGSTEP*2, 0);
+      lineinControl.volume[i]=gtk_vscale_new(GTK_ADJUSTMENT(lineinControl.adj[i]));
+      gtk_widget_show(lineinControl.volume[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), lineinControl.volume[i], TRUE, TRUE, 0);
+      gtk_scale_set_draw_value(GTK_SCALE(lineinControl.volume[i]), 0);
+      gtk_signal_connect(GTK_OBJECT(lineinControl.adj[i]), "value_changed", GTK_SIGNAL_FUNC(PCM_volume_changed), (gpointer)i);
+      // Value label
+      lineinControl.label[i]=gtk_label_new("xxx");
+      gtk_widget_show(lineinControl.label[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), lineinControl.label[i], FALSE, FALSE, 0);
+      gtk_adjustment_set_value(GTK_ADJUSTMENT(lineinControl.adj[i]), IN_INVERT(lineinControl.Gain[i]));
+    }
+    gtk_widget_set_usize(GTK_WIDGET(lineinControl.volume[0]), 0, 170); // Set minimum y size
+  }
+
+
+  // Line output volume widgets
+  if (1) {
+    frame=gtk_frame_new("Line output volume");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, TRUE, 0);
+    hbox=gtk_hbox_new(TRUE, 1);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<nLOut; i++) {
+      vbox=gtk_vbox_new(FALSE, 0);
+      gtk_widget_show(vbox);
+      gtk_container_add(GTK_CONTAINER(hbox), vbox);
+      // Channel label
+      if (i<fdOut)
+        sprintf(str, "A%d", i);
+      else
+        sprintf(str, "D%d", i-fdOut);
+      label=gtk_label_new(str);
+      gtk_widget_show(label);
+      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+      // Volume
+      lineoutControl.adj[i]=gtk_adjustment_new(0, ECHOGAIN_MINOUT, ECHOGAIN_MAXOUT, SHORTSTEP, LONGSTEP, 0);
+      lineoutControl.volume[i]=gtk_vscale_new(GTK_ADJUSTMENT(lineoutControl.adj[i]));
+      gtk_widget_show(lineoutControl.volume[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), lineoutControl.volume[i], TRUE, TRUE, 0);
+      gtk_scale_set_draw_value(GTK_SCALE(lineoutControl.volume[i]), 0);
+      gtk_signal_connect(GTK_OBJECT(lineoutControl.adj[i]), "value_changed", GTK_SIGNAL_FUNC(LineOut_volume_changed), (gpointer)i);
+      // Value label
+      lineoutControl.label[i]=gtk_label_new("xxx");
+      gtk_widget_show(lineoutControl.label[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), lineoutControl.label[i], FALSE, FALSE, 0);
+      gtk_adjustment_set_value(GTK_ADJUSTMENT(lineoutControl.adj[i]), INVERT(lineoutControl.Gain[i]));
+    }
+    gtk_widget_set_usize(GTK_WIDGET(lineoutControl.volume[0]), 0, 170);                // Set minimum y size
+  }
+
+
+
+/* ********** Mixer window ********** */
+
+  if (mixerId) {
+    mixerControl.window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    sprintf(str, "%s Monitor mixer", cardId);
+    gtk_window_set_title(GTK_WINDOW(mixerControl.window), str);
+    gtk_window_set_wmclass(GTK_WINDOW(mixerControl.window), "mixer", "Emixer");
+    gtk_signal_connect(GTK_OBJECT(mixerControl.window), "delete_event", GTK_SIGNAL_FUNC(CloseWindow), (gpointer)&Mixerw_geom);
+    gtk_container_set_border_width(GTK_CONTAINER(mixerControl.window), BORDER);
+    if (Mixerw_geom.st!=NOPOS) {
+      gtk_widget_set_uposition(mixerControl.window, Mixerw_geom.x, Mixerw_geom.y);
+      gtk_window_set_default_size(GTK_WINDOW(mixerControl.window), Mixerw_geom.w, Mixerw_geom.h);
+//      gdk_window_move_resize(mixerControl.window->window, Mixerw_geom.x, Mixerw_geom.y, Mixerw_geom.w, Mixerw_geom.h);
+/*      gtk_widget_set_usize(mixerControl.window, Mixerw_geom.w, Mixerw_geom.h);
+      gtk_widget_set_usize(mixerControl.window, -1, -1);*/
+    }
+
+    mainbox=gtk_hbox_new(FALSE, SPACING);
+    gtk_widget_show(mainbox);
+    gtk_container_add(GTK_CONTAINER(mixerControl.window), mainbox);
+
+#ifdef REVERSE
+    // Mixer volume widgets
+    frame=gtk_frame_new("Mixer input levels");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, TRUE, 0);
+    hbox=gtk_hbox_new(TRUE, 1);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<nIn; i++) {
+      vbox=gtk_vbox_new(FALSE, 0);
+      gtk_widget_show(vbox);
+      gtk_container_add(GTK_CONTAINER(hbox), vbox);
+      // Channel label
+      if (i<fdIn)
+        sprintf(str, "A%d", i);
+      else
+        sprintf(str, "D%d", i-fdIn);
+      label=gtk_label_new(str);
+      gtk_widget_show(label);
+      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+      // Volume
+      mixerControl.adj[i]=gtk_adjustment_new(0, ECHOGAIN_MINOUT, ECHOGAIN_MAXOUT, SHORTSTEP, LONGSTEP, 0);
+      mixerControl.volume[i]=gtk_vscale_new(GTK_ADJUSTMENT(mixerControl.adj[i]));
+      gtk_widget_show(mixerControl.volume[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), mixerControl.volume[i], TRUE, TRUE, 0);
+      gtk_scale_set_draw_value(GTK_SCALE(mixerControl.volume[i]), 0);
+      gtk_signal_connect(GTK_OBJECT(mixerControl.volume[i]), "grab_focus", GTK_SIGNAL_FUNC(Monitor_volume_clicked), (gpointer)i);
+      gtk_signal_connect(GTK_OBJECT(mixerControl.adj[i]), "value_changed", GTK_SIGNAL_FUNC(Monitor_volume_changed), (gpointer)i);
+      // Value label
+      mixerControl.label[i]=gtk_label_new("xxx");
+      gtk_widget_show(mixerControl.label[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), mixerControl.label[i], FALSE, FALSE, 0);
+    }
+    gtk_widget_set_usize(GTK_WIDGET(mixerControl.volume[0]), 0, 170);          // Set minimum y size
+
+    // Output channel selectors
+    frame=gtk_frame_new("Mixer output");
+    gtk_widget_show(frame);
+    vbsel=gtk_vbox_new(FALSE, 2);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, FALSE, FALSE, 0);
+    gtk_widget_show(vbsel);
+    gtk_container_add(GTK_CONTAINER(frame), vbsel);
+
+    bgroup=0;
+    for (i=n=0; i<nLOut; i++) {
+      if (i<fdOut)
+        sprintf(str, "An-%d", i);
+      else
+        sprintf(str, "Di-%d", i-fdOut);
+      if (i)
+        bgroup=gtk_radio_button_group(GTK_RADIO_BUTTON(mixerControl.outsel[i-1]));
+      mixerControl.outsel[i]=gtk_radio_button_new_with_label(bgroup, str);
+      gtk_widget_show(mixerControl.outsel[i]);
+      gtk_box_pack_start(GTK_BOX(vbsel), mixerControl.outsel[i], FALSE, FALSE, 0);
+      gtk_signal_connect(GTK_OBJECT(mixerControl.outsel[i]), "toggled", GTK_SIGNAL_FUNC(Mixer_Output_selector_clicked), (gpointer)i);
+    }
+    mixerControl.input=0;
+    mixerControl.output=-1;
+    Mixer_Output_selector_clicked(0, 0);
+
+#else // REVERSE
+
+    // Input channel selectors
+    frame=gtk_frame_new("Mixer input");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, FALSE, FALSE, 0);
+    vbsel=gtk_vbox_new(FALSE, 2);
+    gtk_widget_show(vbsel);
+    gtk_container_add(GTK_CONTAINER(frame), vbsel);
+
+    bgroup=0;
+    for (i=n=0; i<nIn; i++) {
+      if (i<fdIn)
+        sprintf(str, "An-%d", i);
+      else
+        sprintf(str, "Di-%d", i-fdIn);
+      if (i)
+        bgroup=gtk_radio_button_group(GTK_RADIO_BUTTON(mixerControl.inpsel[i-1]));
+      mixerControl.inpsel[i]=gtk_radio_button_new_with_label(bgroup, str);
+      gtk_widget_show(mixerControl.inpsel[i]);
+      gtk_box_pack_start(GTK_BOX(vbsel), mixerControl.inpsel[i], FALSE, FALSE, 0);
+      gtk_signal_connect(GTK_OBJECT(mixerControl.inpsel[i]), "toggled", GTK_SIGNAL_FUNC(Mixer_Input_selector_clicked), (gpointer)i);
+    }
+
+    // Mixer volume widgets
+    frame=gtk_frame_new("Mixer output levels");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, TRUE, 0);
+    hbox=gtk_hbox_new(TRUE, 1);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<nLOut; i++) {
+      vbox=gtk_vbox_new(FALSE, 0);
+      gtk_widget_show(vbox);
+      gtk_container_add(GTK_CONTAINER(hbox), vbox);
+      // Channel label
+      if (i<fdOut)
+        sprintf(str, "A%d", i);
+      else
+        sprintf(str, "D%d", i-fdOut);
+      label=gtk_label_new(str);
+      gtk_widget_show(label);
+      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+      // Volume
+      mixerControl.adj[i]=gtk_adjustment_new(0, ECHOGAIN_MINOUT, ECHOGAIN_MAXOUT, SHORTSTEP, LONGSTEP, 0);
+      mixerControl.volume[i]=gtk_vscale_new(GTK_ADJUSTMENT(mixerControl.adj[i]));
+      gtk_widget_show(mixerControl.volume[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), mixerControl.volume[i], TRUE, TRUE, 0);
+      gtk_scale_set_draw_value(GTK_SCALE(mixerControl.volume[i]), 0);
+      gtk_signal_connect(GTK_OBJECT(mixerControl.volume[i]), "grab_focus", GTK_SIGNAL_FUNC(Monitor_volume_clicked), (gpointer)i);
+      gtk_signal_connect(GTK_OBJECT(mixerControl.adj[i]), "value_changed", GTK_SIGNAL_FUNC(Monitor_volume_changed), (gpointer)i);
+      // Value label
+      mixerControl.label[i]=gtk_label_new("xxx");
+      gtk_widget_show(mixerControl.label[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), mixerControl.label[i], FALSE, FALSE, 0);
+    }
+    gtk_widget_set_usize(GTK_WIDGET(mixerControl.volume[0]), 0, 170);          // Set minimum y size
+    mixerControl.input=-1;
+    mixerControl.output=0;
+    Mixer_Input_selector_clicked(0, 0);
+#endif
+  }
+
+
+/* ********** Vmixer window ********** */
+
+  if (vmixerId) {
+    vmixerControl.window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    sprintf(str, "%s Vmixer", cardId);
+    gtk_window_set_title(GTK_WINDOW(vmixerControl.window), str);
+    gtk_window_set_wmclass(GTK_WINDOW(vmixerControl.window), "vmixer", "Emixer");
+    gtk_signal_connect(GTK_OBJECT(vmixerControl.window), "delete_event", GTK_SIGNAL_FUNC(CloseWindow), (gpointer)&Vmixerw_geom);
+    gtk_container_set_border_width(GTK_CONTAINER(vmixerControl.window), BORDER);
+    if (Vmixerw_geom.st!=NOPOS) {
+      gtk_widget_set_uposition(vmixerControl.window, Vmixerw_geom.x, Vmixerw_geom.y);
+      gtk_window_set_default_size(GTK_WINDOW(vmixerControl.window), Vmixerw_geom.w, Vmixerw_geom.h);
+    }
+
+    mainbox=gtk_hbox_new(FALSE, SPACING);
+    gtk_widget_show(mainbox);
+    gtk_container_add(GTK_CONTAINER(vmixerControl.window), mainbox);
+
+#ifdef REVERSE
+
+    // Vmixer volume widgets
+    frame=gtk_frame_new("Vmixer vchannels levels");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, TRUE, 0);
+    hbox=gtk_hbox_new(TRUE, 1);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<vmixerControl.vchannels; i++) {
+      vbox=gtk_vbox_new(FALSE, 0);
+      gtk_widget_show(vbox);
+      gtk_container_add(GTK_CONTAINER(hbox), vbox);
+      // Channel label
+      sprintf(str, "V%d", i);
+      label=gtk_label_new(str);
+      gtk_widget_show(label);
+      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+      // Volume
+      vmixerControl.adj[i]=gtk_adjustment_new(0, ECHOGAIN_MINOUT, ECHOGAIN_MAXOUT, SHORTSTEP, LONGSTEP, 0);
+      vmixerControl.volume[i]=gtk_vscale_new(GTK_ADJUSTMENT(vmixerControl.adj[i]));
+      gtk_widget_show(vmixerControl.volume[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), vmixerControl.volume[i], TRUE, TRUE, 0);
+      gtk_scale_set_draw_value(GTK_SCALE(vmixerControl.volume[i]), 0);
+      gtk_signal_connect(GTK_OBJECT(vmixerControl.volume[i]), "grab_focus", GTK_SIGNAL_FUNC(Vmixer_volume_clicked), (gpointer)i);
+      gtk_signal_connect(GTK_OBJECT(vmixerControl.adj[i]), "value_changed", GTK_SIGNAL_FUNC(Vmixer_volume_changed), (gpointer)i);
+      // Value label
+      vmixerControl.label[i]=gtk_label_new("xxx");
+      gtk_widget_show(vmixerControl.label[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), vmixerControl.label[i], FALSE, FALSE, 0);
+    }
+    gtk_widget_set_usize(GTK_WIDGET(vmixerControl.volume[0]), 0, 170);         // Set minimum y size
+
+    // Input channel selectors
+    frame=gtk_frame_new("Output");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, FALSE, FALSE, 0);
+    vbsel=gtk_vbox_new(FALSE, 2);
+    gtk_widget_show(vbsel);
+    gtk_container_add(GTK_CONTAINER(frame), vbsel);
+
+    bgroup=0;
+    for (i=0; i<vmixerControl.outputs; i++) {
+      if (i<fdOut)
+        sprintf(str, "A%d", i);
+      else
+        sprintf(str, "D%d", i);
+      if (i)
+        bgroup=gtk_radio_button_group(GTK_RADIO_BUTTON(vmixerControl.outsel[i-1]));
+      vmixerControl.outsel[i]=gtk_radio_button_new_with_label(bgroup, str);
+      gtk_widget_show(vmixerControl.outsel[i]);
+      gtk_box_pack_start(GTK_BOX(vbsel), vmixerControl.outsel[i], FALSE, FALSE, 0);
+      gtk_signal_connect(GTK_OBJECT(vmixerControl.outsel[i]), "toggled", GTK_SIGNAL_FUNC(Vmixer_output_selector_clicked), (gpointer)i);
+    }
+    vmixerControl.output=-1;
+    Vmixer_output_selector_clicked(0, 0);
+
+#else // REVERSE
+
+    // Input channel selectors
+    frame=gtk_frame_new("Vchannel");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, FALSE, FALSE, 0);
+    vbsel=gtk_vbox_new(FALSE, 2);
+    gtk_widget_show(vbsel);
+    gtk_container_add(GTK_CONTAINER(frame), vbsel);
+
+    bgroup=0;
+    for (i=0; i<vmixerControl.vchannels; i++) {
+      sprintf(str, "V%d", i);
+      if (i)
+        bgroup=gtk_radio_button_group(GTK_RADIO_BUTTON(vmixerControl.vchsel[i-1]));
+      vmixerControl.vchsel[i]=gtk_radio_button_new_with_label(bgroup, str);
+      gtk_widget_show(vmixerControl.vchsel[i]);
+      gtk_box_pack_start(GTK_BOX(vbsel), vmixerControl.vchsel[i], FALSE, FALSE, 0);
+      gtk_signal_connect(GTK_OBJECT(vmixerControl.vchsel[i]), "toggled", GTK_SIGNAL_FUNC(Vmixer_vchannel_selector_clicked), (gpointer)i);
+    }
+
+    // Vmixer volume widgets
+    frame=gtk_frame_new("Vmixer output levels");
+    gtk_widget_show(frame);
+    gtk_box_pack_start(GTK_BOX(mainbox), frame, TRUE, TRUE, 0);
+    hbox=gtk_hbox_new(TRUE, 1);
+    gtk_widget_show(hbox);
+    gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+    for (i=0; i<vmixerControl.outputs; i++) {
+      vbox=gtk_vbox_new(FALSE, 0);
+      gtk_widget_show(vbox);
+      gtk_container_add(GTK_CONTAINER(hbox), vbox);
+      // Channel label
+      if (i<fdOut)
+        sprintf(str, "A%d", i);
+      else
+        sprintf(str, "D%d", i-fdOut);
+      label=gtk_label_new(str);
+      gtk_widget_show(label);
+      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+      // Volume
+      vmixerControl.adj[i]=gtk_adjustment_new(0, ECHOGAIN_MINOUT, ECHOGAIN_MAXOUT, SHORTSTEP, LONGSTEP, 0);
+      vmixerControl.volume[i]=gtk_vscale_new(GTK_ADJUSTMENT(vmixerControl.adj[i]));
+      gtk_widget_show(vmixerControl.volume[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), vmixerControl.volume[i], TRUE, TRUE, 0);
+      gtk_scale_set_draw_value(GTK_SCALE(vmixerControl.volume[i]), 0);
+      gtk_signal_connect(GTK_OBJECT(vmixerControl.volume[i]), "grab_focus", GTK_SIGNAL_FUNC(Vmixer_volume_clicked), (gpointer)i);
+      gtk_signal_connect(GTK_OBJECT(vmixerControl.adj[i]), "value_changed", GTK_SIGNAL_FUNC(Vmixer_volume_changed), (gpointer)i);
+      // Value label
+      vmixerControl.label[i]=gtk_label_new("xxx");
+      gtk_widget_show(vmixerControl.label[i]);
+      gtk_box_pack_start(GTK_BOX(vbox), vmixerControl.label[i], FALSE, FALSE, 0);
+    }
+    gtk_widget_set_usize(GTK_WIDGET(vmixerControl.volume[0]), 0, 170);         // Set minimum y size
+    vmixerControl.vchannel=-1;
+    Vmixer_vchannel_selector_clicked(0, 0);
+#endif
+  }
+
+
+/* ********** Main window ********** */
+
+  Mainwindow=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  sprintf(str, EM_VERSION, cardId);
+  gtk_window_set_title(GTK_WINDOW(Mainwindow), str);
+  gtk_window_set_wmclass(GTK_WINDOW(Mainwindow), "emixer", "Emixer");
+  gtk_signal_connect(GTK_OBJECT(Mainwindow), "delete_event", GTK_SIGNAL_FUNC(Mainwindow_delete), (gpointer)&Mainw_geom);
+  gtk_container_set_border_width(GTK_CONTAINER(Mainwindow), BORDER);
+  gtk_widget_show(Mainwindow);
+  if (Mainw_geom.x!=NOPOS) {
+    gtk_widget_set_uposition(Mainwindow, Mainw_geom.x, Mainw_geom.y);
+    gtk_window_set_default_size(GTK_WINDOW(Mainwindow), Mainw_geom.w, Mainw_geom.h);
+  }
+
+  mainbox=gtk_hbox_new(FALSE, SPACING);
+  gtk_widget_show(mainbox);
+  gtk_container_add(GTK_CONTAINER(Mainwindow), mainbox);
+
+
+  // Gang button and its frame
+  frame=gtk_frame_new("Gang");
+  gtk_widget_show(frame);
+  gtk_box_pack_start(GTK_BOX(mainbox), frame, FALSE, FALSE, 0);
+  hbox=gtk_hbox_new(FALSE, 0);
+  gtk_widget_show(hbox);
+  gtk_container_add(GTK_CONTAINER(frame), hbox);
+  button=gtk_toggle_button_new_with_label("On");
+  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
+  gtk_widget_show(button);
+  gtk_signal_connect(GTK_OBJECT(button), "toggled", Gang_button_toggled, 0);
+
+  // Controls frame
+  frame=gtk_frame_new("Controls");
+  gtk_widget_show(frame);
+  gtk_box_pack_end(GTK_BOX(mainbox), frame, FALSE, FALSE, 0);
+  hbox=gtk_hbox_new(FALSE, 0);
+  gtk_widget_show(hbox);
+  gtk_container_add(GTK_CONTAINER(frame), hbox);
+
+  // VUmeters button
+  if (vumetersId && vuswitchId) {
+    button=gtk_toggle_button_new_with_label("VU");
+    gtk_widget_show(button);
+    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
+    gtk_signal_connect(GTK_OBJECT(button), "toggled", VUmeters_button_click, 0);
+    VUw_geom.toggler=button;
+    if (VUw_geom.st==1)
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+  }
+
+  // Line volume button
+  button=gtk_toggle_button_new_with_label("Line");
+  gtk_widget_show(button);
+  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
+  gtk_signal_connect(GTK_OBJECT(button), "toggled", ToggleWindow, (gpointer)LVwindow);
+  LVw_geom.toggler=button;
+  if (LVw_geom.st==1)
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+
+  // Misc controls button
+  button=gtk_toggle_button_new_with_label("Misc");
+  gtk_widget_show(button);
+  gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
+  gtk_signal_connect(GTK_OBJECT(button), "toggled", ToggleWindow, (gpointer)Miscwindow);
+  Miscw_geom.toggler=button;
+  if (Miscw_geom.st==1)
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+
+  if (mixerId) {
+    // Graphical mixer button
+    button=gtk_toggle_button_new_with_label("GrMix");
+    gtk_widget_show(button);
+    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
+    gtk_signal_connect(GTK_OBJECT(button), "toggled", GMixer_button_click, 0);
+    GMw_geom.toggler=button;
+    if (GMw_geom.st==1)
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+
+    // Mixer button
+    button=gtk_toggle_button_new_with_label("Mixer");
+    gtk_widget_show(button);
+    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
+    gtk_signal_connect(GTK_OBJECT(button), "toggled", ToggleWindow, (gpointer)mixerControl.window);
+    Mixerw_geom.toggler=button;
+    if (Mixerw_geom.st==1)
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+  }
+
+  if (vmixerId) {
+    // Vmixer button
+    button=gtk_toggle_button_new_with_label("Vmixer");
+    gtk_widget_show(button);
+    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
+    gtk_signal_connect(GTK_OBJECT(button), "toggled", ToggleWindow, (gpointer)vmixerControl.window);
+    Vmixerw_geom.toggler=button;
+    if (Vmixerw_geom.st==1)
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+  }
+
+  if (pcmoutId) {
+    // PCM volume button
+    button=gtk_toggle_button_new_with_label("PCM");
+    gtk_widget_show(button);
+    gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 1);
+    gtk_signal_connect(GTK_OBJECT(button), "toggled", ToggleWindow, (gpointer)pcmoutControl.window);
+    PVw_geom.toggler=button;
+    if (PVw_geom.st==1)
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+  }
+
+
+
+
+/* ********** GTK-main ********** */
+
+  Gang=1;
+  if (dmodeId)
+    Digital_mode_activate(dmodeOpt, (gpointer)dmodeVal);       // Also calls SetSensitivity()
+  gtk_widget_show(Mainwindow);
+  gtk_main();
+
+  if (save) {
+    FILE *f;
+    if (snprintf(str, 255, "%s/.Emixer_%s", getenv("HOME"), cardId)>0) {
+      str[255]=0;
+      if ((f=fopen(str, "w"))) {
+        fprintf(f, "-- LineOut <channel> <gain>\n");
+        for (i=0; i<nLOut; i++)
+          fprintf(f, "LineOut %2d %d\n", i, lineoutControl.Gain[i]);
+        fprintf(f, "-- LineIn <channel> <gain>\n");
+        for (i=0; i<nIn; i++)
+          fprintf(f, "LineIn %2d %d\n", i, lineinControl.Gain[i]);
+        fprintf(f, "-- PcmOut <channel> <gain>\n");
+        for (i=0; i<nPOut; i++)
+          fprintf(f, "PcmOut %2d %d\n", i, pcmoutControl.Gain[i]);
+        fprintf(f, "-- Mixer <output> <input> <gain>\n");
+        for (o=0; o<nLOut; o++)
+          for (i=0; i<nIn; i++)
+            fprintf(f, "Mixer %2d %2d %d\n", o, i, mixerControl.mixer[o][i].Gain);
+        fprintf(f, "-- Vmixer <output> <vchannel> <gain>\n");
+        if (vmixerId)
+          for (o=0; o<nLOut; o++)
+            for (i=0; i<nPOut; i++)
+              fprintf(f, "Vmixer %2d %2d %d\n", o, i, vmixerControl.mixer[o][i].Gain);
+        fprintf(f, "-- xxWindow <x> <y> <width> <height> <visible>\n");
+        fprintf(f, "MainWindow %d %d %d %d\n", Mainw_geom.x, Mainw_geom.y, Mainw_geom.w, Mainw_geom.h);
+        if (VUwindow)
+          gdk_window_get_root_origin(VUwindow->window, &VUw_geom.x, &VUw_geom.y);
+        fprintf(f, "VUmetersWindow %d %d %d\n", VUw_geom.x, VUw_geom.y, VUw_geom.st);
+        if (GMwindow)
+          gdk_window_get_root_origin(GMwindow->window, &VUw_geom.x, &VUw_geom.y);
+        fprintf(f, "GfxMixerWindow %d %d %d\n", GMw_geom.x, GMw_geom.y, GMw_geom.st);
+        if (pcmoutId) {
+          if (pcmoutControl.window->window) {
+            gdk_window_get_root_origin(pcmoutControl.window->window, &PVw_geom.x, &PVw_geom.y);
+            gdk_window_get_size(pcmoutControl.window->window, &PVw_geom.w, &PVw_geom.h);
+          }
+          fprintf(f, "PcmVolumeWindow %d %d %d %d %d\n", PVw_geom.x, PVw_geom.y, PVw_geom.w, PVw_geom.h, !!GTK_WIDGET_VISIBLE(pcmoutControl.window));
+        }
+        if (LVwindow->window) {
+          gdk_window_get_root_origin(LVwindow->window, &LVw_geom.x, &LVw_geom.y);
+          gdk_window_get_size(LVwindow->window, &LVw_geom.w, &LVw_geom.h);
+        }
+        fprintf(f, "LineVolumeWindow %d %d %d %d %d\n", LVw_geom.x, LVw_geom.y, LVw_geom.w, LVw_geom.h, !!GTK_WIDGET_VISIBLE(LVwindow));
+        if (Miscwindow->window) {
+          gdk_window_get_root_origin(Miscwindow->window, &Miscw_geom.x, &Miscw_geom.y);
+          gdk_window_get_size(Miscwindow->window, &Miscw_geom.w, &Miscw_geom.h);
+        }
+        fprintf(f, "MiscControlsWindow %d %d %d %d %d\n", Miscw_geom.x, Miscw_geom.y, Miscw_geom.w, Miscw_geom.h, !!GTK_WIDGET_VISIBLE(Miscwindow));
+        if (mixerId) {
+          if (mixerControl.window->window) {
+            gdk_window_get_root_origin(mixerControl.window->window, &Mixerw_geom.x, &Mixerw_geom.y);
+            gdk_window_get_size(mixerControl.window->window, &Mixerw_geom.w, &Mixerw_geom.h);
+          }
+          fprintf(f, "MixerWindow %d %d %d %d %d\n", Mixerw_geom.x, Mixerw_geom.y, Mixerw_geom.w, Mixerw_geom.h, !!GTK_WIDGET_VISIBLE(mixerControl.window));
+        }
+        if (vmixerId) {
+          if (vmixerControl.window->window) {
+            gdk_window_get_root_origin(vmixerControl.window->window, &Vmixerw_geom.x, &Vmixerw_geom.y);
+            gdk_window_get_size(vmixerControl.window->window, &Vmixerw_geom.w, &Vmixerw_geom.h);
+          }
+          fprintf(f, "VmixerWindow %d %d %d %d %d\n", Vmixerw_geom.x, Vmixerw_geom.y, Vmixerw_geom.w, Vmixerw_geom.h, !!GTK_WIDGET_VISIBLE(vmixerControl.window));
+        }
+        fprintf(f, "\n");
+        fclose(f);
+      }
+    }
+  }
+
+  if (VUwindow) {
+    SetVUmeters(0);
+    gtk_timeout_remove(VUtimer);
+  }
+  if (GMwindow) {
+    SetVUmeters(0);
+    gtk_timeout_remove(Mixtimer);
+  }
+  snd_ctl_close(ctlhandle);
+  return(0);
+}
+
+/*
+TODO:
+non controlla se c'รจ mixerId e forse altri
+controllare tutorial su GdkColor e tracciamento
+*/
+
+